diff --git a/package.json b/package.json index 775aba0..94444c1 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "preinstall": "npm run build:local:packages", "start": "npm start -w graphql-mesh", "startmesh": "npm run startmesh -w graphql-mesh", - "serve": "npm run serve -w graphql-mesh" - }, + "serve": "npm run serve -w graphql-mesh" + }, "devDependencies": { "concurrently": "^8.2.2", "patch-package": "^8.0.0" diff --git a/packages/graphql-mesh/custom-plugins/monitor-envelop.ts b/packages/graphql-mesh/custom-plugins/monitor-envelop.ts index 1506f4a..b56c1fe 100644 --- a/packages/graphql-mesh/custom-plugins/monitor-envelop.ts +++ b/packages/graphql-mesh/custom-plugins/monitor-envelop.ts @@ -1,110 +1,146 @@ import { type Plugin } from '@envelop/core' import { Logger } from '../utils/logger' -import { NoSchemaIntrospectionCustomRule } from 'graphql'; -import { GraphQLError } from 'graphql'; +import { NoSchemaIntrospectionCustomRule } from 'graphql' +import { GraphQLError } from 'graphql' /** - * monitor plugin in order to get event contextual log and add some security rules - * useful to - * - log the graphql Query event - * - add desabled introspection validation rule - * - remove suggestion message - * - log the execute result summary with executes duration - * - remove not allowed introspection in result + * Monitor plugin to get event contextual logs and add some security rules. + * Used to: + * - log the GraphQL Query event + * - add a disabled introspection validation rule + * - remove suggestion messages + * - log the execute result summary with execution duration + * - remove not allowed introspection in the result */ const formatter = (error: GraphQLError, mask: string): GraphQLError => { - if (error instanceof GraphQLError) { - error.message = error.message.replace(/Did you mean ".+"/g, mask); - } - return error as GraphQLError; -}; + if (error instanceof GraphQLError) { + error.message = error.message.replace(/Did you mean ".+"/g, mask) + } + return error as GraphQLError +} + export default ({ options }): Plugin => { - // not allow by default - const allowIntrospection = options?.introspection?.allow || process.env['ENABLED_INTROSPECTION'] || false - // low info in log by default - const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : "low" - const denyIntrospectionHeaderName = options?.introspection?.denyHeaderName || null - const denyIntrospectionHeaderValue = options?.introspection?.denyHeaderValue || null - const allowIntrospectionHeaderName = options?.introspection?.allowHeaderName || null - const allowIntrospectionHeaderValue = options?.introspection?.allowHeaderValue || null - const isMaskSuggestion = options?.maskSuggestion?.enabled || false - const maskSuggestionMessage = options?.maskSuggestion?.message || "" - return { - onParse({ context }) { - if (options.logOnParse) { - Logger.graphqlQuery(context['request']['headers'], context['params']) - } - }, + // Not allowed by default + const allowIntrospection = + options?.introspection?.allow || process.env['ENABLED_INTROSPECTION'] || false + // Low info in log by default + const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : 'low' + const denyIntrospectionHeaderName = options?.introspection?.denyHeaderName || null + const denyIntrospectionHeaderValue = options?.introspection?.denyHeaderValue || null + const allowIntrospectionHeaderName = options?.introspection?.allowHeaderName || null + const allowIntrospectionHeaderValue = options?.introspection?.allowHeaderValue || null + const isMaskSuggestion = options?.maskSuggestion?.enabled || false + const maskSuggestionMessage = options?.maskSuggestion?.message || '' - onValidate: ({ addValidationRule, context }) => { - const headers = context['request'].headers - let deny = true - /* - allowIntrospection=false : intropection deny for all - denyIntrospectionHeaderName : name of the header to check to deny introspection is deny ex plublic proxy header - allowIntrospectionHeaderName : name of the header allow if this header and value is presents - */ - // if introspection not allow - if (allowIntrospection) { - // intropection may be allow - deny = false - // is existed a header to deny introspection - if (denyIntrospectionHeaderName) { - if (headers.get(denyIntrospectionHeaderName)) { - if (headers.get(denyIntrospectionHeaderName).includes(denyIntrospectionHeaderValue)) { - Logger.denyIntrospection("onValidate", "deny by headers " + denyIntrospectionHeaderName + ": " + headers.get(denyIntrospectionHeaderName), headers) - deny = true - } - } - } - // is existed a header mandatory to allow introspection - if (allowIntrospectionHeaderName) { - deny = true - if (headers.get(allowIntrospectionHeaderName)) { - if (headers.get(allowIntrospectionHeaderName).includes(allowIntrospectionHeaderValue)) { - Logger.allowIntrospection("onValidate", "allow by headers " + allowIntrospectionHeaderName + ": " + headers.get(allowIntrospectionHeaderName).substring(0, 4) + "...", headers) - deny = false - } else { - Logger.denyIntrospection("onValidate", "deny by bad header value " + allowIntrospectionHeaderName + ": " + headers.get(allowIntrospectionHeaderName).substring(0, 4) + "...", headers) - } - } else { - Logger.denyIntrospection("onValidate", "deny by no header " + allowIntrospectionHeaderName, headers) - } - } - } - if (deny) { - addValidationRule(NoSchemaIntrospectionCustomRule) - } + return { + onParse({ context }) { + if (options.logOnParse) { + Logger.graphqlQuery(context['request']['headers'], context['params']) + } + }, - return function onValidateEnd({ valid, result, setResult }) { - if (isMaskSuggestion && !valid) { - setResult(result.map((error: GraphQLError) => formatter(error, maskSuggestionMessage))); - } - }; - }, + onValidate: ({ addValidationRule, context }) => { + const headers = context['request'].headers + let deny = true + /* + allowIntrospection = false: introspection denied for all + denyIntrospectionHeaderName: name of the header to check to deny introspection, e.g., public proxy header + allowIntrospectionHeaderName: name of the header to allow if this header and value are present + */ + if (allowIntrospection) { + // Introspection may be allowed + deny = false + // If there exists a header to deny introspection + if (denyIntrospectionHeaderName) { + if (headers.get(denyIntrospectionHeaderName)) { + if (headers.get(denyIntrospectionHeaderName).includes(denyIntrospectionHeaderValue)) { + Logger.denyIntrospection( + 'onValidate', + 'Denied by headers ' + + denyIntrospectionHeaderName + + ': ' + + headers.get(denyIntrospectionHeaderName), + headers + ) + deny = true + } + } + } + // If there exists a mandatory header to allow introspection + if (allowIntrospectionHeaderName) { + deny = true + if (headers.get(allowIntrospectionHeaderName)) { + if (headers.get(allowIntrospectionHeaderName).includes(allowIntrospectionHeaderValue)) { + Logger.allowIntrospection( + 'onValidate', + 'Allowed by headers ' + + allowIntrospectionHeaderName + + ': ' + + headers.get(allowIntrospectionHeaderName).substring(0, 4) + + '...', + headers + ) + deny = false + } else { + Logger.denyIntrospection( + 'onValidate', + 'Denied by bad header value ' + + allowIntrospectionHeaderName + + ': ' + + headers.get(allowIntrospectionHeaderName).substring(0, 4) + + '...', + headers + ) + } + } else { + Logger.denyIntrospection( + 'onValidate', + 'Denied by missing header ' + allowIntrospectionHeaderName, + headers + ) + } + } + } + if (deny) { + addValidationRule(NoSchemaIntrospectionCustomRule) + } - onExecute(/*{ args, extendContext }*/) { - let timestampDebut = new Date().getTime() - return { - before() { + return function onValidateEnd({ valid, result, setResult }) { + if (isMaskSuggestion && !valid) { + setResult(result.map((error: GraphQLError) => formatter(error, maskSuggestionMessage))) + } + } + }, - timestampDebut = new Date().getTime() - }, - onExecuteDone({ result, args }) { - const timestampDone = new Date().getTime(); - // short cut to desabled introspection response in case of bad configuration rule - if (!allowIntrospection && args.contextValue['params'].query.includes('__schema')) { - result['data'] = {} - result['errors'] = [{ message: "Fordidden" }] - Logger.error('SECU', 'onExecute', 'Introspection query deteted not allowed', args.contextValue['params']) - } - if (options.loOnExecuteDone) { - Logger.endExec(args.contextValue['request']['headers'], result, timestampDone - timestampDebut, resultLogInfoLevel) - } - } - } - } - } + onExecute(/* { args, extendContext } */) { + let timestampStart = new Date().getTime() + return { + before() { + timestampStart = new Date().getTime() + }, + onExecuteDone({ result, args }) { + const timestampEnd = new Date().getTime() + // Shortcut to disable introspection response in case of bad configuration rule + if (!allowIntrospection && args.contextValue['params'].query.includes('__schema')) { + result['data'] = {} + result['errors'] = [{ message: 'Forbidden' }] + Logger.error( + 'SECU', + 'onExecute', + 'Introspection query detected and not allowed', + args.contextValue['params'] + ) + } + if (options.logOnExecuteDone) { + Logger.endExec( + args.contextValue['request']['headers'], + result, + timestampEnd - timestampStart, + resultLogInfoLevel + ) + } + } + } + } + } } - - diff --git a/packages/graphql-mesh/custom-plugins/monitor-fetch.ts b/packages/graphql-mesh/custom-plugins/monitor-fetch.ts index 26d7437..d5ce872 100644 --- a/packages/graphql-mesh/custom-plugins/monitor-fetch.ts +++ b/packages/graphql-mesh/custom-plugins/monitor-fetch.ts @@ -1,65 +1,63 @@ -import { type Plugin } from '@envelop/core'; +import { type Plugin } from '@envelop/core' import { Logger } from '../utils/logger' /** - * monitor fetch source - * use to : - * - add log event for each fetch like, url, response status, duration + * Monitor fetch source + * Used to: + * - add log events for each fetch such as URL, response status, and duration */ export default ({ options }) => { - return { - onFetch({ context, info }) { - if (!info) { - Logger.warn("noFeychInfo", "onFetch", "no info in on fetch") - return; - } - const start = Date.now(); - let rawSource = context[info.sourceName] - let description = info.parentType._fields[info.path.key].description + return { + onFetch({ context, info }) { + if (!info) { + Logger.warn('noFetchInfo', 'onFetch', 'No info in on fetch') + return + } + const start = Date.now() + let rawSource = context[info.sourceName] + let description = info.parentType._fields[info.path.key].description - return (fetch: any) => { - if (options.logOnFetch) { - const duration = Date.now() - start; - let fetchInfo = {} - let httpStatus = null - let url = null - if (options.fullFetchInfo) { - fetchInfo = { - fieldName: info.fieldName, - sourceName: info.sourceName, - pathKey: info.path.key, - operation: info.operation.name, - variables: info.variables, - endpoint: rawSource.rawSource.handler.config.endpoint, - description: description - } - } else { - fetchInfo = { - fieldName: info.fieldName, - pathKey: info.path.key, - operation: info.operation.name, - variables: info.variableValues, - endpoint: rawSource.rawSource.handler.config.endpoint, - } - } - //const fetchResponseInfo = {} - if (fetch.response) { - - httpStatus = fetch.response.status - url = fetch.response.url - const options = fetch.response.options - if (options) { - fetchInfo['options'] = { - requestId: options.headers['x-request-id'], - server: options.headers['server'] - } - } - } - Logger.onFetch(context.request, url, httpStatus, duration, fetchInfo) - } - } - } - } + return (fetch: any) => { + if (options.logOnFetch) { + const duration = Date.now() - start + let fetchInfo = {} + let httpStatus = null + let url = null + if (options.fullFetchInfo) { + fetchInfo = { + fieldName: info.fieldName, + sourceName: info.sourceName, + pathKey: info.path.key, + operation: info.operation.name, + variables: info.variables, + endpoint: rawSource.rawSource.handler.config.endpoint, + description: description + } + } else { + fetchInfo = { + fieldName: info.fieldName, + pathKey: info.path.key, + operation: info.operation.name, + variables: info.variableValues, + endpoint: rawSource.rawSource.handler.config.endpoint + } + } + if (fetch.response) { + httpStatus = fetch.response.status + url = fetch.response.url + const options = fetch.response.options + if (options) { + fetchInfo['options'] = { + requestId: options.headers['x-request-id'], + server: options.headers['server'] + } + } + } + Logger.onFetch(context.request, url, httpStatus, duration, fetchInfo) + } + } + } + } } diff --git a/packages/graphql-mesh/custom-plugins/monitor-yoga.ts b/packages/graphql-mesh/custom-plugins/monitor-yoga.ts index 02ae05e..4506325 100644 --- a/packages/graphql-mesh/custom-plugins/monitor-yoga.ts +++ b/packages/graphql-mesh/custom-plugins/monitor-yoga.ts @@ -3,116 +3,138 @@ import { Logger } from '../utils/logger' import { GraphQLError } from 'graphql' import { v4 as uuidv4 } from 'uuid' /** - * monitor plugin in order to get event contextual log and add some security rules - * useful to : - * - log new request comming event - * - add request timestamp in headers to get duration time - * - monitor instropection request - * - mask error in result is required ( ex in production ) - * - log response sumary event - * - remove a eventualy not allowed instropection data in result + * Monitor plugin to get event contextual logs and add some security rules. + * Used to: + * - log new incoming request events + * - add request timestamps in headers to get duration time + * - monitor introspection requests + * - mask errors in the result if required (e.g., in production) + * - log response summary events + * - remove potentially not allowed introspection data in the result */ -export function useYagaMonitoring({ options }): Plugin { - const isMaskErrors = options?.maskError?.enabled || process.env['MASK_ERRORS'] || false - // filter error in production anyway - const isFilterError = options?.filterError?.enabled || process.env['FILTER_ERRORS'] == 'true' || process.env['IS_PROUCTION_ENV'] == 'true' || false - const errorMaskMessage = options?.maskError?.message ? options.maskError.message : "something goes wrong" - const responseLogInfoLevel = options?.responseLogInfoLevel ? options.responseLogInfoLevel : "medium" - const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : "medium" +export function useYogaMonitoring({ options }): Plugin { + const isMaskErrors = options?.maskError?.enabled || process.env['MASK_ERRORS'] || false + // Filter errors in production anyway + const isFilterError = + options?.filterError?.enabled || + process.env['FILTER_ERRORS'] == 'true' || + process.env['IS_PRODUCTION_ENV'] == 'true' || + false + const errorMaskMessage = options?.maskError?.message + ? options.maskError.message + : 'Something went wrong' + const responseLogInfoLevel = options?.responseLogInfoLevel + ? options.responseLogInfoLevel + : 'medium' + const resultLogInfoLevel = options?.resultLogInfoLevel ? options.resultLogInfoLevel : 'medium' - return { - onRequest({ request/*, fetchAPI, endResponse */ }) { - if (options.LogOnRequest) { - // log only graphql request, avoid log other request like metric requests - if (request.url.includes("/graphql")) { - Logger.onRequest(request) - } - } + return { + onRequest({ request /*, fetchAPI, endResponse */ }) { + if (options.LogOnRequest) { + // Log only GraphQL requests, avoid logging other requests like metric requests + if (request.url.includes('/graphql')) { + Logger.onRequest(request) + } + } - // add resuestTimestamp in headers - const timestamp = new Date().getTime(); - request.headers.append("requestTimestamp", String(timestamp)) + // Add requestTimestamp in headers + const timestamp = new Date().getTime() + request.headers.append('requestTimestamp', String(timestamp)) - // add x-request-id in header if not present - if (!request.headers.get('x-request-id')) { - request.headers.append("x-request-id", uuidv4()) - } + // Add x-request-id in header if not present + if (!request.headers.get('x-request-id')) { + request.headers.append('x-request-id', uuidv4()) + } + }, + onRequestParse(args) { + const beforeTimestamp = new Date().getTime() + let requestHeaders = args.request.headers + return { + onRequestParseDone(nRequestParseDoneEventPayload) { + const timestamp = new Date().getTime() + if (options.logOnRequestParseDone) { + Logger.onRequestParseDone( + requestHeaders, + nRequestParseDoneEventPayload.requestParserResult['query'], + nRequestParseDoneEventPayload.requestParserResult['operationName'], + nRequestParseDoneEventPayload.requestParserResult['variables'], + timestamp - beforeTimestamp + ) + } + if (nRequestParseDoneEventPayload.requestParserResult['query'].includes('__schema')) { + Logger.introspection( + requestHeaders, + nRequestParseDoneEventPayload.requestParserResult['query'] + ) + } + } + } + }, + onResultProcess(args) { + if (options.logOnResultProcess) { + // Calculate duration from request timestamp + let requestTimestamp: number = 0 + if (args.request['headers']) { + const requestTimestampString = args.request['headers'].get('requesttimestamp') + if (requestTimestampString) { + requestTimestamp = parseInt(requestTimestampString) + } + } + const responseTimestamp = new Date().getTime() + Logger.onResultProcess( + args.request, + args.result, + requestTimestamp > 0 ? responseTimestamp - requestTimestamp : 0, + resultLogInfoLevel + ) + } + // If we want to replace all messages with a generic message + if (isMaskErrors) { + if (args.result['errors']) { + let errors = args.result['errors'] + for (let i = 0; i < errors.length; i++) { + errors[i] = errorMaskMessage + } + } + } else { + // If we want to filter errors to only return the message, don't return extended information like stack traces + if (args.result['errors']) { + if (isFilterError) { + let errors = args.result['errors'] + for (let i = 0; i < errors.length; i++) { + // Return only message and path + errors[i] = new GraphQLError(filterErrorMessage(errors[i]['message']), { + path: errors[i]['path'] + }) + } + } + } + } + }, - }, - onRequestParse(args) { - const beforeTimestamp = new Date().getTime(); - let requestHeaders = args.request.headers - return { - onRequestParseDone(nRequestParseDoneEventPayload) { - const timestamp = new Date().getTime(); - if (options.logOnRequestParseDone) { - Logger.onRequestParseDone(requestHeaders, nRequestParseDoneEventPayload.requestParserResult['query'], nRequestParseDoneEventPayload.requestParserResult['operationName'], nRequestParseDoneEventPayload.requestParserResult['variables'], timestamp - beforeTimestamp) - } - if (nRequestParseDoneEventPayload.requestParserResult['query'].includes('__schema')) { - Logger.introspection(requestHeaders, nRequestParseDoneEventPayload.requestParserResult['query']) - } - } - } - }, - onResultProcess(args) { - if (options.logOnResultProcess) { - // calculate duration from request timestamp - let requestTimestamp: number = 0 - if (args.request['headers']) { - const requestTimestampString = args.request['headers'].get('requesttimestamp') - if (requestTimestampString) { - requestTimestamp = parseInt(requestTimestampString) - } - } - const responseTimestamp = new Date().getTime(); - Logger.onResultProcess(args.request, args.result, requestTimestamp > 0 ? responseTimestamp - requestTimestamp : 0, resultLogInfoLevel) - } - // if we want to replace all message with a generic message - if (isMaskErrors) { - if (args.result['errors']) { - let errors = args.result['errors'] - for (let i = 0; i < errors.length; i++) { - errors[i] = errorMaskMessage - } - } - } else { - // if we want to filter error to only return the message, don't return extend information like stacktrace - - if (args.result['errors']) { - if (isFilterError) { - let errors = args.result['errors'] - for (let i = 0; i < errors.length; i++) { - // return only message and path - errors[i] = new GraphQLError(filterErrorMessage(errors[i]['message']), { path: errors[i]['path'] }) - } - } - } - } - }, - - onResponse({ request, response }) { - // dont log options http - if (request.method != 'OPTIONS') { - if (options.logOnResponse) { - // only log graphql request don't log metrics or other requests - if (request.url.includes("/graphql")) { - Logger.onResponse(request, response, responseLogInfoLevel) - } - } - } - } - } + onResponse({ request, response }) { + // Don't log OPTIONS HTTP method + if (request.method != 'OPTIONS') { + if (options.logOnResponse) { + // Only log GraphQL requests, don't log metrics or other requests + if (request.url.includes('/graphql')) { + Logger.onResponse(request, response, responseLogInfoLevel) + } + } + } + } + } } /** filterErrorMessage - * use to filter error message : - * - remove disabled introspection - * todo: add other filter rules to remove non expecting message -*/ + * Used to filter error messages: + * - Removes disabled introspection messages + * TODO: Add other filter rules to remove unexpected messages + */ function filterErrorMessage(message: string) { - if (message.includes("introspection has been disabled")) { - return "forbidden" - } - return message + if (message.includes('introspection has been disabled')) { + return 'forbidden' + } + return message } diff --git a/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz b/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz index aa64f12..14325af 100644 Binary files a/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/directive-spl-1.0.0.tgz differ diff --git a/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz b/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz index 1f45b74..93b007f 100644 Binary files a/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz and b/packages/graphql-mesh/local-pkg/inject-additional-transforms-1.0.0.tgz differ diff --git a/packages/graphql-mesh/package.json b/packages/graphql-mesh/package.json index 2391c34..35dc307 100644 --- a/packages/graphql-mesh/package.json +++ b/packages/graphql-mesh/package.json @@ -6,15 +6,15 @@ "postinstall": "patch-package", "serve": "npm run build && sucrase-node serve.ts", "start": "npm run downloadswaggers && mesh dev", - "startmesh": "mesh build", - "server": "sucrase-node serve.ts", - "test": "vitest" + "startmesh": "mesh build", + "server": "sucrase-node serve.ts", + "test": "vitest" }, "dependencies": { "@graphql-mesh/cache-localforage": "^0.96.2", "@graphql-mesh/cli": "^0.88.4", "@graphql-mesh/graphql": "^0.96.2", - "@graphql-mesh/plugin-prometheus": "^0.96.2", + "@graphql-mesh/plugin-prometheus": "^0.96.2", "@graphql-mesh/merger-stitching": "^0.96.2", "@graphql-mesh/openapi": "^0.97.4", "@graphql-mesh/transform-filter-schema": "^0.96.2", @@ -23,18 +23,18 @@ "@graphql-mesh/transform-type-merging": "^0.96.2", "@graphql-tools/schema": "^10.0.2", "@graphql-tools/utils": "^10.0.12", - "prom-client": "^15.1.3", + "prom-client": "^15.1.3", "directive-spl": "file:./local-pkg/directive-spl-1.0.0.tgz", - "monitor-envelop": "file:./custom-plugins/monitor-envelop.ts", - "monitor-fetch": "file:./custom-plugins/monitor-fetch.ts", - "monitor-yoga": "file:./custom-plugins/monitor-yoga.ts", + "monitor-envelop": "file:./custom-plugins/monitor-envelop.ts", + "monitor-fetch": "file:./custom-plugins/monitor-fetch.ts", + "monitor-yoga": "file:./custom-plugins/monitor-yoga.ts", "filter-null-plugin": "file:./custom-plugins/filter-null.ts", "glob": "^10.3.10", "graphql": "^16.8.1", "inject-additional-transforms": "file:./local-pkg/inject-additional-transforms-1.0.0.tgz", "patch-package": "^8.0.0", "sucrase": "^3.35.0", - "uuid": "^10.0.0" + "uuid": "^10.0.0" }, "devDependencies": { "@graphql-mesh/types": "^0.96.3", diff --git a/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch b/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch index 5778222..3257901 100644 --- a/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch +++ b/packages/graphql-mesh/patches/@omnigraph+json-schema+0.97.4.patch @@ -37,4 +37,4 @@ index 8006747..f2af17c + const headers = {}; for (const headerName in globalOperationHeaders) { - const nonInterpolatedValue = globalOperationHeaders[headerName]; \ No newline at end of file + const nonInterpolatedValue = globalOperationHeaders[headerName]; diff --git a/packages/graphql-mesh/serve.ts b/packages/graphql-mesh/serve.ts index aa1b9fb..34690e6 100644 --- a/packages/graphql-mesh/serve.ts +++ b/packages/graphql-mesh/serve.ts @@ -1,13 +1,12 @@ import { createServer } from 'node:http' import { getConfig } from './utils/parseYamlConfig' import { Logger } from './utils/logger' -const mesh = process.env['MESH_ARTIFACTS_DIRNAME'] || ".mesh" -import(mesh) - .then(obj => { - const config = getConfig() - const PORT = config.serve?.port ?? 4000 - const HOSTNAME = config.serve?.hostname ?? 'http://0.0.0.0' - const server = createServer(obj.createBuiltMeshHTTPHandler()) - Logger.info('STARTUP', 'main', `🚀 Server ready at ${HOSTNAME}:${PORT}/graphql`) - server.listen(PORT) - }) +const mesh = process.env['MESH_ARTIFACTS_DIRNAME'] || '.mesh' +import(mesh).then((obj) => { + const config = getConfig() + const PORT = config.serve?.port ?? 4000 + const HOSTNAME = config.serve?.hostname ?? 'http://0.0.0.0' + const server = createServer(obj.createBuiltMeshHTTPHandler()) + Logger.info('STARTUP', 'main', `🚀 Server ready at ${HOSTNAME}:${PORT}/graphql`) + server.listen(PORT) +}) diff --git a/packages/graphql-mesh/utils/configFromSwaggers.ts b/packages/graphql-mesh/utils/configFromSwaggers.ts index a37110d..7ff1357 100755 --- a/packages/graphql-mesh/utils/configFromSwaggers.ts +++ b/packages/graphql-mesh/utils/configFromSwaggers.ts @@ -4,7 +4,7 @@ import { Catalog, Spec, ConfigExtension } from '../types' import { getConfig, getSourceName, - getSourceOpenapiEnpoint, + getSourceOpenapiEndpoint, getSourceTransforms } from './parseYamlConfig' import { mergeObjects } from './helpers' @@ -224,7 +224,7 @@ export default class ConfigFromSwaggers { handler: { openapi: { source, - endpoint: getSourceOpenapiEnpoint(source, this.config) || '{env.ENDPOINT}', + endpoint: getSourceOpenapiEndpoint(source, this.config) || '{env.ENDPOINT}', ignoreErrorResponses: true, operationHeaders: { Authorization: `{context.headers["authorization"]}`, diff --git a/packages/graphql-mesh/utils/logger.ts b/packages/graphql-mesh/utils/logger.ts index b30dadd..d9c29fb 100644 --- a/packages/graphql-mesh/utils/logger.ts +++ b/packages/graphql-mesh/utils/logger.ts @@ -1,521 +1,641 @@ /** - * Logger : Class static - * use to log event with much useful context information and some useful data + * Logger: Static Class + * Used to log events with contextual information and useful data * - * TODO : split into specifiques loggers and core logger + * TODO: Split into specific loggers and core logger */ -import { v4 as uuidv4 } from 'uuid'; - +import { v4 as uuidv4 } from 'uuid' export class Logger { - - private static level: string = process.env["LogLevel"] || "INFO" - private static format: string = process.env["LogFormat"] || "HUMAN" // set JSON_STRING to have one line json string by log - private static bodyMaxLogSize = process.env["LogBodyMaxSize"] ? parseInt(process.env["LogBodyMaxSize"]) : 200 - private static maxSkackLogSize = process.env["LogStackTraceMaxSize"] ? parseInt(process.env["LogStackTraceMaxSize"]) : 500 - private static envLog: string = process.env["LogEnvFieldToAdd"] // use to add env extra field in json log ex "app=graphql,env.name=production,env.site=Paris" - private static localDateCountry: string = process.env["LogLocalDateCountry"] || "fr-FR" - private static logHeaders = defineLogHeaders(process.env["LogHeaders"] || "headers.host=host,headers.origin,headers.user-agent=user-agent,headers.content-length=content-length,headers.authorization=authorization") - private static logContextHeaders = defineLogHeaders(process.env["LogContextHeaders"] || "ctx.requestId=x-request-id") - private static skipHealthCheck = process.env["skipHealthCheck"] ? process.env["skipHealthCheck"] == "true" : true - private static healthCheckHeaderName = process.env["healthCheckHeazder"] || "x-internal" - private static HealthCheckHeaderValue = process.env["healthCheckHeaderValue"] || "health-check" - private static MaxErrorStackSize = process.env["MaxErrorStackSize"] ? parseInt(process.env["MaxErrorStackSize"]) : 200 - constructor() { - - } - /** - * Core logger with Human format or machine in one line json string format - */ - private static log(level: string, typeEv: string, message: string, headers = null, onlyContextHeader: boolean, info: any = null, err = null) { - - const date = new Date(); - const timestamp = date.getTime(); - - // if machine friendly json string is required - if (Logger.format == 'JSON_STRING') { - const log = { - events: [{ - idEv: uuidv4(), - date: date.toLocaleString(this.localDateCountry), - level: level, - typeEv: typeEv, - timestamp: timestamp, - message: message - }] - } - try { - // add http headers - if (headers) { - this.addHeadersToContextLog(log, headers) - if (!onlyContextHeader) { - this.addHeadersToLog(log.events[0], headers) - } - } - // add extra information to log event - if (info) { - for (const key in info) { - log.events[0][key] = info[key] - } - } - // if exception error add message and an extract of stack trace - if (err) { - log.events[0]['exeception'] = {} - //console.log("Exception:", err) - - log.events[0]['exeception'] = { - message: err.message, - stack: err.stack?.substring(0, this.MaxErrorStackSize) + " ..." - } - - } - // if define add extra env field - if (this.envLog) { - addEnvFieldLog(this.envLog, log) - } - - } catch (e) { - log['error'] = "error whileg getting log event contextual info :" + e.message - } - console.log(JSON.stringify(log)) - } else { - if (info) { - if (err) { - console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, info, err) - } else - console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, info) - - } else { - if (err) { - console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, err) - } else - console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message) - - } - } - } - - public static error(typeEv: string, source: string, message: string, headers = null, info = null, e = null) { - if (Logger.level == 'ERROR' || Logger.level == 'WARN' || Logger.level == 'INFO' || Logger.level == 'DEBUG') { - Logger.log('ERROR', typeEv, source + ":" + message, headers, false, info, e) - } - } - public static warn(typeEv: string, source: string, message: string, headers: any = null, info: any = null, e = null) { - if (Logger.level == 'WARN' || Logger.level == 'INFO' || Logger.level == 'DEBUG') { - Logger.log('INFO', typeEv, source + ":" + message, headers, false, info, e) - } - } - - public static info(typeEv: string, source: string, message: string, headers: any = null, info: any = null, e = null) { - if (Logger.level == 'INFO' || Logger.level == 'DEBUG') { - Logger.log('INFO', typeEv, source + ":" + message, headers, true, info, e) - } - } - public static debug(typeEv: string, source: string, message: string, headers: any = null, info: any = null, e = null) { - if (Logger.level == 'DEBUG') { - Logger.log('DEBUG', typeEv, source + ":" + message, headers, true, info, e) - } - } - - - /** - * log the on parse event - */ - public static onParse(headers: any) { - try { - if (this.isEventToLog(headers)) { - Logger.log('INFO', "ON-PARSE", "Request", headers, true) - } - } catch (e) { - Logger.error('LOGGER_ERROR', 'onParse logger', 'error during log generation', null, e) - - } - } - - /** - * log the on end exec done event - */ - public static endExec(headers: any, result: any, duration: number, resultLogInfo: any) { - try { - if (this.isEventToLog(headers)) { - const info = { - result: InfoResult(result, this.maxSkackLogSize, resultLogInfo), - duration: duration, - } - Logger.log('INFO', "endExecDone", "Request", headers, true, info) - } - } catch (e) { - Logger.error('LOGGER_ERROR', 'endExec logger', 'error during log generation', headers, null, e) - } - } - - /** - * log the on result process event - */ - public static onResultProcess(request: any, result: any, duration: number, resultLogInfo: any) { - const headers = request['headers'] - try { - - if (this.isEventToLog(headers)) { - - const info = { - result: InfoResult(result, this.maxSkackLogSize, resultLogInfo), - duration: duration - } - Logger.log('INFO', "onResultProcess", "Result", headers, true, info) - } - } catch (e) { - Logger.error('LOGGER_ERROR', 'onResponse logger', 'error during log generation', headers, null, e) - } - } - - /** - * log the on request parse done event - */ - public static onRequestParseDone(headers: any, query: any, operation: string, variables: any, duration: number) { - if (this.isEventToLog(headers)) { - - const info = { - operation: operation, - query: query, - variables: variables, - parsingDuration: duration - } - Logger.log('INFO', "requestParseDone", "requestParse", headers, true, info) - } - } - /** - * log the on reponse event http respnse return - */ - public static onResponse(request: any, response: any, logResponseLevel: string) { - const headers = request['headers'] - try { - if (this.isEventToLog(headers)) { - // calculate duration from request timestamp - const requestTimestampString: string = headers.get('requesttimestamp') - let requestTimestamp: number; - if (requestTimestampString) { - requestTimestamp = parseInt(requestTimestampString) - } - const responseTimestamp = new Date().getTime(); - const info = { - request: { - url: request.url, - method: request.method - }, - response: { - status: response.status, - contentLength: response.contentLength, - }, - httpStatus: response.status, - duration: responseTimestamp - requestTimestamp - } - if (logResponseLevel != 'low') { - info.response['bodyExtract'] = extractBody(response.bodyInit, this.bodyMaxLogSize) - } - - Logger.log('INFO', "onResponse", responseSummary(response.bodyInit), headers, true, info) - } - } - catch (e) { - Logger.error('LOGGER_ERROR', 'onResponse logger', 'error during log generation', headers, null, e) - } - } - - public static introspection(headers, query) { - try { - const info = { - query: query - } - Logger.warn('WARN_INTROSPECTION', "introspection", "introspection query", headers, info) - } - catch (e) { - Logger.error('LOGGER_ERROR', 'introspection logger', 'error during log generation', null, null, e) - } - } - - public static denyIntrospection(headers, info, message,) { - try { - Logger.warn('DENY_INTROSPECTION', info, message, headers) - } - catch (e) { - Logger.error('LOGGER_ERROR', 'denyIntrospection logger', 'error during log generation', headers, null, e) - } - } - - public static allowIntrospection(headers, info, message,) { - try { - Logger.warn('ALLOW_INTROSPECTION', info, message, headers) - } - catch (e) { - Logger.error('LOGGER_ERROR', 'denyIntrospection logger', 'error during log generation', headers, null, e) - } - } - public static onRequest(request: any) { - const headersInit = request['headers']['headersInit'] - const headers = new Map - - for (const header in headersInit) { - headers.set(header, headersInit[header]) - } - try { - if (this.isEventToLog(headers)) { - const info = { - url: request.url, - method: request.method, - //body: request.body - } - Logger.log('INFO', "onRequest", "request incomming", headers, false, info) - } - } - catch (e) { - Logger.error('LOGGER_ERROR', 'onRequest logger', 'error during log generation', headers, null, e) - } - } - - public static onFetch(request: any, url: string, httpStatus: string, duration: number, fetchInfo: any) { - try { - const headers = request['headers'] - const info = { - fetch: fetch, - duration: duration, - httpStatus: httpStatus, - url: url - } - Logger.log('INFO', "onFetch", "fetch", headers, true, info) - } catch (e) { - Logger.error('LOGGER_ERROR', 'onFetch logger', 'error during log generation', null, e) - } - } - - public static graphqlQuery(headers: any, params: any) { - try { - if (this.isEventToLog(headers)) { - const regex = / /gi; - const queryTolog = { - query: params['query'].replace(regex, ""), - operationName: params['operationName'], - variables: params['variables'] - } - Logger.log('INFO', "graphqlQuery", "GraphQL Query", headers, true, queryTolog) - } - } catch (e) { - Logger.error('LOGGER_ERROR', 'graphql query logger', 'error during log generation', headers, null, e) - } - } - private static isEventToLog(headers): boolean { - if (this.skipHealthCheck && headers) { - if (headers.get(this.healthCheckHeaderName) == this.HealthCheckHeaderValue) { - return false - } - } - return true - } - private static addHeadersToContextLog(log: any, headers: any) { - - try { - let headerMap = null - if (headers["_map"]) { - headerMap = headers["_map"] - } else { - headerMap = headers - } - - if (headerMap && headerMap.get) { - if (this.logContextHeaders) { - for (const contextKey in this.logContextHeaders) { - const contextHeader = this.logContextHeaders[contextKey] - if (headerMap.get(contextHeader.header)) { - - if (contextHeader.header.toLowerCase() == "authorization") { - addLog(log, contextHeader.name, mask(headerMap.get('authorization'))) - } else { - addLog(log, contextHeader.name, headerMap.get(contextHeader.header)) - } - } - } - } - } - } catch (e) { - Logger.error('LOGGER_ERROR', 'onheadersToLog', 'error during headers log generation', headers, null, e) - } - return log - } - - private static addHeadersToLog(log: any, headers: any) { - let headerMap = null - if (headers["_map"]) { - headerMap = headers["_map"] - } else { - headerMap = headers - } - try { - if (headerMap) { - if (this.logHeaders) { - - for (const logHeaderKey in this.logHeaders) { - const logHeader = this.logHeaders[logHeaderKey] - if (headerMap.get(logHeader.header)) { - if (logHeader.header.toLowerCase() == "authorization") { - addLog(log, logHeader.name, mask(headerMap.get('authorization'))) - } else { - addLog(log, logHeader.name, headerMap.get(logHeader.header)) - } - } - } - } - } - } catch (e) { - Logger.error('LOGGER_ERROR', 'onheadersToLog', 'error during headers log generation', headers, null, e) - } - return log - } + private static level: string = process.env['LogLevel'] || 'INFO' + private static format: string = process.env['LogFormat'] || 'HUMAN' // Set to JSON_STRING for one-line JSON string logs + private static bodyMaxLogSize = process.env['LogBodyMaxSize'] + ? parseInt(process.env['LogBodyMaxSize']) + : 200 + private static maxStackLogSize = process.env['LogStackTraceMaxSize'] + ? parseInt(process.env['LogStackTraceMaxSize']) + : 500 + private static envLog: string = process.env['LogEnvFieldToAdd'] // Add extra env fields in JSON log e.g., "app=graphql,env.name=production,env.site=Paris" + private static localDateCountry: string = process.env['LogLocalDateCountry'] || 'fr-FR' + private static logHeaders = defineLogHeaders( + process.env['LogHeaders'] || + 'headers.host=host,headers.origin,headers.user-agent=user-agent,headers.content-length=content-length,headers.authorization=authorization' + ) + private static logContextHeaders = defineLogHeaders( + process.env['LogContextHeaders'] || 'ctx.requestId=x-request-id' + ) + private static skipHealthCheck = process.env['skipHealthCheck'] + ? process.env['skipHealthCheck'] == 'true' + : true + private static healthCheckHeaderName = process.env['healthCheckHeader'] || 'x-internal' + private static healthCheckHeaderValue = process.env['healthCheckHeaderValue'] || 'health-check' + private static maxErrorStackSize = process.env['MaxErrorStackSize'] + ? parseInt(process.env['MaxErrorStackSize']) + : 200 + + constructor() {} + + /** + * Core logger with human-readable format or machine-friendly one-line JSON string format + */ + private static log( + level: string, + typeEv: string, + message: string, + headers = null, + onlyContextHeader: boolean, + info: any = null, + err = null + ) { + const date = new Date() + const timestamp = date.getTime() + + // If machine-friendly JSON string is required + if (Logger.format == 'JSON_STRING') { + const log = { + events: [ + { + idEv: uuidv4(), + date: date.toLocaleString(this.localDateCountry), + level: level, + typeEv: typeEv, + timestamp: timestamp, + message: message + } + ] + } + try { + // Add HTTP headers + if (headers) { + this.addHeadersToContextLog(log, headers) + if (!onlyContextHeader) { + this.addHeadersToLog(log.events[0], headers) + } + } + // Add extra information to log event + if (info) { + for (const key in info) { + log.events[0][key] = info[key] + } + } + // If exception error, add message and a portion of the stack trace + if (err) { + log.events[0]['exception'] = { + message: err.message, + stack: err.stack?.substring(0, this.maxErrorStackSize) + ' ...' + } + } + // If defined, add extra env fields + if (this.envLog) { + addEnvFieldLog(this.envLog, log) + } + } catch (e) { + log['error'] = 'Error while getting log event contextual info: ' + e.message + } + console.log(JSON.stringify(log)) + } else { + if (info) { + if (err) { + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, info, err) + } else { + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, info) + } + } else { + if (err) { + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message, err) + } else { + console.log(date.toLocaleString(this.localDateCountry), level, typeEv, message) + } + } + } + } + + public static error( + typeEv: string, + source: string, + message: string, + headers = null, + info = null, + e = null + ) { + if ( + Logger.level == 'ERROR' || + Logger.level == 'WARN' || + Logger.level == 'INFO' || + Logger.level == 'DEBUG' + ) { + Logger.log('ERROR', typeEv, source + ': ' + message, headers, false, info, e) + } + } + + public static warn( + typeEv: string, + source: string, + message: string, + headers: any = null, + info: any = null, + e = null + ) { + if (Logger.level == 'WARN' || Logger.level == 'INFO' || Logger.level == 'DEBUG') { + Logger.log('WARN', typeEv, source + ': ' + message, headers, false, info, e) + } + } + + public static info( + typeEv: string, + source: string, + message: string, + headers: any = null, + info: any = null, + e = null + ) { + if (Logger.level == 'INFO' || Logger.level == 'DEBUG') { + Logger.log('INFO', typeEv, source + ': ' + message, headers, true, info, e) + } + } + + public static debug( + typeEv: string, + source: string, + message: string, + headers: any = null, + info: any = null, + e = null + ) { + if (Logger.level == 'DEBUG') { + Logger.log('DEBUG', typeEv, source + ': ' + message, headers, true, info, e) + } + } + + /** + * Log the onParse event + */ + public static onParse(headers: any) { + try { + if (this.isEventToLog(headers)) { + Logger.log('INFO', 'ON-PARSE', 'Request', headers, true) + } + } catch (e) { + Logger.error('LOGGER_ERROR', 'onParse logger', 'Error during log generation', null, e) + } + } + + /** + * Log the on end exec done event + */ + public static endExec(headers: any, result: any, duration: number, resultLogInfo: any) { + try { + if (this.isEventToLog(headers)) { + const info = { + result: InfoResult(result, this.maxStackLogSize, resultLogInfo), + duration: duration + } + Logger.log('INFO', 'endExecDone', 'Request', headers, true, info) + } + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'endExec logger', + 'Error during log generation', + headers, + null, + e + ) + } + } + + /** + * Log the on result process event + */ + public static onResultProcess(request: any, result: any, duration: number, resultLogInfo: any) { + const headers = request['headers'] + try { + if (this.isEventToLog(headers)) { + const info = { + result: InfoResult(result, this.maxStackLogSize, resultLogInfo), + duration: duration + } + Logger.log('INFO', 'onResultProcess', 'Result', headers, true, info) + } + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'onResponse logger', + 'Error during log generation', + headers, + null, + e + ) + } + } + + /** + * Log the onRequestParseDone event + */ + public static onRequestParseDone( + headers: any, + query: any, + operation: string, + variables: any, + duration: number + ) { + if (this.isEventToLog(headers)) { + const info = { + operation: operation, + query: query, + variables: variables, + parsingDuration: duration + } + Logger.log('INFO', 'requestParseDone', 'Request Parse', headers, true, info) + } + } + + /** + * Log the onResponse event when HTTP response returns + */ + public static onResponse(request: any, response: any, logResponseLevel: string) { + const headers = request['headers'] + try { + if (this.isEventToLog(headers)) { + // Calculate duration from request timestamp + const requestTimestampString: string = headers.get('requesttimestamp') + let requestTimestamp: number + if (requestTimestampString) { + requestTimestamp = parseInt(requestTimestampString) + } + const responseTimestamp = new Date().getTime() + const info = { + request: { + url: request.url, + method: request.method + }, + response: { + status: response.status, + contentLength: response.contentLength + }, + httpStatus: response.status, + duration: responseTimestamp - requestTimestamp + } + if (logResponseLevel != 'low') { + info.response['bodyExtract'] = extractBody(response.bodyInit, this.bodyMaxLogSize) + } + + Logger.log('INFO', 'onResponse', responseSummary(response.bodyInit), headers, true, info) + } + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'onResponse logger', + 'Error during log generation', + headers, + null, + e + ) + } + } + + public static introspection(headers, query) { + try { + const info = { + query: query + } + Logger.warn('WARN_INTROSPECTION', 'Introspection', 'Introspection query', headers, info) + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'introspection logger', + 'Error during log generation', + null, + null, + e + ) + } + } + + public static denyIntrospection(headers, info, message) { + try { + Logger.warn('DENY_INTROSPECTION', info, message, headers) + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'denyIntrospection logger', + 'Error during log generation', + headers, + null, + e + ) + } + } + + public static allowIntrospection(headers, info, message) { + try { + Logger.warn('ALLOW_INTROSPECTION', info, message, headers) + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'allowIntrospection logger', + 'Error during log generation', + headers, + null, + e + ) + } + } + + public static onRequest(request: any) { + const headersInit = request['headers']['headersInit'] + const headers = new Map() + + for (const header in headersInit) { + headers.set(header, headersInit[header]) + } + try { + if (this.isEventToLog(headers)) { + const info = { + url: request.url, + method: request.method + // body: request.body + } + Logger.log('INFO', 'onRequest', 'Request incoming', headers, false, info) + } + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'onRequest logger', + 'Error during log generation', + headers, + null, + e + ) + } + } + + public static onFetch( + request: any, + url: string, + httpStatus: string, + duration: number, + fetchInfo: any + ) { + try { + const headers = request['headers'] + const info = { + fetch: fetch, + duration: duration, + httpStatus: httpStatus, + url: url + } + Logger.log('INFO', 'onFetch', 'Fetch', headers, true, info) + } catch (e) { + Logger.error('LOGGER_ERROR', 'onFetch logger', 'Error during log generation', null, e) + } + } + + public static graphqlQuery(headers: any, params: any) { + try { + if (this.isEventToLog(headers)) { + const regex = / /gi + const queryToLog = { + query: params['query'].replace(regex, ''), + operationName: params['operationName'], + variables: params['variables'] + } + Logger.log('INFO', 'graphqlQuery', 'GraphQL Query', headers, true, queryToLog) + } + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'graphqlQuery logger', + 'Error during log generation', + headers, + null, + e + ) + } + } + + private static isEventToLog(headers): boolean { + if (this.skipHealthCheck && headers) { + if (headers.get(this.healthCheckHeaderName) == this.healthCheckHeaderValue) { + return false + } + } + return true + } + + private static addHeadersToContextLog(log: any, headers: any) { + try { + let headerMap = null + if (headers['_map']) { + headerMap = headers['_map'] + } else { + headerMap = headers + } + + if (headerMap && headerMap.get) { + if (this.logContextHeaders) { + for (const contextKey in this.logContextHeaders) { + const contextHeader = this.logContextHeaders[contextKey] + if (headerMap.get(contextHeader.header)) { + if (contextHeader.header.toLowerCase() == 'authorization') { + addLog(log, contextHeader.name, mask(headerMap.get('authorization'))) + } else { + addLog(log, contextHeader.name, headerMap.get(contextHeader.header)) + } + } + } + } + } + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'onHeadersToLog', + 'Error during headers log generation', + headers, + null, + e + ) + } + return log + } + + private static addHeadersToLog(log: any, headers: any) { + let headerMap = null + if (headers['_map']) { + headerMap = headers['_map'] + } else { + headerMap = headers + } + try { + if (headerMap) { + if (this.logHeaders) { + for (const logHeaderKey in this.logHeaders) { + const logHeader = this.logHeaders[logHeaderKey] + if (headerMap.get(logHeader.header)) { + if (logHeader.header.toLowerCase() == 'authorization') { + addLog(log, logHeader.name, mask(headerMap.get('authorization'))) + } else { + addLog(log, logHeader.name, headerMap.get(logHeader.header)) + } + } + } + } + } + } catch (e) { + Logger.error( + 'LOGGER_ERROR', + 'onHeadersToLog', + 'Error during headers log generation', + headers, + null, + e + ) + } + return log + } } function mask(stringToMask: string) { - if (stringToMask) { - return "*******************" + stringToMask.substring(22) - } - return null; + if (stringToMask) { + return '*******************' + stringToMask.substring(22) + } + return null } function InfoResult(result: any, maxStackLogSize: number, resultLogInfoLevel: string) { - let resultInfo = {} - let nbKeys = 0 - const maxKeys = 1 - const nbErrorsMaxToLog = 3 - if (result['errors']) { - resultInfo['nbErrors'] = result['errors'].length - if (resultLogInfoLevel != 'low') { - let nbErrors = 0 - resultInfo['errors'] = [] - for (const errorKey in result['errors']) { - const error = result['errors'][errorKey] - let logError = {} - if (error['message']) { - if (nbErrorsMaxToLog > nbErrors) { - logError['message'] = error['message'] - } - } - if (error['path']) { - logError['path'] = error['path'] - } - if (resultLogInfoLevel == 'high') { - // no stack trace and extension in low trace level - if (error['stack']) { - logError['stack'] = error['stack'].substring(0, maxStackLogSize) - } - if (error['extensions']) { - logError['extensions'] = error['extensions'] - } - } - resultInfo['errors'].push(logError) - } - } - } else { - resultInfo['nbErrors'] = 0 - } - if (result['data']) { - let keys = null - for (const key in result['data']) { - if (result['data'].hasOwnProperty(key)) { - if (keys == null) { - keys = key - } else { - if (nbKeys < maxKeys) { - keys = keys + ',' + key - } - } - nbKeys = nbKeys + 1 - } - } - resultInfo['dataFields'] = keys - } - return resultInfo + let resultInfo = {} + let nbKeys = 0 + const maxKeys = 1 + const nbErrorsMaxToLog = 3 + if (result['errors']) { + resultInfo['nbErrors'] = result['errors'].length + if (resultLogInfoLevel != 'low') { + let nbErrors = 0 + resultInfo['errors'] = [] + for (const errorKey in result['errors']) { + const error = result['errors'][errorKey] + let logError = {} + if (error['message']) { + if (nbErrorsMaxToLog > nbErrors) { + logError['message'] = error['message'] + } + } + if (error['path']) { + logError['path'] = error['path'] + } + if (resultLogInfoLevel == 'high') { + // No stack trace and extension in low trace level + if (error['stack']) { + logError['stack'] = error['stack'].substring(0, maxStackLogSize) + } + if (error['extensions']) { + logError['extensions'] = error['extensions'] + } + } + resultInfo['errors'].push(logError) + } + } + } else { + resultInfo['nbErrors'] = 0 + } + if (result['data']) { + let keys = null + for (const key in result['data']) { + if (result['data'].hasOwnProperty(key)) { + if (keys == null) { + keys = key + } else { + if (nbKeys < maxKeys) { + keys = keys + ',' + key + } + } + nbKeys = nbKeys + 1 + } + } + resultInfo['dataFields'] = keys + } + return resultInfo } + function extractBody(body: String, bodyMaxLogSize: number) { - if (body != null) { - return body.substring(0, bodyMaxLogSize) + " ... " - } else { - return "" - } + if (body != null) { + return body.substring(0, bodyMaxLogSize) + ' ... ' + } else { + return '' + } } + function responseSummary(body: String) { - if (body != null) { - return "response :" + body.substring(0, 100) + " ... " - } else { - return "response : empty" - } + if (body != null) { + return 'response: ' + body.substring(0, 100) + ' ... ' + } else { + return 'response: empty' + } } - /** - * Use to add some environment fields to log, like env.name, app.name ... - * ex : logEnvInfoField : string = "app=graphql,env.name=production,env.site=Paris" + * Used to add some environment fields to the log, like env.name, app.name, etc. + * Example: logEnvInfoField: string = "app=graphql,env.name=production,env.site=Paris" * -> log={ * app: "graphql", - * env: + * env: { * name: "production", - * site: "Paris", - * ... // logs field + * site: "Paris" + * }, + * ... // logs field */ function addEnvFieldLog(logEnvInfoField: string, log: any) { - - // split fields list witth dot serator - const fields: string[] = logEnvInfoField.split(",") - for (let idxField = 0; idxField < fields.length; idxField++) { - // split key, value - const kvField: string[] = fields[idxField].trim().split("=") - // split key in objects tree hierarchy - const levelField = kvField[0].trim().split(".") - let current = log - // create new object if does not exist for each level and set value for leaf level - for (let idxLevel = 0; idxLevel < levelField.length; idxLevel++) { - if (current[levelField[idxLevel]]) { - current = current[levelField[idxLevel]] - } else { - if (idxLevel < levelField.length - 1) { - current = current[levelField[idxLevel]] = {} - } else { - current[levelField[idxLevel]] = kvField[1] - } - } - } - } + // Split fields list with dot separator + const fields: string[] = logEnvInfoField.split(',') + for (let idxField = 0; idxField < fields.length; idxField++) { + // Split key, value + const kvField: string[] = fields[idxField].trim().split('=') + // Split key in objects tree hierarchy + const levelField = kvField[0].trim().split('.') + let current = log + // Create new object if it does not exist for each level and set value for leaf level + for (let idxLevel = 0; idxLevel < levelField.length; idxLevel++) { + if (current[levelField[idxLevel]]) { + current = current[levelField[idxLevel]] + } else { + if (idxLevel < levelField.length - 1) { + current = current[levelField[idxLevel]] = {} + } else { + current[levelField[idxLevel]] = kvField[1] + } + } + } + } } function addLog(log: any, key: string, value: any) { - - const levelField = key.split(".") - let current = log - // create new object if does not exist for each level and set value for leaf level - for (let idxLevel = 0; idxLevel < levelField.length; idxLevel++) { - if (current[levelField[idxLevel]]) { - current = current[levelField[idxLevel]] - } else { - if (idxLevel < levelField.length - 1) { - current = current[levelField[idxLevel]] = {} - } else { - current[levelField[idxLevel]] = value - } - } - } + const levelField = key.split('.') + let current = log + // Create new object if it does not exist for each level and set value for leaf level + for (let idxLevel = 0; idxLevel < levelField.length; idxLevel++) { + if (current[levelField[idxLevel]]) { + current = current[levelField[idxLevel]] + } else { + if (idxLevel < levelField.length - 1) { + current = current[levelField[idxLevel]] = {} + } else { + current[levelField[idxLevel]] = value + } + } + } } function defineLogHeaders(logHeaders: string) { - const headersList = [] - if (logHeaders) { - const headers = logHeaders.split(',') - - for (const headerKey in headers) { - const header = headers[headerKey] - const headerInfo = header.split('=') - headersList.push({ name: headerInfo[0], header: headerInfo[1] ? headerInfo[1] : headerInfo[0] }) - } - } - return headersList + const headersList = [] + if (logHeaders) { + const headers = logHeaders.split(',') + + for (const headerKey in headers) { + const header = headers[headerKey] + const headerInfo = header.split('=') + headersList.push({ + name: headerInfo[0], + header: headerInfo[1] ? headerInfo[1] : headerInfo[0] + }) + } + } + return headersList } - - - - - - diff --git a/packages/graphql-mesh/utils/parseYamlConfig.ts b/packages/graphql-mesh/utils/parseYamlConfig.ts index f4e606f..c0ac989 100644 --- a/packages/graphql-mesh/utils/parseYamlConfig.ts +++ b/packages/graphql-mesh/utils/parseYamlConfig.ts @@ -1,81 +1,86 @@ import { YamlConfig } from '@graphql-mesh/types' -//import { DefaultLogger } from '@graphql-mesh/utils' import { load } from 'js-yaml' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import { Logger } from '../utils/logger' - -//const logger = new DefaultLogger() - // Load the config.yaml file export const getConfig = (): YamlConfig.Config => { - const configFile = process.env['ConfigFile'] || 'config.yaml' - Logger.info('CONFIG', 'getConfig', 'Loading config file ' + configFile) - let config: YamlConfig.Config - - try { - const configPath = resolve(configFile) - config = load(readFileSync(configPath, { encoding: 'utf-8' })) - } catch (e) { - Logger.error('CONFIG', 'getConfig', "Failed loading config " + configFile, e) - throw new Error('FAiles laoding config file ' + configFile, e) - } + const configFile = process.env['ConfigFile'] || 'config.yaml' + Logger.info('CONFIG', 'getConfig', 'Loading config file ' + configFile) + let config: YamlConfig.Config - if (!config) { - Logger.error('CONFIG', 'getConfig', "No config loaded from " + configFile) - throw new Error('No configuration loaded from ' + configFile) - } + try { + const configPath = resolve(configFile) + config = load(readFileSync(configPath, { encoding: 'utf-8' })) + } catch (e) { + Logger.error('CONFIG', 'getConfig', 'Failed loading config ' + configFile, e) + throw new Error('Failed loading config file ' + configFile, e) + } - // if sources config is on a separate file define by SourcesConfigFile env var - const sourcesConfigFile = process.env['SourcesConfigFile'] - if (sourcesConfigFile) { - let sourcesConfig: YamlConfig.Config - Logger.info('CONFIG', 'getConfig', 'Loading sources config file ' + sourcesConfigFile) + if (!config) { + Logger.error('CONFIG', 'getConfig', 'No config loaded from ' + configFile) + throw new Error('No configuration loaded from ' + configFile) + } - try { - const sourcesConfigPath = resolve(sourcesConfigFile) - let file = readFileSync(sourcesConfigPath, { encoding: 'utf-8' }) - sourcesConfig = load(readFileSync(sourcesConfigPath, { encoding: 'utf-8' })) - Logger.debug('CONFIG', 'getConfig', 'sources loading from ' + sourcesConfigPath , null, file) - } catch (e) { - Logger.error('CONFIG', 'getConfig', "Failed loading sources Config " + sourcesConfigFile, null, null, e) - throw new Error('FAiles laoding sources config file ' + sourcesConfigFile, e) - } - if (sourcesConfig.sources) { - config['sources'] = sourcesConfig.sources - Logger.info('CONFIG', 'getConfig', 'sources Config file loaded successfully with ' + sourcesConfig.sources.length + " sources") - } else { - Logger.error('CONFIG', 'getConfig', 'sources Config file loaded successfully but without sources') - } - + // If sources config is in a separate file defined by SourcesConfigFile env var + const sourcesConfigFile = process.env['SourcesConfigFile'] + if (sourcesConfigFile) { + let sourcesConfig: YamlConfig.Config + Logger.info('CONFIG', 'getConfig', 'Loading sources config file ' + sourcesConfigFile) - } else { - if (!config.sources) { - Logger.error('CONFIG', 'getConfig', "No source defined in configuration file " + configFile) - throw new Error('No source defioned in configuration file ' + configFile) - } - } - return config + try { + const sourcesConfigPath = resolve(sourcesConfigFile) + let file = readFileSync(sourcesConfigPath, { encoding: 'utf-8' }) + sourcesConfig = load(file) + Logger.debug('CONFIG', 'getConfig', 'Sources loading from ' + sourcesConfigPath, null, file) + } catch (e) { + Logger.error( + 'CONFIG', + 'getConfig', + 'Failed loading sources config ' + sourcesConfigFile, + null, + null, + e + ) + throw new Error('Failed loading sources config file ' + sourcesConfigFile, e) + } + if (sourcesConfig.sources) { + config['sources'] = sourcesConfig.sources + Logger.info( + 'CONFIG', + 'getConfig', + 'Sources config file loaded successfully with ' + sourcesConfig.sources.length + ' sources' + ) + } else { + Logger.error('CONFIG', 'getConfig', 'Sources config file loaded successfully but without sources') + } + } else { + if (!config.sources) { + Logger.error('CONFIG', 'getConfig', 'No source defined in configuration file ' + configFile) + throw new Error('No source defined in configuration file ' + configFile) + } + } + return config } -// Get the endpoint of a specific openapi source -export const getSourceOpenapiEnpoint = ( - source: string, - config: YamlConfig.Config +// Get the endpoint of a specific OpenAPI source +export const getSourceOpenapiEndpoint = ( + source: string, + config: YamlConfig.Config ): string | undefined => { - const data = config.sources?.find((item) => source.includes(item.name)) - return data?.handler.openapi?.endpoint + const data = config.sources?.find((item) => source.includes(item.name)) + return data?.handler?.openapi?.endpoint } // Get the name of a specific source export const getSourceName = (source: string, config: YamlConfig.Config): string => { - const data = config.sources?.find((item) => source.includes(item.name)) - return data?.name + const data = config.sources?.find((item) => source.includes(item.name)) + return data?.name } // Get the list of transforms of a specific source export const getSourceTransforms = (source: string, config: YamlConfig.Config) => { - const data = config.sources?.find((item) => source.includes(item.name)) - return data?.transforms + const data = config.sources?.find((item) => source.includes(item.name)) + return data?.transforms }