Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: log every rpc call across all the viem clients used #85

Merged
merged 6 commits into from
Nov 8, 2024

Conversation

jahabeebs
Copy link
Collaborator

🤖 Linear

Closes GRT-239

Description

  • Logs every RPC call using some resources linked below regarding viem's HTTP transport
  • transportsArray now injected into logger instantiation
  • some changes to the logger class to log out metadata well

https://viem.sh/docs/clients/transports/http.html#onfetchrequest-optional
https://viem.sh/docs/clients/transports/http.html#onfetchresponse-optional
wevm/viem#2244 (ended up using the clone function suggested here) - https://developer.mozilla.org/en-US/docs/Web/API/Response/clone

@jahabeebs jahabeebs self-assigned this Nov 7, 2024
Copy link

linear bot commented Nov 7, 2024

@jahabeebs jahabeebs changed the title feat: log every RPC call across all the viem clients used feat: log every rpc call across all the viem clients used Nov 7, 2024
Copy link
Collaborator

@0xyaco 0xyaco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one!

*/
constructor(
private readonly rpcConfig: ProtocolRpcConfig,
contracts: ProtocolContractsAddresses,
privateKey: Hex,
blockNumberService?: BlockNumberService,
private readonly logger?: ILogger,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason for logger to be optional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was doing this for not needing to inject for tests and scripts but you're right we should have it mandatory--did the refactor

Comment on lines 255 to 274
onFetchRequest: async (request) => {
const clonedRequest = {
url: request.url,
method: request.method,
headers: Array.from(request.headers.entries()),
body: await request.clone().text(),
};

this.logger?.debug(`Fetch Request`, clonedRequest);
},
onFetchResponse: async (response) => {
const clonedResponse = {
url: response.url,
status: response.status,
headers: Array.from(response.headers.entries()),
body: await response.clone().text(),
};

this.logger?.debug(`Fetch Response`, clonedResponse);
},
Copy link
Collaborator

@0xyaco 0xyaco Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Mind sharing an example for a logged read request/response + a logged write request/response? Interested in seeing the body mostly, to know if operators will have enough data for their debugging.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i add a comment on same lines, if logger is undefined, we can omit doing the clone object (i think the delay is minimum but we have await operations on the clone() method)

however, this question is related to Yaco's one

Copy link
Collaborator Author

@jahabeebs jahabeebs Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xyaco I am having some difficulties with running the e2e tests but here are some logs from the unit tests:

@ebo-agent/shared:test: stdout | tests/services/logger.spec.ts > Logger Methods > includes metadata in log output
@ebo-agent/shared:test: 2024-11-07 16:26:37 info: Test message {"key":"value"}
@ebo-agent/shared:test: 
@ebo-agent/shared:test: stdout | tests/services/logger.spec.ts > Logger Methods > handles errors with metadata
@ebo-agent/shared:test: 2024-11-07 16:26:37 error: Error: An error occurred
@ebo-agent/shared:test:     at /Users/jacob/Documents/Programming/EBO-agent/packages/shared/tests/services/logger.spec.ts:28:23
@ebo-agent/shared:test:     at file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:146:14
@ebo-agent/shared:test:     at file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:533:11
@ebo-agent/shared:test:     at runWithTimeout (file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:61:7)
@ebo-agent/shared:test:     at runTest (file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:986:17)
@ebo-agent/shared:test:     at processTicksAndRejections (node:internal/process/task_queues:105:5)
@ebo-agent/shared:test:     at runSuite (file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:1142:15)
@ebo-agent/shared:test:     at runSuite (file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:1142:15)
@ebo-agent/shared:test:     at runFiles (file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:1199:5)
@ebo-agent/shared:test:     at startTests (file:///Users/jacob/Documents/Programming/EBO-agent/node_modules/.pnpm/@[email protected]/node_modules/@vitest/runner/dist/index.js:1208:3) {"key":"value"}

note the value at the bottom there ^

also, this is what it would look like theoretically (before the below refactor to the JSON format) if I could get the e2e tests running on this branch lol

2024-11-07 16:45:23 debug: Fetch Request {
  "url": "https://l2-node.example.com",
  "method": "POST",
  "headers": [
    ["Content-Type", "application/json"]
  ],
  "body": "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBlockByNumber\",\"params\":[\"latest\",false],\"id\":1}"
}

2024-11-07 16:45:23 debug: Fetch Response {
  "url": "https://l2-node.example.com",
  "status": 200,
  "headers": [
    ["Content-Type", "application/json"]
  ],
  "body": "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"number\":\"0x10d4f\",\"hash\":\"0x...\",\"...\":\"...\"}}"
}


2024-11-07 16:45:25 debug: Fetch Request {
  "url": "https://l2-node.example.com",
  "method": "POST",
  "headers": [
    ["Content-Type", "application/json"]
  ],
  "body": "{\"jsonrpc\":\"2.0\",\"method\":\"eth_sendRawTransaction\",\"params\":[\"0xf86c808504a817c80082520894...\"],\"id\":2}"
}

2024-11-07 16:45:25 debug: Fetch Response {
  "url": "https://l2-node.example.com",
  "status": 200,
  "headers": [
    ["Content-Type", "application/json"]
  ],
  "body": "{\"jsonrpc\":\"2.0\",\"id\":2,\"result\":\"0xtransactionHash...\"}"
}

@0xnigir1 got rid of the clone 👍🏻

Comment on lines 24 to 26
const { level, message, timestamp, stack, ...meta } = info;
const metaString = Object.keys(meta).length ? ` ${stringify(meta)}` : "";
return `${timestamp} ${level}: ${stack ?? message ?? ""}${metaString}`;
Copy link
Collaborator

@0xyaco 0xyaco Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

Now that we have the possibility of passing some extra metadata while logging, I think we are ready to change the logging format from the current one to a more "tooling-consumable" format (ie JSON):

{
  "timestamp": <timestamp>,
  "level": <level>,
  "message": <message>,
  "stack": <stack>,
  "data": <stack>
}

With stack and data included only if their values are defined.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ refactored the logger class

@@ -10,50 +12,53 @@ export class Logger implements ILogger {
private logger: WinstonLogger;
private static instance: Logger | null;
private level: LogLevel;
private constructor() {
private constructor(transportsArray?: Transport[]) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we setting the transportsArray somewhere while initializing the logger?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually I didn't end up needing this so I've removed it

}
return Logger.instance;
}
isValidLogLevel(level: any): level is LogLevel {
return validLogLevels.includes(level);
}

info(message: string) {
this.logger.info(message);
info(message: string, meta?: unknown) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice usage of unknown 👍

Copy link
Collaborator

@0xnigir1 0xnigir1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice pr 💯

@@ -23,7 +23,8 @@
"keywords": [],
"author": "",
"dependencies": {
"winston": "3.13.1"
"winston": "3.13.1",
"winston-transport": "^4.8.0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

carettt

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sigh

Comment on lines 255 to 274
onFetchRequest: async (request) => {
const clonedRequest = {
url: request.url,
method: request.method,
headers: Array.from(request.headers.entries()),
body: await request.clone().text(),
};

this.logger?.debug(`Fetch Request`, clonedRequest);
},
onFetchResponse: async (response) => {
const clonedResponse = {
url: response.url,
status: response.status,
headers: Array.from(response.headers.entries()),
body: await response.clone().text(),
};

this.logger?.debug(`Fetch Response`, clonedResponse);
},
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i add a comment on same lines, if logger is undefined, we can omit doing the clone object (i think the delay is minimum but we have await operations on the clone() method)

however, this question is related to Yaco's one

@jahabeebs jahabeebs requested review from 0xyaco and 0xnigir1 November 7, 2024 23:55
0xnigir1
0xnigir1 previously approved these changes Nov 8, 2024
url: response.url,
status: response.status,
headers: Array.from(response.headers.entries()),
body: await response.clone().text(),
body: await response.text(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jahabeebs won't removing the clone() call here consume the response stream and cause the response to be used by our protocol provider later to be empty? 🤔

Asking based on this issue I sent you before: wevm/viem#2244

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm yes you're right, I confused what nigiri mentioned 😅

}),
format((info) => {
const { timestamp, level, message, stack, ...meta } = info;
const logEntry: any = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's define LogEntry type and change this to avoid using any:

Suggested change
const logEntry: any = {
const logEntry: LogEntry = {

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added better typing ✅

@jahabeebs jahabeebs requested a review from 0xnigir1 November 8, 2024 17:04
@jahabeebs jahabeebs merged commit e9893cd into dev Nov 8, 2024
5 checks passed
@jahabeebs jahabeebs deleted the feat/grt-239-log-every-rpc-call branch November 8, 2024 17:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants