diff --git a/package-lock.json b/package-lock.json index 49d26f7d..8f8f2b45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "cosmjs-types": "^0.7.1", "dotenv": "^16.0.3", "lodash": "^4.17.21", - "mathjs": "^11.7.0" + "mathjs": "^11.7.0", + "winston": "^3.13.0" } }, "node_modules/@babel/runtime": { @@ -32,6 +33,14 @@ "node": ">=6.9.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/@confio/ics23": { "version": "0.6.8", "resolved": "https://registry.npmjs.org/@confio/ics23/-/ics23-0.6.8.tgz", @@ -175,6 +184,16 @@ "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.30.1.tgz", "integrity": "sha512-KvvX58MGMWh7xA+N+deCfunkA/ZNDvFLw4YbOmX3f/XBIkqrVY7qlotfy2aNb1kgp6h4B6Yc8YawJPDTfvWX7g==" }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@ethersproject/abstract-provider": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", @@ -805,6 +824,11 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.10.tgz", "integrity": "sha512-9avDaQJczATcXgfmMAW3MIWArOO7A+m90vuCFLr8AotWf8igO/mRoYukrk2cqZVtv38tHs33retzHEilM7FpeQ==" }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/aes-js": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", @@ -821,6 +845,11 @@ "node": ">=4" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -958,6 +987,15 @@ "node": ">=4" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -971,6 +1009,24 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1082,6 +1138,11 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/escape-latex": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", @@ -1095,6 +1156,16 @@ "node": ">=0.8.0" } }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -1296,6 +1367,11 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-core-module": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", @@ -1318,6 +1394,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", @@ -1336,6 +1423,11 @@ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, "node_modules/libsodium": { "version": "0.7.11", "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.11.tgz", @@ -1368,6 +1460,22 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "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" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", @@ -1443,6 +1551,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -1459,6 +1572,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1502,6 +1623,19 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readonly-date": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/readonly-date/-/readonly-date-1.0.0.tgz", @@ -1566,6 +1700,14 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/scrypt-js": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", @@ -1615,6 +1757,30 @@ "node": ">=6" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1645,11 +1811,24 @@ "node": ">=0.10" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/tiny-emitter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/typed-function": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", @@ -1658,6 +1837,45 @@ "node": ">= 14" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/winston": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", + "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" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 4c674812..134e9c52 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "cosmjs-types": "^0.7.1", "dotenv": "^16.0.3", "lodash": "^4.17.21", - "mathjs": "^11.7.0" + "mathjs": "^11.7.0", + "winston": "^3.13.0" }, "scripts": { "dryrun": "node scripts/dryrun.mjs", diff --git a/src/autostake/Health.mjs b/src/autostake/Health.mjs index 3521ca46..dffc6785 100644 --- a/src/autostake/Health.mjs +++ b/src/autostake/Health.mjs @@ -1,6 +1,6 @@ import _ from 'lodash' import axios from 'axios' -import { timeStamp } from '../utils/Helpers.mjs' +import { createLogger } from '../utils/Helpers.mjs' class Health { constructor(config, opts) { @@ -22,26 +22,28 @@ class Health { // https://healthchecks.selfhosted.com/ping/{uuid} rather than https://hc-ping.com/{uuid} this.address = this.address + "/ping" } + + this.logger = createLogger('health') } started(...args) { - timeStamp(...args) - if (this.uuid) timeStamp('Starting health', [this.address, this.uuid].join('/')) + this.logger.info(args.join(' ')) + if (this.uuid) this.logger.info('Starting health', { path: [this.address, this.uuid].join('/') }) return this.ping('start', [args.join(' ')]) } success(...args) { - timeStamp(...args) + this.logger.info(args.join(' ')) return this.ping(undefined, [...this.logs, args.join(' ')]) } failed(...args) { - timeStamp(...args) + this.logger.warn(args.join(' ')) return this.ping('fail', [...this.logs, args.join(' ')]) } log(...args) { - timeStamp(...args) + this.logger.info(args.join(' ')) this.logs = [...this.logs, args.join(' ')] } @@ -67,7 +69,7 @@ class Health { this.uuid = res.data.ping_url.split('/')[4] }); } catch (error) { - timeStamp("Health Check creation failed: " + error) + this.logger.error('Health Check creation failed', { error }) } } @@ -78,14 +80,14 @@ class Health { async ping(action, logs) { if (!this.uuid) return - if (this.dryRun) return timeStamp('DRYRUN: Skipping health check ping') + if (this.dryRun) return this.logger.info('DRYRUN: Skipping health check ping') return axios.request({ method: 'POST', url: _.compact([this.address, this.uuid, action]).join('/'), data: logs.join("\n") }).catch(error => { - timeStamp('Health ping failed', error.message) + this.logger.error('Health ping failed', { error }) }) } } diff --git a/src/autostake/NetworkRunner.mjs b/src/autostake/NetworkRunner.mjs index e9725987..6d3e6523 100644 --- a/src/autostake/NetworkRunner.mjs +++ b/src/autostake/NetworkRunner.mjs @@ -3,7 +3,7 @@ import _ from 'lodash' import { add, subtract, bignumber, floor, smaller, smallerEq } from 'mathjs' import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx.js"; -import { coin, timeStamp, mapSync, executeSync, parseGrants } from '../utils/Helpers.mjs' +import { coin, mapSync, executeSync, parseGrants, createLogger } from '../utils/Helpers.mjs' export default class NetworkRunner { constructor(network, operator, signingClient, opts) { @@ -25,6 +25,8 @@ export default class NetworkRunner { this.processed = {} this.errors = {} this.results = [] + + this.logger = createLogger('network_runner').child({ chain: network.name }) } didSucceed() { @@ -52,22 +54,22 @@ export default class NetworkRunner { } setError(address, error) { - timeStamp(address, ':', error) + this.logger.error(error, { address }) this.errors[address] = error } async run(addresses) { try { - timeStamp("Running with options:", this.opts) + this.logger.info('Running with options', this.opts) this.balance = await this.getBalance() || 0 let grantedAddresses = await this.getAddressesFromGrants(addresses) if(grantedAddresses === false){ - timeStamp('All grants query not supported, falling back to checking delegators...') + this.logger.warn('All grants query not supported, falling back to checking delegators...') grantedAddresses = await this.getAddressesFromDelegators(addresses) } - timeStamp("Found", grantedAddresses.length, "addresses with valid grants...") + this.logger.info('Found addresses with valid grants...', { count: grantedAddresses.length}) if (grantedAddresses.length) { await this.autostake(grantedAddresses) } @@ -83,7 +85,7 @@ export default class NetworkRunner { return this.queryClient.getBalance(this.operator.botAddress, this.network.denom, { timeout }) .then( (balance) => { - timeStamp("Bot balance is", balance.amount, balance.denom) + this.logger.info('Fetched bot balance', balance) return balance.amount }, (error) => { @@ -98,21 +100,21 @@ export default class NetworkRunner { let pageSize = this.opts.batchPageSize let allGrants try { - timeStamp('Finding all grants...') + this.logger.info('Finding all grants...') allGrants = await this.queryClient.getGranteeGrants(botAddress, { timeout, pageSize }, (pages) => { - timeStamp("...batch", pages.length) + this.logger.info('...batch', { length: pages.length }) return this.throttleQuery() }) } catch (error) { if(error.response?.status === 501){ return false }else{ - throw new Error('Failed to load grants') + throw new Error(`Failed to load grants: ${error.response?.status}`) } } - if(addresses){ - timeStamp("Checking", addresses.length, "addresses for grants...") - }else{ + if (addresses){ + this.logger.info('Checking addresses for grants...', { length: addresses.length }) + } else { addresses = allGrants.map(grant => grant.granter) } let addressGrants = _.uniq(addresses).map(item => { @@ -122,8 +124,8 @@ export default class NetworkRunner { } async getAddressesFromDelegators(addresses) { - if(!addresses){ - timeStamp('Finding delegators...') + if (!addresses) { + this.logger.info('Finding delegators...') addresses = await this.getDelegations().then(delegations => { return delegations.map(delegation => { if (delegation.balance.amount === 0) return @@ -131,9 +133,9 @@ export default class NetworkRunner { return delegation.delegation.delegator_address }) }) - timeStamp("Checking", addresses.length, "delegators for grants...") - }else{ - timeStamp("Checking", addresses.length, "addresses for grants...") + this.logger.info('Checking delegators for grants...', { length: addresses.length }) + } else { + this.logger.info('Checking addresses for grants...', { length: addresses.length }) } let grantCalls = _.uniq(addresses).map(item => { return async () => { @@ -145,7 +147,7 @@ export default class NetworkRunner { } }) let grantedAddresses = await mapSync(grantCalls, this.opts.batchQueries, (batch, index) => { - timeStamp('...batch', index + 1) + this.logger.info('...batch', { count: index + 1 }) return this.throttleQuery() }) return _.compact(grantedAddresses.flat()) @@ -155,7 +157,7 @@ export default class NetworkRunner { let timeout = this.opts.delegationsTimeout let pageSize = this.opts.batchPageSize return this.queryClient.getAllValidatorDelegations(this.operator.address, pageSize, { timeout }, (pages) => { - timeStamp("...batch", pages.length) + this.logger.info('...batch', { count: pages.length }) return this.throttleQuery() }).catch(error => { throw new Error(`Failed to get delegations: ${error.message || error}`) @@ -192,7 +194,7 @@ export default class NetworkRunner { grantValidators = [] } if (!grantValidators.includes(validatorAddress)) { - timeStamp(delegatorAddress, "Not autostaking for this validator, skipping") + this.logger.info('Not autostaking for this validator, skipping', { delegatorAddress }) return } maxTokens = result.stakeGrant.authorization.max_tokens @@ -218,7 +220,10 @@ export default class NetworkRunner { return } if (withdrawAddress && withdrawAddress !== address) { - timeStamp(address, 'has a different withdraw address:', withdrawAddress) + this.logger.info('Wallet has a different withdraw address', { + withdrawAddress, + address + }) return } @@ -233,29 +238,45 @@ export default class NetworkRunner { let autostakeAmount = floor(totalRewards) if (smaller(bignumber(autostakeAmount), bignumber(this.operator.minimumReward))) { - timeStamp(address, autostakeAmount, this.network.denom, 'reward is too low, skipping') + this.logger.info('Reward is too low, skipping', { + address, + amount: autostakeAmount, + denom: this.network.denom, + }) return } if (grant.maxTokens) { if (smallerEq(grant.maxTokens, 0)) { - timeStamp(address, grant.maxTokens, this.network.denom, 'grant balance is empty, skipping') + this.logger.info('Grant balance is empty, skipping', { + address, + maxTokens: grant.maxToken, + denom: this.network.denom, + }) return } if (smaller(grant.maxTokens, autostakeAmount)) { autostakeAmount = grant.maxTokens - timeStamp(address, grant.maxTokens, this.network.denom, 'grant balance is too low, using remaining') + this.logger.info('Grant balance is too low, using remaining', { + address, + maxTokens: grant.maxToken, + denom: this.network.denom, + }) } } - timeStamp(address, "Can autostake", autostakeAmount, this.network.denom) + this.logger.info('Can autostake', { + address, + amount: autostakeAmount, + denom: this.network.denom, + }) return this.buildRestakeMessage(address, this.operator.address, autostakeAmount, this.network.denom) } async autostake(grantedAddresses) { let batchSize = this.opts.batchTxs - timeStamp('Calculating and autostaking in batches of', batchSize) + this.logger.info('Calculating and autostaking in batches', { batchSize }) const calls = grantedAddresses.map((item, index) => { return async () => { @@ -289,7 +310,7 @@ export default class NetworkRunner { const messages = [...this.messages] const promise = messages[messages.length - 1] || Promise.resolve() const sendTx = promise.then(() => { - timeStamp('Sending batch', messages.length + 1) + this.logger.info('Sending batch', { batch: messages.length + 1 }) return this.sendMessages(addresses, batch) }) this.messages.push(sendTx) @@ -311,24 +332,36 @@ export default class NetworkRunner { if (this.opts.dryRun) { const message = `DRYRUN: Would send ${messages.length} messages using ${gas} gas` - timeStamp(message) + this.logger.info('DRYRUN: Would send messages using gas', { + messages: messages.length, + gas, + }) this.balance = subtract(bignumber(this.balance), bignumber(fee.amount)) return { message, addresses } } else { return await this.signingClient.signAndBroadcast(this.operator.botAddress, [execMsg], gas, memo).then((response) => { const message = `Sent ${messages.length} messages - ${response.transactionHash}` - timeStamp(message) + this.logger.info('Sent messages', { + transactionHash: response.transactionHash, + length: messages.length, + }) this.balance = subtract(bignumber(this.balance), bignumber(fee.amount)) return { message, addresses } }, (error) => { const message = `Failed ${messages.length} messages - ${error.message}` - timeStamp(message) + this.logger.info('Failed messages', { + error: error.message, + length: messages.length, + }) return { message, addresses, error } }) } } catch (error) { const message = `Failed ${messages.length} messages: ${error.message}` - timeStamp(message) + this.logger.info('Failed messages', { + error: error.message, + length: messages.length, + }) return { message, addresses, error } } } diff --git a/src/autostake/index.mjs b/src/autostake/index.mjs index 5be6937b..f5c1ced5 100644 --- a/src/autostake/index.mjs +++ b/src/autostake/index.mjs @@ -12,15 +12,17 @@ import Network from '../utils/Network.mjs' import Wallet from '../utils/Wallet.mjs'; import NetworkRunner from './NetworkRunner.mjs'; import Health from './Health.mjs'; -import { timeStamp, executeSync, overrideNetworks } from '../utils/Helpers.mjs' +import { executeSync, overrideNetworks, createLogger } from '../utils/Helpers.mjs' import EthSigner from '../utils/EthSigner.mjs'; export default function Autostake(mnemonic, opts) { + const mainLogger = createLogger('autostake') + opts = opts || {} let failed = false if (!mnemonic) { - timeStamp('Please provide a MNEMONIC environment variable') + mainLogger.error('Please provide a MNEMONIC environment variable') process.exit() } @@ -124,23 +126,31 @@ export default function Autostake(mnemonic, opts) { throw new Error(`Unable to load network data for ${network.name}`) } - timeStamp('Loaded', network.prettyName) + const logger = mainLogger.child({ chain: network.name }) + + logger.info('Loaded chain', { prettyName: network.prettyName }) const { signer, slip44 } = await getSigner(network) const wallet = new Wallet(network, signer) const botAddress = await wallet.getAddress() - timeStamp('Bot address is', botAddress) + logger.info('Bot address', { address: botAddress }) if (network.slip44 && network.slip44 !== slip44) { - timeStamp("!! You are not using the preferred derivation path !!") - timeStamp("!! You should switch to the correct path unless you have grants. Check the README !!") + logger.warn("!! You are not using the preferred derivation path !!") + logger.warn("!! You should switch to the correct path unless you have grants. Check the README !!") } const operator = network.getOperatorByBotAddress(botAddress) - if (!operator) return timeStamp('Not an operator') + if (!operator) { + logger.info('Not an operator') + return + } - if (!network.authzSupport) return timeStamp('No Authz support') + if (!network.authzSupport) { + logger.info('No Authz support') + return + } await network.connect({ timeout: config.delegationsTimeout || 20000 }) @@ -148,11 +158,11 @@ export default function Autostake(mnemonic, opts) { if (!restUrl) throw new Error('Could not connect to REST API') - timeStamp('Using REST URL', restUrl) + logger.info('Using REST URL', { url: restUrl }) if (usingDirectory) { - timeStamp('You are using public nodes, they may not be reliable. Check the README to use your own') - timeStamp('Delaying briefly and adjusting config to reduce load...') + logger.warn('You are using public nodes, they may not be reliable. Check the README to use your own') + logger.warn('Delaying briefly and adjusting config to reduce load...') config = {...config, batchPageSize: 50, batchQueries: 10, queryThrottle: 2500} await new Promise(r => setTimeout(r, (Math.random() * 31) * 1000)); } @@ -169,9 +179,11 @@ export default function Autostake(mnemonic, opts) { } async function getSigner(network) { + const logger = mainLogger.child({ chain: network.name }) + let slip44 if (network.data.autostake?.correctSlip44 || network.slip44 === 60) { - if (network.slip44 === 60) timeStamp('Found ETH coin type') + if (network.slip44 === 60) logger.info('Found ETH coin type') slip44 = network.slip44 || 118 } else { slip44 = network.data.autostake?.slip44 || 118 @@ -183,7 +195,7 @@ export default function Autostake(mnemonic, opts) { Slip10RawIndex.normal(0), Slip10RawIndex.normal(0), ]; - slip44 != 118 && timeStamp('Using HD Path', pathToString(hdPath)) + slip44 != 118 && logger.info('Using HD Path', { path: pathToString(hdPath) }) let signer = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: network.prefix, @@ -206,7 +218,7 @@ export default function Autostake(mnemonic, opts) { const overrides = overridesData && JSON.parse(overridesData) || {} return overrideNetworks(networks, overrides) } catch (error) { - timeStamp('Failed to parse networks.local.json, check JSON is valid', error.message) + mainLogger.error('Failed to parse networks.local.json, check JSON is valid', { message: error.message }) return networks } } diff --git a/src/utils/Helpers.mjs b/src/utils/Helpers.mjs index 72cbab7a..84946969 100644 --- a/src/utils/Helpers.mjs +++ b/src/utils/Helpers.mjs @@ -2,13 +2,10 @@ import _ from 'lodash' import { format, floor, bignumber } from 'mathjs' import { coin as _coin } from '@cosmjs/stargate' import axios from 'axios' +import winston from 'winston' import { RESTAKE_USER_AGENT } from './constants.mjs' -export function timeStamp(...args) { - console.log('[' + new Date().toISOString().substring(11, 23) + ']', ...args); -} - export function coin(amount, denom){ return _coin(format(floor(amount), {notation: 'fixed'}), denom) } @@ -156,3 +153,31 @@ export async function post(url, body, opts) { } }) } + +export function createLogger(module) { + return winston.createLogger({ + level: 'debug', + defaultMeta: { module }, + format: winston.format.combine( + winston.format.colorize(), + winston.format.prettyPrint(), + winston.format.timestamp(), + winston.format.splat(), + winston.format.printf(({ + timestamp, + level, + message, + label = '', + ...meta + }) => { + const metaFormatted = Object.entries(meta) + .map(([key, value]) => `${key}=${value}`) + .join(' ') + return `[${timestamp}] ${level}: ${message} ${metaFormatted}` + }) + ), + transports: [ + new winston.transports.Console() + ], + }); +}