Skip to content

Commit 2faaa43

Browse files
authored
feat(apps/price_pusher)!: switch to structured logger via pino (#1714)
* feat(apps/price_pusher)!: switch to structured logger via pino This change switches from `console` to `pino` for logging that is a structured logger and allows us to have json formatted loggers. In adition to just that change, pino does an amazing job at logging the errors by including all their data + the stacktrace. This change does minimal change to the log level and the `console.log` is changed to info or debug level based on my judgement. This change is marked as breaking because it breaks any reliability/ops based on the logs. * fix: address review comments * fix: add @solana/web3.js deps as its used * refactor: make it more configurable
1 parent b50e9e1 commit 2faaa43

21 files changed

+645
-332
lines changed

apps/price_pusher/README.md

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ To run the price pusher, please run the following commands, replacing the comman
8383

8484
```sh
8585
# Please run the two following commands once from the root of the repo to build the code.
86-
npm install
86+
pnpm install
8787
pnpm exec lerna run build --scope @pythnetwork/price-pusher --include-dependencies
8888
8989
# Navigate to the price_pusher folder
9090
cd apps/price_pusher
9191
9292
# For EVM
93-
npm run start -- evm --endpoint wss://example-rpc.com \
93+
pnpm run start evm --endpoint wss://example-rpc.com \
9494
--pyth-contract-address 0xff1a0f4744e8582DF...... \
9595
--price-service-endpoint https://example-hermes-rpc.com \
9696
--price-config-file "path/to/price-config.beta.sample.yaml" \
@@ -100,7 +100,7 @@ npm run start -- evm --endpoint wss://example-rpc.com \
100100
[--override-gas-price-multiplier 1.1]
101101
102102
# For Injective
103-
npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
103+
pnpm run start injective --grpc-endpoint https://grpc-endpoint.com \
104104
--pyth-contract-address inj1z60tg0... --price-service-endpoint "https://example-hermes-rpc.com" \
105105
--price-config-file "path/to/price-config.beta.sample.yaml" \
106106
--mnemonic-file "path/to/mnemonic.txt" \
@@ -110,7 +110,7 @@ npm run start -- injective --grpc-endpoint https://grpc-endpoint.com \
110110
[--polling-frequency 5]
111111
112112
# For Aptos
113-
npm run start -- aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
113+
pnpm run start aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
114114
--pyth-contract-address 0x7e783b349d3e89cf5931af376ebeadbfab855b3fa239b7ada8f5a92fbea6b387 \
115115
--price-service-endpoint "https://example-hermes-rpc.com" \
116116
--price-config-file "path/to/price-config.beta.sample.yaml" \
@@ -119,7 +119,7 @@ npm run start -- aptos --endpoint https://fullnode.testnet.aptoslabs.com/v1 \
119119
[--polling-frequency 5]
120120
121121
# For Sui
122-
npm run start -- sui \
122+
pnpm run start sui \
123123
--endpoint https://sui-testnet-rpc.allthatnode.com \
124124
--pyth-package-id 0x975e063f398f720af4f33ec06a927f14ea76ca24f7f8dd544aa62ab9d5d15f44 \
125125
--pyth-state-id 0xd8afde3a48b4ff7212bd6829a150f43f59043221200d63504d981f62bff2e27a \
@@ -134,7 +134,7 @@ npm run start -- sui \
134134
[--num-gas-objects 30]
135135
136136
# For Near
137-
npm run start -- near \
137+
pnpm run start near \
138138
--node-url https://rpc.testnet.near.org \
139139
--network testnet \
140140
--account-id payer.testnet \
@@ -146,7 +146,7 @@ npm run start -- near \
146146
[--polling-frequency 5]
147147
148148
# For Solana, using Jito (recommended)
149-
npm run start -- solana \
149+
pnpm run start solana \
150150
--endpoint https://api.mainnet-beta.solana.com \
151151
--keypair-file ./id.json \
152152
--shard-id 1 \
@@ -161,7 +161,7 @@ npm run start -- solana \
161161
[--polling-frequency 5]
162162
163163
# For Solana, using Solana RPC
164-
npm run start -- solana \
164+
pnpm run start solana \
165165
--endpoint https://api.devnet.solana.com \
166166
--keypair-file ./id.json \
167167
--shard-id 1 \
@@ -184,23 +184,35 @@ docker run public.ecr.aws/pyth-network/xc-price-pusher:v<version> -- <above-argu
184184
To know more about the arguments the price-pusher accepts. You can run:
185185

186186
```sh
187-
npm run start -- --help
187+
pnpm run start --help
188188
189189
# for specific network run
190-
npm run start -- {network} --help
190+
pnpm run start {network} --help
191191
```
192192

193+
### Logging
194+
195+
By default, the logging is set to `info`. You can change the logging level by passing the argument `--log-level` with the desired level.
196+
The available levels are `error`, `warn`, `info`, `debug`, and `trace`. Also, the logs have JSON format. If you wish to run the code with
197+
human-readable logs, you can pipe the output of the program to `pino-pretty`. See the example below for more information on how to do this.
198+
199+
You can configure the log level of some of the modules of the price pusher as well. The available modules are PriceServiceConnection, which
200+
is responsible for connecting to the Hermes price service, and Controller, which is responsible for checking the prices from the Hermes
201+
and the on-chain Pyth contract and deciding whether to push a new price. You can configure the log level of these modules by passing the
202+
`--price-service-connection-log-level` and `--controller-log-level` arguments, respectively.
203+
193204
### Example
194205

195206
For example, to push `BTC/USD` and `BNB/USD` prices on Fantom testnet, run the following command:
196207

197208
```sh
198-
npm run dev -- evm \
209+
pnpm run dev evm \
199210
--endpoint https://endpoints.omniatech.io/v1/fantom/testnet/public \
200211
--pyth-contract-address 0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb \
201212
--price-service-endpoint https://hermes.pyth.network \
202213
--mnemonic-file "./mnemonic" \
203-
--price-config-file "./price-config.stable.sample.yaml"
214+
--price-config-file "./price-config.stable.sample.yaml" \
215+
| pnpm exec pino-pretty # Make logs human-readable
204216
```
205217

206218
[`price-config.stable.sample.yaml`](./price-config.stable.sample.yaml) contains configuration for `BTC/USD`
@@ -210,7 +222,7 @@ contains the same configuration for `BTC/USD` and `BNB/USD` on Pyth beta data so
210222
You can also provide a config file instead of providing command line options, run the following command:
211223

212224
```sh
213-
npm run start -- injective --config "./config.injective.sample.json"
225+
pnpm run start injective --config "./config.injective.sample.json"
214226
```
215227

216228
[`config.injective.sample.json`](./config.injective.sample.json) contains configuration to publish on Injective testnet.

apps/price_pusher/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pythnetwork/price-pusher",
3-
"version": "6.8.0",
3+
"version": "7.0.0-alpha",
44
"description": "Pyth Price Pusher",
55
"homepage": "https://pyth.network",
66
"main": "lib/index.js",
@@ -46,6 +46,7 @@
4646
"@typescript-eslint/parser": "^6.0.0",
4747
"eslint": "^8.13.0",
4848
"jest": "^29.7.0",
49+
"pino-pretty": "^11.2.1",
4950
"prettier": "^2.6.2",
5051
"ts-jest": "^29.1.1",
5152
"ts-node": "^10.9.1",
@@ -62,11 +63,14 @@
6263
"@pythnetwork/pyth-solana-receiver": "workspace:*",
6364
"@pythnetwork/pyth-sui-js": "workspace:*",
6465
"@pythnetwork/solana-utils": "workspace:*",
66+
"@solana/web3.js": "^1.93.0",
6567
"@truffle/hdwallet-provider": "^2.1.3",
68+
"@types/pino": "^7.0.5",
6669
"aptos": "^1.8.5",
6770
"jito-ts": "^3.0.1",
6871
"joi": "^17.6.0",
6972
"near-api-js": "^3.0.2",
73+
"pino": "^9.2.0",
7074
"web3": "^1.8.1",
7175
"web3-core": "^1.8.1",
7276
"web3-eth-contract": "^1.8.1",

apps/price_pusher/src/aptos/aptos.ts

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ import {
77
import { AptosAccount, AptosClient } from "aptos";
88
import { DurationInSeconds } from "../utils";
99
import { PriceServiceConnection } from "@pythnetwork/price-service-client";
10+
import { Logger } from "pino";
1011

1112
export class AptosPriceListener extends ChainPriceListener {
1213
constructor(
1314
private pythModule: string,
1415
private endpoint: string,
1516
priceItems: PriceItem[],
17+
private logger: Logger,
1618
config: {
1719
pollingFrequency: DurationInSeconds;
1820
}
1921
) {
20-
super("aptos", config.pollingFrequency, priceItems);
22+
super(config.pollingFrequency, priceItems);
2123
}
2224

2325
async getOnChainPriceInfo(priceId: string): Promise<PriceInfo | undefined> {
@@ -46,7 +48,7 @@ export class AptosPriceListener extends ChainPriceListener {
4648
const price =
4749
multiplier * Number(priceItemRes.price_feed.price.price.magnitude);
4850

49-
console.log(
51+
this.logger.debug(
5052
`Polled an Aptos on-chain price for feed ${this.priceIdToAlias.get(
5153
priceId
5254
)} (${priceId}).`
@@ -57,11 +59,11 @@ export class AptosPriceListener extends ChainPriceListener {
5759
conf: priceItemRes.price_feed.price.conf,
5860
publishTime: Number(priceItemRes.price_feed.price.timestamp),
5961
};
60-
} catch (e) {
61-
console.error(
62-
`Polling Aptos on-chain price for ${priceId} failed. Error:`
62+
} catch (err) {
63+
this.logger.error(
64+
err,
65+
`Polling Aptos on-chain price for ${priceId} failed.`
6366
);
64-
console.error(e);
6567
return undefined;
6668
}
6769
}
@@ -88,6 +90,7 @@ export class AptosPricePusher implements IPricePusher {
8890

8991
constructor(
9092
private priceServiceConnection: PriceServiceConnection,
93+
private logger: Logger,
9194
private pythContractAddress: string,
9295
private endpoint: string,
9396
private mnemonic: string,
@@ -126,9 +129,8 @@ export class AptosPricePusher implements IPricePusher {
126129
try {
127130
// get the latest VAAs for updatePriceFeed and then push them
128131
priceFeedUpdateData = await this.getPriceFeedsUpdateData(priceIds);
129-
} catch (e) {
130-
console.error("Error fetching the latest vaas to push");
131-
console.error(e);
132+
} catch (err) {
133+
this.logger.error(err, "Error fetching the latest vaas to push.");
132134
return;
133135
}
134136

@@ -158,7 +160,10 @@ export class AptosPricePusher implements IPricePusher {
158160
const signedTx = await client.signTransaction(account, rawTx);
159161
const pendingTx = await client.submitTransaction(signedTx);
160162

161-
console.log("Successfully broadcasted txHash:", pendingTx.hash);
163+
this.logger.debug(
164+
{ hash: pendingTx.hash },
165+
"Successfully broadcasted tx."
166+
);
162167

163168
// Sometimes broadcasted txs don't make it on-chain and they cause our sequence number
164169
// to go out of sync. Missing transactions are rare and we don't want this check to block
@@ -167,9 +172,8 @@ export class AptosPricePusher implements IPricePusher {
167172
this.waitForTransactionConfirmation(client, pendingTx.hash);
168173

169174
return;
170-
} catch (e: any) {
171-
console.error("Error executing messages");
172-
console.error(e);
175+
} catch (err: any) {
176+
this.logger.error(err, "Error executing messages");
173177

174178
// Reset the sequence number to re-sync it (in case that was the issue)
175179
this.lastSequenceNumber = undefined;
@@ -189,10 +193,12 @@ export class AptosPricePusher implements IPricePusher {
189193
timeoutSecs: 10,
190194
});
191195

192-
console.log(`Transaction with txHash "${txHash}" confirmed.`);
193-
} catch (e) {
194-
console.error(`Transaction with txHash "${txHash}" failed to confirm.`);
195-
console.error(e);
196+
this.logger.info({ hash: txHash }, `Transaction confirmed.`);
197+
} catch (err) {
198+
this.logger.error(
199+
{ err, hash: txHash },
200+
`Transaction failed to confirm.`
201+
);
196202

197203
this.lastSequenceNumber = undefined;
198204
}
@@ -218,7 +224,7 @@ export class AptosPricePusher implements IPricePusher {
218224
this.lastSequenceNumber = Number(
219225
(await client.getAccount(account.address())).sequence_number
220226
);
221-
console.log(
227+
this.logger.debug(
222228
`Fetched account sequence number: ${this.lastSequenceNumber}`
223229
);
224230
return this.lastSequenceNumber;

apps/price_pusher/src/aptos/command.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
APTOS_ACCOUNT_HD_PATH,
1212
} from "./aptos";
1313
import { AptosAccount } from "aptos";
14+
import pino from "pino";
1415

1516
export default {
1617
command: "aptos",
@@ -37,6 +38,9 @@ export default {
3738
...options.pythContractAddress,
3839
...options.pollingFrequency,
3940
...options.pushingFrequency,
41+
...options.logLevel,
42+
...options.priceServiceConnectionLogLevel,
43+
...options.controllerLogLevel,
4044
},
4145
handler: function (argv: any) {
4246
// FIXME: type checks for this
@@ -49,44 +53,50 @@ export default {
4953
pushingFrequency,
5054
pollingFrequency,
5155
overrideGasPriceMultiplier,
56+
logLevel,
57+
priceServiceConnectionLogLevel,
58+
controllerLogLevel,
5259
} = argv;
5360

61+
const logger = pino({ level: logLevel });
62+
5463
const priceConfigs = readPriceConfigFile(priceConfigFile);
5564
const priceServiceConnection = new PriceServiceConnection(
5665
priceServiceEndpoint,
5766
{
58-
logger: {
59-
// Log only warnings and errors from the price service client
60-
info: () => undefined,
61-
warn: console.warn,
62-
error: console.error,
63-
debug: () => undefined,
64-
trace: () => undefined,
65-
},
67+
logger: logger.child(
68+
{ module: "PriceServiceConnection" },
69+
{ level: priceServiceConnectionLogLevel }
70+
),
6671
}
6772
);
73+
6874
const mnemonic = fs.readFileSync(mnemonicFile, "utf-8").trim();
6975
const account = AptosAccount.fromDerivePath(
7076
APTOS_ACCOUNT_HD_PATH,
7177
mnemonic
7278
);
73-
console.log(`Pushing from account address: ${account.address()}`);
79+
logger.info(`Pushing from account address: ${account.address()}`);
7480

7581
const priceItems = priceConfigs.map(({ id, alias }) => ({ id, alias }));
7682

7783
const pythListener = new PythPriceListener(
7884
priceServiceConnection,
79-
priceItems
85+
priceItems,
86+
logger.child({ module: "PythPriceListener" })
8087
);
8188

8289
const aptosListener = new AptosPriceListener(
8390
pythContractAddress,
8491
endpoint,
8592
priceItems,
93+
logger.child({ module: "AptosPriceListener" }),
8694
{ pollingFrequency }
8795
);
96+
8897
const aptosPusher = new AptosPricePusher(
8998
priceServiceConnection,
99+
logger.child({ module: "AptosPricePusher" }),
90100
pythContractAddress,
91101
endpoint,
92102
mnemonic,
@@ -98,6 +108,7 @@ export default {
98108
pythListener,
99109
aptosListener,
100110
aptosPusher,
111+
logger.child({ module: "Controller" }, { level: controllerLogLevel }),
101112
{ pushingFrequency }
102113
);
103114

0 commit comments

Comments
 (0)