From 8d052add2d815acc5e3c237b9dd71624b56374fa Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Thu, 6 Jun 2024 16:54:49 +0530 Subject: [PATCH] Log GQL requests in watcher (#517) * Update config for server GQL * Add winston logger for GQL requests * Fix codegen templates for resolver and package * Update package versions --------- Co-authored-by: Prathamesh Musale --- lerna.json | 2 +- packages/cache/package.json | 2 +- packages/cli/package.json | 12 +- packages/cli/src/server.ts | 13 +- packages/codegen/package.json | 4 +- packages/codegen/src/assets/.gitignore | 2 + .../src/templates/config-template.handlebars | 36 +++-- .../src/templates/indexer-template.handlebars | 2 +- .../src/templates/package-template.handlebars | 13 +- .../src/templates/readme-template.handlebars | 2 +- .../templates/resolvers-template.handlebars | 114 ++++++++++++-- packages/graph-node/package.json | 10 +- packages/ipld-eth-client/package.json | 6 +- packages/peer/package.json | 2 +- packages/rpc-eth-client/package.json | 8 +- packages/solidity-mapper/package.json | 2 +- packages/test/package.json | 2 +- packages/tracing-client/package.json | 2 +- packages/util/package.json | 9 +- packages/util/src/config.ts | 36 +++-- packages/util/src/graph/database.ts | 2 +- packages/util/src/index.ts | 1 + packages/util/src/logger.ts | 18 +++ packages/util/src/metrics.ts | 6 +- packages/util/src/server.ts | 15 +- yarn.lock | 149 +++++++++++++++++- 26 files changed, 374 insertions(+), 96 deletions(-) create mode 100644 packages/util/src/logger.ts diff --git a/lerna.json b/lerna.json index 446af5a94..8b95dab64 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "0.2.93", + "version": "0.2.94", "npmClient": "yarn", "useWorkspaces": true, "command": { diff --git a/packages/cache/package.json b/packages/cache/package.json index 1fe60e8fb..d8c1303d8 100644 --- a/packages/cache/package.json +++ b/packages/cache/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/cache", - "version": "0.2.93", + "version": "0.2.94", "description": "Generic object cache", "main": "dist/index.js", "scripts": { diff --git a/packages/cli/package.json b/packages/cli/package.json index c65d2b6e5..333e071a2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/cli", - "version": "0.2.93", + "version": "0.2.94", "main": "dist/index.js", "license": "AGPL-3.0", "scripts": { @@ -15,13 +15,13 @@ }, "dependencies": { "@apollo/client": "^3.7.1", - "@cerc-io/cache": "^0.2.93", - "@cerc-io/ipld-eth-client": "^0.2.93", + "@cerc-io/cache": "^0.2.94", + "@cerc-io/ipld-eth-client": "^0.2.94", "@cerc-io/libp2p": "^0.42.2-laconic-0.1.4", "@cerc-io/nitro-node": "^0.1.15", - "@cerc-io/peer": "^0.2.93", - "@cerc-io/rpc-eth-client": "^0.2.93", - "@cerc-io/util": "^0.2.93", + "@cerc-io/peer": "^0.2.94", + "@cerc-io/rpc-eth-client": "^0.2.94", + "@cerc-io/util": "^0.2.94", "@ethersproject/providers": "^5.4.4", "@graphql-tools/utils": "^9.1.1", "@ipld/dag-cbor": "^8.0.0", diff --git a/packages/cli/src/server.ts b/packages/cli/src/server.ts index 97eec8cab..68e7b169e 100644 --- a/packages/cli/src/server.ts +++ b/packages/cli/src/server.ts @@ -11,6 +11,7 @@ import assert from 'assert'; import { ConnectionOptions } from 'typeorm'; import express, { Application } from 'express'; import { ApolloServer } from 'apollo-server-express'; +import winston from 'winston'; import { JsonRpcProvider } from '@ethersproject/providers'; import { @@ -30,7 +31,8 @@ import { Consensus, readParty, UpstreamConfig, - fillBlocks + fillBlocks, + createGQLLogger } from '@cerc-io/util'; import { TypeSource } from '@graphql-tools/utils'; import type { @@ -268,7 +270,11 @@ export class ServerCmd { } async exec ( - createResolvers: (indexer: IndexerInterface, eventWatcher: EventWatcher) => Promise, + createResolvers: ( + indexer: IndexerInterface, + eventWatcher: EventWatcher, + gqlLogger: winston.Logger + ) => Promise, typeDefs: TypeSource, paymentsManager?: PaymentsManager ): Promise<{ @@ -308,7 +314,8 @@ export class ServerCmd { await eventWatcher.start(); } - const resolvers = await createResolvers(indexer, eventWatcher); + const gqlLogger = createGQLLogger(config.server.gql.logDir); + const resolvers = await createResolvers(indexer, eventWatcher, gqlLogger); // Create an Express app const app: Application = express(); diff --git a/packages/codegen/package.json b/packages/codegen/package.json index 516dc395a..72e241bac 100644 --- a/packages/codegen/package.json +++ b/packages/codegen/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/codegen", - "version": "0.2.93", + "version": "0.2.94", "description": "Code generator", "private": true, "main": "index.js", @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { - "@cerc-io/util": "^0.2.93", + "@cerc-io/util": "^0.2.94", "@graphql-tools/load-files": "^6.5.2", "@npmcli/package-json": "^5.0.0", "@poanet/solidity-flattener": "https://github.com/vulcanize/solidity-flattener.git", diff --git a/packages/codegen/src/assets/.gitignore b/packages/codegen/src/assets/.gitignore index 0b4cc3f66..549d70b48 100644 --- a/packages/codegen/src/assets/.gitignore +++ b/packages/codegen/src/assets/.gitignore @@ -4,3 +4,5 @@ out/ .vscode .idea + +gql-logs/ diff --git a/packages/codegen/src/templates/config-template.handlebars b/packages/codegen/src/templates/config-template.handlebars index 96ab41f76..02e719643 100644 --- a/packages/codegen/src/templates/config-template.handlebars +++ b/packages/codegen/src/templates/config-template.handlebars @@ -2,7 +2,6 @@ host = "127.0.0.1" port = {{port}} kind = "{{watcherKind}}" - gqlPath = "/graphql" # Checkpointing state. checkpointing = true @@ -24,25 +23,32 @@ clearEntitiesCacheInterval = 1000 {{/if}} - # Max block range for which to return events in eventsInRange GQL query. - # Use -1 for skipping check on block range. - maxEventsBlockRange = 1000 - # Flag to specify whether RPC endpoint supports block hash as block tag parameter rpcSupportsBlockHashParam = true - # GQL cache settings - [server.gqlCache] - enabled = true + # Server GQL config + [server.gql] + path = "/graphql" + + # Max block range for which to return events in eventsInRange GQL query. + # Use -1 for skipping check on block range. + maxEventsBlockRange = 1000 + + # Log directory for GQL requests + logDir = "./gql-logs" + + # GQL cache settings + [server.gql.cache] + enabled = true - # Max in-memory cache size (in bytes) (default 8 MB) - # maxCacheSize + # Max in-memory cache size (in bytes) (default 8 MB) + # maxCacheSize - # GQL cache-control max-age settings (in seconds) - maxAge = 15 - {{#if (subgraphPath)}} - timeTravelMaxAge = 86400 # 1 day - {{/if}} + # GQL cache-control max-age settings (in seconds) + maxAge = 15 + {{#if (subgraphPath)}} + timeTravelMaxAge = 86400 # 1 day + {{/if}} [metrics] host = "127.0.0.1" diff --git a/packages/codegen/src/templates/indexer-template.handlebars b/packages/codegen/src/templates/indexer-template.handlebars index a606a69b8..b8d37d59c 100644 --- a/packages/codegen/src/templates/indexer-template.handlebars +++ b/packages/codegen/src/templates/indexer-template.handlebars @@ -660,7 +660,7 @@ export class Indexer implements IndexerInterface { } async getEventsInRange (fromBlockNumber: number, toBlockNumber: number): Promise> { - return this._baseIndexer.getEventsInRange(fromBlockNumber, toBlockNumber, this._serverConfig.maxEventsBlockRange); + return this._baseIndexer.getEventsInRange(fromBlockNumber, toBlockNumber, this._serverConfig.gql.maxEventsBlockRange); } async getSyncStatus (): Promise { diff --git a/packages/codegen/src/templates/package-template.handlebars b/packages/codegen/src/templates/package-template.handlebars index 961879a3c..f8800dbfa 100644 --- a/packages/codegen/src/templates/package-template.handlebars +++ b/packages/codegen/src/templates/package-template.handlebars @@ -41,12 +41,12 @@ "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { "@apollo/client": "^3.3.19", - "@cerc-io/cli": "^0.2.93", - "@cerc-io/ipld-eth-client": "^0.2.93", - "@cerc-io/solidity-mapper": "^0.2.93", - "@cerc-io/util": "^0.2.93", + "@cerc-io/cli": "^0.2.94", + "@cerc-io/ipld-eth-client": "^0.2.94", + "@cerc-io/solidity-mapper": "^0.2.94", + "@cerc-io/util": "^0.2.94", {{#if (subgraphPath)}} - "@cerc-io/graph-node": "^0.2.93", + "@cerc-io/graph-node": "^0.2.94", {{/if}} "@ethersproject/providers": "^5.4.4", "debug": "^4.3.1", @@ -75,6 +75,7 @@ "eslint-plugin-standard": "^5.0.0", "husky": "^7.0.2", "ts-node": "^10.2.1", - "typescript": "^5.0.2" + "typescript": "^5.0.2", + "winston": "^3.13.0" } } diff --git a/packages/codegen/src/templates/readme-template.handlebars b/packages/codegen/src/templates/readme-template.handlebars index 70ee21cc1..db1350577 100644 --- a/packages/codegen/src/templates/readme-template.handlebars +++ b/packages/codegen/src/templates/readme-template.handlebars @@ -63,7 +63,7 @@ To enable GQL requests caching: -* Update the `server.gqlCache` config with required settings. +* Update the `server.gql.cache` config with required settings. * In the GQL [schema file](./src/schema.gql), use the `cacheControl` directive to apply cache hints at schema level. diff --git a/packages/codegen/src/templates/resolvers-template.handlebars b/packages/codegen/src/templates/resolvers-template.handlebars index ea3c7d00d..927606af0 100644 --- a/packages/codegen/src/templates/resolvers-template.handlebars +++ b/packages/codegen/src/templates/resolvers-template.handlebars @@ -5,6 +5,8 @@ import assert from 'assert'; import debug from 'debug'; import { GraphQLResolveInfo } from 'graphql'; +import { ExpressContext } from 'apollo-server-express'; +import winston from 'winston'; import { {{#if queries}} @@ -37,25 +39,57 @@ import { {{query.entityName}} } from './entity/{{query.entityName}}'; const log = debug('vulcanize:resolver'); -const executeAndRecordMetrics = async (gqlLabel: string, operation: () => Promise) => { +const executeAndRecordMetrics = async ( + indexer: Indexer, + gqlLogger: winston.Logger, + opName: string, + expressContext: ExpressContext, + operation: () => Promise +) => { gqlTotalQueryCount.inc(1); - gqlQueryCount.labels(gqlLabel).inc(1); - const endTimer = gqlQueryDuration.labels(gqlLabel).startTimer(); + gqlQueryCount.labels(opName).inc(1); + const endTimer = gqlQueryDuration.labels(opName).startTimer(); try { - const result = await operation(); - + const [result, syncStatus] = await Promise.all([ + operation(), + indexer.getSyncStatus() + ]); + + gqlLogger.info({ + opName, + query: expressContext.req.body.query, + variables: expressContext.req.body.variables, + latestIndexedBlockNumber: syncStatus?.latestIndexedBlockNumber, + urlPath: expressContext.req.path, + apiKey: expressContext.req.header('x-api-key'), + origin: expressContext.req.headers.origin + }); return result; + } catch (error) { + gqlLogger.error({ + opName, + error, + query: expressContext.req.body.query, + variables: expressContext.req.body.variables, + urlPath: expressContext.req.path, + apiKey: expressContext.req.header('x-api-key'), + origin: expressContext.req.headers.origin + }); } finally { endTimer(); } }; -export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher: EventWatcher): Promise => { +export const createResolvers = async ( + indexerArg: IndexerInterface, + eventWatcher: EventWatcher, + gqlLogger: winston.Logger +): Promise => { const indexer = indexerArg as Indexer; // eslint-disable-next-line @typescript-eslint/no-unused-vars - const gqlCacheConfig = indexer.serverConfig.gqlCache; + const gqlCacheConfig = indexer.serverConfig.gql.cache; return { BigInt: GraphQLBigInt, @@ -93,7 +127,7 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher {{~#each this.params}}, {{this.name~}} {{/each}} }: { blockHash: string, contractAddress: string {{~#each this.params}}, {{this.name}}: {{this.type~}} {{/each}} }, // eslint-disable-next-line @typescript-eslint/no-unused-vars - __: any, + expressContext: ExpressContext, // eslint-disable-next-line @typescript-eslint/no-unused-vars info: GraphQLResolveInfo ): Promise => { @@ -104,7 +138,10 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher // setGQLCacheHints(info, {}, gqlCacheConfig); return executeAndRecordMetrics( + indexer, + gqlLogger, '{{this.name}}', + expressContext, async () => indexer.{{this.name}}(blockHash, contractAddress {{~#each this.params}}, {{this.name~}} {{/each}}) ); @@ -116,7 +153,7 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher {{this.queryName}}: async ( _: any, { id, block = {} }: { id: string, block: BlockHeight }, - __: any, + expressContext: ExpressContext, info: GraphQLResolveInfo ) => { log('{{this.queryName}}', id, JSON.stringify(block, jsonBigIntStringReplacer)); @@ -125,7 +162,10 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher // setGQLCacheHints(info, block, gqlCacheConfig); return executeAndRecordMetrics( + indexer, + gqlLogger, '{{this.queryName}}', + expressContext, async () => indexer.getSubgraphEntity({{this.entityName}}, id, block, info) ); }, @@ -133,7 +173,7 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher {{this.pluralQueryName}}: async ( _: any, { block = {}, where, first, skip, orderBy, orderDirection }: { block: BlockHeight, where: { [key: string]: any }, first: number, skip: number, orderBy: string, orderDirection: OrderDirection }, - __: any, + expressContext: ExpressContext, info: GraphQLResolveInfo ) => { log('{{this.pluralQueryName}}', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection); @@ -142,7 +182,10 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher // setGQLCacheHints(info, block, gqlCacheConfig); return executeAndRecordMetrics( + indexer, + gqlLogger, '{{this.pluralQueryName}}', + expressContext, async () => indexer.getSubgraphEntities( {{this.entityName}}, block, @@ -154,11 +197,18 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher }, {{/each}} - events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => { + events: async ( + _: any, + { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }, + expressContext: ExpressContext + ) => { log('events', blockHash, contractAddress, name); return executeAndRecordMetrics( + indexer, + gqlLogger, 'events', + expressContext, async () => { const block = await indexer.getBlockProgress(blockHash); if (!block || !block.isComplete) { @@ -171,11 +221,18 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher ); }, - eventsInRange: async (_: any, { fromBlockNumber, toBlockNumber }: { fromBlockNumber: number, toBlockNumber: number }) => { + eventsInRange: async ( + _: any, + { fromBlockNumber, toBlockNumber }: { fromBlockNumber: number, toBlockNumber: number }, + expressContext: ExpressContext + ) => { log('eventsInRange', fromBlockNumber, toBlockNumber); return executeAndRecordMetrics( + indexer, + gqlLogger, 'eventsInRange', + expressContext, async () => { const syncStatus = await indexer.getSyncStatus(); @@ -193,11 +250,18 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher ); }, - getStateByCID: async (_: any, { cid }: { cid: string }) => { + getStateByCID: async ( + _: any, + { cid }: { cid: string }, + expressContext: ExpressContext + ) => { log('getStateByCID', cid); return executeAndRecordMetrics( + indexer, + gqlLogger, 'getStateByCID', + expressContext, async () => { const state = await indexer.getStateByCID(cid); @@ -206,11 +270,18 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher ); }, - getState: async (_: any, { blockHash, contractAddress, kind }: { blockHash: string, contractAddress: string, kind: string }) => { + getState: async ( + _: any, + { blockHash, contractAddress, kind }: { blockHash: string, contractAddress: string, kind: string }, + expressContext: ExpressContext + ) => { log('getState', blockHash, contractAddress, kind); return executeAndRecordMetrics( + indexer, + gqlLogger, 'getState', + expressContext, async () => { const state = await indexer.getPrevState(blockHash, contractAddress, kind); @@ -222,22 +293,33 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher _meta: async ( _: any, - { block = {} }: { block: BlockHeight } + { block = {} }: { block: BlockHeight }, + expressContext: ExpressContext ) => { log('_meta'); return executeAndRecordMetrics( + indexer, + gqlLogger, '_meta', + expressContext, async () => indexer.getMetaData(block) ); }, {{/if}} - getSyncStatus: async () => { + getSyncStatus: async ( + _: any, + __: Record, + expressContext: ExpressContext + ) => { log('getSyncStatus'); return executeAndRecordMetrics( + indexer, + gqlLogger, 'getSyncStatus', + expressContext, async () => indexer.getSyncStatus() ); } diff --git a/packages/graph-node/package.json b/packages/graph-node/package.json index 13d7f3351..4c157570a 100644 --- a/packages/graph-node/package.json +++ b/packages/graph-node/package.json @@ -1,10 +1,10 @@ { "name": "@cerc-io/graph-node", - "version": "0.2.93", + "version": "0.2.94", "main": "dist/index.js", "license": "AGPL-3.0", "devDependencies": { - "@cerc-io/solidity-mapper": "^0.2.93", + "@cerc-io/solidity-mapper": "^0.2.94", "@ethersproject/providers": "^5.4.4", "@graphprotocol/graph-ts": "^0.22.0", "@nomiclabs/hardhat-ethers": "^2.0.2", @@ -51,9 +51,9 @@ "dependencies": { "@apollo/client": "^3.3.19", "@cerc-io/assemblyscript": "0.19.10-watcher-ts-0.1.2", - "@cerc-io/cache": "^0.2.93", - "@cerc-io/ipld-eth-client": "^0.2.93", - "@cerc-io/util": "^0.2.93", + "@cerc-io/cache": "^0.2.94", + "@cerc-io/ipld-eth-client": "^0.2.94", + "@cerc-io/util": "^0.2.94", "@types/json-diff": "^0.5.2", "@types/yargs": "^17.0.0", "bn.js": "^4.11.9", diff --git a/packages/ipld-eth-client/package.json b/packages/ipld-eth-client/package.json index 736706056..1923aad54 100644 --- a/packages/ipld-eth-client/package.json +++ b/packages/ipld-eth-client/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/ipld-eth-client", - "version": "0.2.93", + "version": "0.2.94", "description": "IPLD ETH Client", "main": "dist/index.js", "scripts": { @@ -20,8 +20,8 @@ "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { "@apollo/client": "^3.7.1", - "@cerc-io/cache": "^0.2.93", - "@cerc-io/util": "^0.2.93", + "@cerc-io/cache": "^0.2.94", + "@cerc-io/util": "^0.2.94", "cross-fetch": "^3.1.4", "debug": "^4.3.1", "ethers": "^5.4.4", diff --git a/packages/peer/package.json b/packages/peer/package.json index e1e8fc865..6575abd89 100644 --- a/packages/peer/package.json +++ b/packages/peer/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/peer", - "version": "0.2.93", + "version": "0.2.94", "description": "libp2p module", "main": "dist/index.js", "exports": "./dist/index.js", diff --git a/packages/rpc-eth-client/package.json b/packages/rpc-eth-client/package.json index a7250d6f1..06fd0b663 100644 --- a/packages/rpc-eth-client/package.json +++ b/packages/rpc-eth-client/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/rpc-eth-client", - "version": "0.2.93", + "version": "0.2.94", "description": "RPC ETH Client", "main": "dist/index.js", "scripts": { @@ -19,9 +19,9 @@ }, "homepage": "https://github.com/cerc-io/watcher-ts#readme", "dependencies": { - "@cerc-io/cache": "^0.2.93", - "@cerc-io/ipld-eth-client": "^0.2.93", - "@cerc-io/util": "^0.2.93", + "@cerc-io/cache": "^0.2.94", + "@cerc-io/ipld-eth-client": "^0.2.94", + "@cerc-io/util": "^0.2.94", "chai": "^4.3.4", "ethers": "^5.4.4", "left-pad": "^1.3.0", diff --git a/packages/solidity-mapper/package.json b/packages/solidity-mapper/package.json index be862d9ce..af7d331f8 100644 --- a/packages/solidity-mapper/package.json +++ b/packages/solidity-mapper/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/solidity-mapper", - "version": "0.2.93", + "version": "0.2.94", "main": "dist/index.js", "license": "AGPL-3.0", "devDependencies": { diff --git a/packages/test/package.json b/packages/test/package.json index 06f1f9c61..28dabb10a 100644 --- a/packages/test/package.json +++ b/packages/test/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/test", - "version": "0.2.93", + "version": "0.2.94", "main": "dist/index.js", "license": "AGPL-3.0", "private": true, diff --git a/packages/tracing-client/package.json b/packages/tracing-client/package.json index 05639c1a1..fd5273bef 100644 --- a/packages/tracing-client/package.json +++ b/packages/tracing-client/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/tracing-client", - "version": "0.2.93", + "version": "0.2.94", "description": "ETH VM tracing client", "main": "dist/index.js", "scripts": { diff --git a/packages/util/package.json b/packages/util/package.json index 8f65578cc..e59864880 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,13 +1,13 @@ { "name": "@cerc-io/util", - "version": "0.2.93", + "version": "0.2.94", "main": "dist/index.js", "license": "AGPL-3.0", "dependencies": { "@apollo/utils.keyvaluecache": "^1.0.1", "@cerc-io/nitro-node": "^0.1.15", - "@cerc-io/peer": "^0.2.93", - "@cerc-io/solidity-mapper": "^0.2.93", + "@cerc-io/peer": "^0.2.94", + "@cerc-io/solidity-mapper": "^0.2.94", "@cerc-io/ts-channel": "1.0.3-ts-nitro-0.1.1", "@ethersproject/properties": "^5.7.0", "@ethersproject/providers": "^5.4.4", @@ -49,11 +49,12 @@ "toml": "^3.0.0", "typeorm": "0.2.37", "typeorm-naming-strategies": "^2.0.0", + "winston": "^3.13.0", "ws": "^8.11.0", "yargs": "^17.0.1" }, "devDependencies": { - "@cerc-io/cache": "^0.2.93", + "@cerc-io/cache": "^0.2.94", "@nomiclabs/hardhat-waffle": "^2.0.1", "@types/bunyan": "^1.8.8", "@types/express": "^4.17.14", diff --git a/packages/util/src/config.ts b/packages/util/src/config.ts index 773dd8ed7..000b1c7a6 100644 --- a/packages/util/src/config.ts +++ b/packages/util/src/config.ts @@ -203,11 +203,31 @@ export interface P2PConfig { consensus: ConsensusConfig; } +// GQL config +export interface GQLConfig { + path: string; + maxEventsBlockRange: number; + + // GQL cache-control max-age settings (in seconds) + cache: GQLCacheConfig; + + // Boolean to load GQL query nested entity relations sequentially + loadRelationsSequential: boolean; + + // Max GQL API requests to process simultaneously (defaults to 1) + maxSimultaneousRequests?: number; + + // Max GQL API requests in queue until reject (defaults to -1, means do not reject) + maxRequestQueueLimit?: number; + + // Log directory for GQL requests + logDir?: string; +} + export interface ServerConfig { host: string; port: number; mode: string; - gqlPath: string; kind: string; enableConfigValidation: boolean; checkpointing: boolean; @@ -215,23 +235,13 @@ export interface ServerConfig { subgraphPath: string; enableState: boolean; wasmRestartBlocksInterval: number; - maxEventsBlockRange: number; clearEntitiesCacheInterval: number; // Boolean to skip updating entity fields required in state creation and not required in the frontend skipStateFieldsUpdate: boolean; - // Max GQL API requests to process simultaneously (defaults to 1) - maxSimultaneousRequests?: number; - - // Max GQL API requests in queue until reject (defaults to -1, means do not reject) - maxRequestQueueLimit?: number; - - // Boolean to load GQL query nested entity relations sequentially - loadRelationsSequential: boolean; - - // GQL cache-control max-age settings (in seconds) - gqlCache: GQLCacheConfig; + // GQL config for server + gql: GQLConfig p2p: P2PConfig; diff --git a/packages/util/src/graph/database.ts b/packages/util/src/graph/database.ts index f69b850ad..cbd86e747 100644 --- a/packages/util/src/graph/database.ts +++ b/packages/util/src/graph/database.ts @@ -763,7 +763,7 @@ export class GraphDatabase { const relationSelections = selections.filter((selection) => selection.kind === 'Field' && Boolean(relations[selection.name.value])); - if (this._serverConfig.loadRelationsSequential) { + if (this._serverConfig.gql.loadRelationsSequential) { for (const selection of relationSelections) { await this.loadRelation(queryRunner, block, relationsMap, relations, entities, selection, queryInfo); } diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 679c1537d..b0dc8c1f3 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -27,3 +27,4 @@ export * from './payments'; export * from './eth'; export * from './consensus'; export * from './validate-config'; +export * from './logger'; diff --git a/packages/util/src/logger.ts b/packages/util/src/logger.ts new file mode 100644 index 000000000..5bc86815c --- /dev/null +++ b/packages/util/src/logger.ts @@ -0,0 +1,18 @@ +import winston from 'winston'; +import path from 'path'; + +export const createGQLLogger = (logsDir = ''): winston.Logger => { + return winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + transports: [ + // Write all logs with importance level of `error` or less to `watcher-gql-error.log` + new winston.transports.File({ filename: path.resolve(logsDir, 'watcher-gql-error.log'), level: 'error' }), + // Write all logs with importance level of `info` or less to `watcher-gql.log` + new winston.transports.File({ filename: path.resolve(logsDir, 'watcher-gql.log') }) + ] + }); +}; diff --git a/packages/util/src/metrics.ts b/packages/util/src/metrics.ts index 4f9a2a9ca..a3e36c793 100644 --- a/packages/util/src/metrics.ts +++ b/packages/util/src/metrics.ts @@ -231,10 +231,10 @@ const registerWatcherConfigMetrics = async ({ server, upstream, jobQueue }: Conf watcherConfigMetric.set({ category: 'server', field: 'is_active' }, Number(server.kind === 'active')); watcherConfigMetric.set({ category: 'server', field: 'is_subgraph_watcher' }, Number(server.subgraphPath?.length > 0)); - watcherConfigMetric.set({ category: 'server', field: 'max_events_block_range' }, Number(server.maxEventsBlockRange)); + watcherConfigMetric.set({ category: 'server', field: 'max_events_block_range' }, Number(server.gql.maxEventsBlockRange)); watcherConfigMetric.set({ category: 'server', field: 'clear_entities_cache_interval' }, Number(server.clearEntitiesCacheInterval)); - watcherConfigMetric.set({ category: 'server', field: 'max_simultaneous_requests' }, Number(server.maxSimultaneousRequests)); - watcherConfigMetric.set({ category: 'server', field: 'max_request_queue_limit' }, Number(server.maxRequestQueueLimit)); + watcherConfigMetric.set({ category: 'server', field: 'max_simultaneous_requests' }, Number(server.gql.maxSimultaneousRequests)); + watcherConfigMetric.set({ category: 'server', field: 'max_request_queue_limit' }, Number(server.gql.maxRequestQueueLimit)); watcherConfigMetric.set({ category: 'upstream', field: 'is_using_rpc_client' }, Number(upstream.ethServer.rpcClient)); watcherConfigMetric.set({ category: 'upstream', field: 'is_fevm' }, Number(upstream.ethServer.isFEVM)); diff --git a/packages/util/src/server.ts b/packages/util/src/server.ts index a7645a761..0094b60ac 100644 --- a/packages/util/src/server.ts +++ b/packages/util/src/server.ts @@ -1,5 +1,5 @@ import { Application } from 'express'; -import { ApolloServer } from 'apollo-server-express'; +import { ApolloServer, ExpressContext } from 'apollo-server-express'; import { createServer } from 'http'; import { WebSocketServer } from 'ws'; import { useServer } from 'graphql-ws/lib/use/ws'; @@ -33,10 +33,12 @@ export const createAndStartServer = async ( const { host, port, - gqlCache: gqlCacheConfig, - maxSimultaneousRequests, - maxRequestQueueLimit, - gqlPath = DEFAULT_GQL_PATH + gql: { + cache: gqlCacheConfig, + maxSimultaneousRequests, + maxRequestQueueLimit, + path: gqlPath = DEFAULT_GQL_PATH + } } = serverConfig; app.use(queue({ activeLimit: maxSimultaneousRequests || 1, queuedLimit: maxRequestQueueLimit || -1 })); @@ -62,6 +64,9 @@ export const createAndStartServer = async ( } const server = new ApolloServer({ + context: (expressContext: ExpressContext) => { + return expressContext; + }, schema, csrfPrevention: true, cache: gqlCache, diff --git a/yarn.lock b/yarn.lock index dd0ab3f5a..7feecaab9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -506,6 +506,11 @@ it-pushable "^3.1.0" uint8arraylist "^2.3.2" +"@colors/colors@1.6.0", "@colors/colors@^1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.6.0.tgz#ec6cd237440700bc23ca23087f513c75508958b0" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -513,6 +518,15 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz#7f7e97ee9a725dffc7808d93668cc984e1dc477a" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + "@ensdomains/ens@^0.4.4": version "0.4.5" resolved "https://registry.npmjs.org/@ensdomains/ens/-/ens-0.4.5.tgz" @@ -3935,6 +3949,11 @@ "@types/glob" "~7.2.0" "@types/node" "*" +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/triple-beam/-/triple-beam-1.3.5.tgz#74fef9ffbaa198eb8b588be029f38b00299caa2c" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + "@types/ws@^8.5.3": version "8.5.4" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.4.tgz#bb10e36116d6e570dd943735f86c933c1587b8a5" @@ -4750,6 +4769,11 @@ async@^2.4.0: dependencies: lodash "^4.17.14" +async@^3.2.3: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asyncify-wasm@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/asyncify-wasm/-/asyncify-wasm-1.2.1.tgz#a15c0480e858619a4f971e44e6fc05c49015d9e8" @@ -6168,7 +6192,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.9.0: +color-convert@^1.9.0, color-convert@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== @@ -6187,11 +6211,35 @@ color-name@1.1.3: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== -color-name@~1.1.4: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.4.tgz#8d442d1186152f60453bf8070cd66eb364e59243" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + columnify@^1.5.4: version "1.5.4" resolved "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz" @@ -7150,6 +7198,11 @@ emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" @@ -8285,6 +8338,11 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-4.2.3.tgz#4d9ccdbc61e8629b259fdca67e65891448d569fd" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + fetch-ponyfill@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-4.1.0.tgz" @@ -8436,6 +8494,11 @@ flow-stoplight@^1.0.0: resolved "https://registry.npmjs.org/flow-stoplight/-/flow-stoplight-1.0.0.tgz" integrity sha1-SiksW8/4s5+mzAyxqFPYbyfu/3s= +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + follow-redirects@^1.0.0: version "1.15.3" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" @@ -9899,6 +9962,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-bigint@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz" @@ -10864,6 +10932,11 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + lcid@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" @@ -11299,6 +11372,18 @@ log-symbols@4.1.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" +logform@^2.3.2, logform@^2.4.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-2.6.0.tgz#8c82a983f05d6eaeb2d75e3decae7a768b2bf9b5" + integrity sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + loglevel@^1.6.8: version "1.8.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.8.1.tgz#5c621f83d5b48c54ae93b6156353f555963377b4" @@ -12764,6 +12849,13 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + onetime@^5.1.0, onetime@^5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" @@ -14512,6 +14604,11 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" +safe-stable-stringify@^2.3.1: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" @@ -14841,6 +14938,13 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + simple-update-notifier@^1.0.7: version "1.1.0" resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz#67694c121de354af592b347cdba798463ed49c82" @@ -15135,6 +15239,11 @@ ssri@^8.0.0, ssri@^8.0.1: dependencies: minipass "^3.1.1" +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + stacktrace-parser@^0.1.10: version "0.1.10" resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz#29fb0cae4e0d0b85155879402857a1639eb6051a" @@ -15512,6 +15621,11 @@ text-extensions@^1.0.0: resolved "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz" integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -15686,6 +15800,11 @@ trim-right@^1.0.1: resolved "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.4.1.tgz#6fde70271dc6e5d73ca0c3b24e2d92afb7441984" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + truncate-utf8-bytes@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz#405923909592d56f78a5818434b0b78489ca5f2b" @@ -16755,6 +16874,32 @@ window-size@^0.2.0: resolved "https://registry.npmjs.org/window-size/-/window-size-0.2.0.tgz" integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU= +winston-transport@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.7.0.tgz#e302e6889e6ccb7f383b926df6936a5b781bd1f0" + integrity sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg== + dependencies: + logform "^2.3.2" + readable-stream "^3.6.0" + triple-beam "^1.3.0" + +winston@^3.13.0: + version "3.13.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.13.0.tgz#e76c0d722f78e04838158c61adc1287201de7ce3" + integrity sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.4.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.7.0" + word-wrap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"