Skip to content

Commit e0a2794

Browse files
committed
Implement sdk log watching and progress on log modal
1 parent b2fcf74 commit e0a2794

File tree

4 files changed

+126
-12
lines changed

4 files changed

+126
-12
lines changed

src/consts/consts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export const MIN_CHAIN_BALANCE = 1; // 1 Wei
22
// TODO edit this based on experiments
33
export const WARP_DEPLOY_GAS_UNITS = BigInt(1e7);
4-
export const REFUND_FEE_PADDING_FACTOR = 1.1;
4+
export const REFUND_FEE_PADDING_FACTOR = 1.2;
55
export const MIN_DEPLOYER_BALANCE_TO_SHOW = BigInt(1e15); // 0.001 ETH

src/features/deployment/warp/WarpDeploymentDeploy.tsx

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MultiProtocolProvider, WarpCoreConfig } from '@hyperlane-xyz/sdk';
22
import { errorToString, sleep } from '@hyperlane-xyz/utils';
3-
import { Button, Modal, SpinnerIcon, useModal } from '@hyperlane-xyz/widgets';
3+
import { Button, SpinnerIcon, useModal } from '@hyperlane-xyz/widgets';
44
import { useMemo, useState } from 'react';
55
import { PlanetSpinner } from '../../../components/animation/PlanetSpinner';
66
import { SlideIn } from '../../../components/animation/SlideIn';
@@ -9,7 +9,6 @@ import { GasIcon } from '../../../components/icons/GasIcon';
99
import { LogsIcon } from '../../../components/icons/LogsIcon';
1010
import { StopIcon } from '../../../components/icons/StopIcon';
1111
import { H1 } from '../../../components/text/Headers';
12-
import { config } from '../../../consts/config';
1312
import { WARP_DEPLOY_GAS_UNITS } from '../../../consts/consts';
1413
import { CardPage } from '../../../flows/CardPage';
1514
import { useCardNav } from '../../../flows/hooks';
@@ -19,11 +18,13 @@ import { getChainDisplayName } from '../../chains/utils';
1918
import { useFundDeployerAccount } from '../../deployerWallet/fund';
2019
import { useRefundDeployerAccounts } from '../../deployerWallet/refund';
2120
import { useOrCreateDeployerWallets } from '../../deployerWallet/wallets';
21+
import { LogModal } from '../../logs/LogModal';
22+
import { useSdkLogWatcher } from '../../logs/useSdkLogs';
2223
import { useDeploymentHistory, useWarpDeploymentConfig } from '../hooks';
2324
import { DeploymentStatus, DeploymentType, WarpDeploymentConfig } from '../types';
2425
import { useWarpDeployment } from './deploy';
2526

26-
const CANCEL_SLEEP_DELAY = config.isDevMode ? 5_000 : 10_000;
27+
const CANCEL_SLEEP_DELAY = 10_000;
2728

2829
enum DeployStep {
2930
FundDeployer,
@@ -41,6 +42,8 @@ export function WarpDeploymentDeploy() {
4142
const { updateDeploymentStatus, currentIndex, completeDeployment, failDeployment } =
4243
useDeploymentHistory();
4344

45+
useSdkLogWatcher();
46+
4447
const { refundAsync } = useRefundDeployerAccounts();
4548

4649
const onFailure = (error: Error) => {
@@ -211,10 +214,6 @@ function ExecuteDeploy({
211214
const chainListString = chains.map((c) => getChainDisplayName(multiProvider, c, true)).join(', ');
212215

213216
const { isOpen, open, close } = useModal();
214-
const onClickViewLogs = () => {
215-
// TODO get logs somehow
216-
open();
217-
};
218217

219218
return (
220219
<div className="flex flex-col items-center text-center">
@@ -226,13 +225,11 @@ function ExecuteDeploy({
226225
<p className="text-gray-700">This will take a few minutes</p>
227226
{/* <p className="mt-3">TODO status text</p> */}
228227
</div>
229-
<Button onClick={onClickViewLogs} className="mt-3 gap-2.5">
228+
<Button onClick={open} className="mt-3 gap-2.5">
230229
<LogsIcon width={14} height={14} color={Color.accent['500']} />
231230
<span className="text-md text-accent-500">View deployment logs</span>
232231
</Button>
233-
<Modal isOpen={isOpen} close={close} panelClassname="p-4">
234-
<div>TODO logs here</div>
235-
</Modal>
232+
<LogModal isOpen={isOpen} close={close} />
236233
</div>
237234
);
238235
}

src/features/logs/LogModal.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { Modal, SegmentedControl } from '@hyperlane-xyz/widgets';
2+
import { pino } from 'pino';
3+
import { useState } from 'react';
4+
import { H3 } from '../../components/text/Headers';
5+
import { LOG_LEVELS, useSdkLogs } from './useSdkLogs';
6+
7+
export function LogModal({ isOpen, close }: { isOpen: boolean; close: () => void }) {
8+
const sdkLogs = useSdkLogs();
9+
const [filterLevel, setFilterLevel] = useState(LOG_LEVELS[1]);
10+
const filterLevelValue = getLevelValue(filterLevel);
11+
12+
return (
13+
<Modal isOpen={isOpen} close={close} panelClassname="p-4">
14+
<H3 className="text-center">Deployment Logs</H3>
15+
<SegmentedControl options={LOG_LEVELS} onChange={(l) => setFilterLevel(l!)} />
16+
<ul className="list-none space-y-0.5 text-sm">
17+
{sdkLogs.map(([timestamp, level, message], i) =>
18+
getLevelValue(level) >= filterLevelValue ? (
19+
<li className="nth-child(even):bg-blue-500/5" key={i}>
20+
{new Date(timestamp).toLocaleTimeString()}: {message}
21+
</li>
22+
) : null,
23+
)}
24+
</ul>
25+
</Modal>
26+
);
27+
}
28+
29+
function getLevelValue(level: string): number {
30+
return pino.levels.values[level] || 0;
31+
}

src/features/logs/useSdkLogs.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
configureRootLogger,
3+
getRootLogger,
4+
LogFormat,
5+
LogLevel as LogLevelWithOff,
6+
} from '@hyperlane-xyz/utils';
7+
import { useInterval } from '@hyperlane-xyz/widgets';
8+
import { Logger } from 'pino';
9+
import { useCallback, useEffect, useState } from 'react';
10+
11+
export const LOG_LEVELS = Object.values(LogLevelWithOff).filter((l) => l !== LogLevelWithOff.Off);
12+
export type LogLevel = (typeof LOG_LEVELS)[number];
13+
// Tuple of timestamp, level, message
14+
type Log = [number, LogLevel, string];
15+
let logBuffer: Array<Log> = [];
16+
17+
export function useSdkLogWatcher() {
18+
useEffect(() => {
19+
// TODO confirm this line doesn't break the log watching
20+
configureRootLogger(LogFormat.JSON, LogLevelWithOff.Debug);
21+
const onLog = (timestamp: number, level: LogLevel, ...args: any) => {
22+
const message = `${args}`.replaceAll('[object Object],', '').trim();
23+
logBuffer.push([timestamp, level, message]);
24+
};
25+
const rootLogger = getRootLogger();
26+
// NOTE ABOUT PINO:
27+
// Pino sucks. Splitting it's log output to multiple transports doesn't seem
28+
// to be possible. There is a way to specify transports at logger init time
29+
// but it requires the use of worker threads which greatly complicates the
30+
// bundling and runtime requirements for the utils lib. The following two
31+
// method calls hack in wrappers for the log methods to force a call to onLog.
32+
wrapChildMethod(rootLogger, onLog);
33+
wrapLogMethods(rootLogger, onLog);
34+
35+
return () => {
36+
// Replace global rootLogger with new one
37+
// TODO this may not work since deployer files already got ran and bound to first rootLogger
38+
configureRootLogger(LogFormat.JSON, LogLevelWithOff.Debug);
39+
logBuffer = [];
40+
};
41+
}, []);
42+
}
43+
44+
export function useSdkLogs() {
45+
const [logs, setLogs] = useState<Array<Log>>([]);
46+
const syncLogs = useCallback(() => setLogs([...logBuffer]), []);
47+
useInterval(syncLogs, 250);
48+
return logs;
49+
}
50+
51+
/**
52+
* Add a layer of indirection to the logger's 'child' method
53+
* so that any children created from it have their log methods wrapped.
54+
*/
55+
function wrapChildMethod(
56+
logger: Logger,
57+
onLog: (timestamp: number, level: LogLevel, ...args: any) => void,
58+
) {
59+
const defaultChild = logger.child.bind(logger);
60+
// @ts-ignore allow spread argument
61+
logger.child = (...args: any) => {
62+
// @ts-ignore allow spread argument
63+
const childLogger = defaultChild(...args);
64+
wrapLogMethods(childLogger, onLog);
65+
return childLogger;
66+
};
67+
}
68+
69+
/**
70+
* Add a layer of indirection to the logger's log methods
71+
* so that they trigger the onLog callback each time they're called.
72+
*/
73+
function wrapLogMethods(
74+
logger: Logger,
75+
onLog: (timestamp: number, level: LogLevel, ...args: any) => void,
76+
) {
77+
for (const level of LOG_LEVELS) {
78+
const defaultMethod = logger[level].bind(logger);
79+
const wrappedMethod = (...args: any) => {
80+
// @ts-ignore allow spread argument
81+
defaultMethod(...args);
82+
onLog(Date.now(), level, ...args);
83+
};
84+
logger[level] = wrappedMethod;
85+
}
86+
}

0 commit comments

Comments
 (0)