Add logging & prom middlewares to Price Service (#191)

This commit is contained in:
Ali Behjati 2022-04-25 15:05:43 +01:00 committed by GitHub
parent e9807ade29
commit 5b13be3bbf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 349 additions and 123 deletions

View File

@ -15,12 +15,17 @@
"@pythnetwork/pyth-sdk-js": "^0.1.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/morgan": "^1.9.3",
"@types/response-time": "^2.3.5",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"ethers": "^5.4.4",
"express": "^4.17.2",
"express-validation": "^4.0.1",
"http-status-codes": "^2.2.0",
"morgan": "^1.10.0",
"prom-client": "^14.0.1",
"response-time": "^2.3.2",
"winston": "^3.3.3"
},
"devDependencies": {
@ -1067,6 +1072,19 @@
"node": ">=6"
}
},
"node_modules/@hapi/hoek": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz",
"integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw=="
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@improbable-eng/grpc-web": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz",
@ -1137,6 +1155,24 @@
"resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.1.0.tgz",
"integrity": "sha512-fsGx2vkXncoIpsrcjx6WY7JN0R74YG/lX2UA1Wz/m6MPgJrde1LHkmikOSdMZUU3KkpWGHT1ZdYoW+Ikv7Nv9g=="
},
"node_modules/@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg=="
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"node_modules/@solana/buffer-layout": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz",
@ -1484,6 +1520,11 @@
"@types/range-parser": "*"
}
},
"node_modules/@types/hapi__joi": {
"version": "16.0.12",
"resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-16.0.12.tgz",
"integrity": "sha512-xJYifuz59jXdWY5JMS15uvA3ycS3nQYOGqoIIE0+fwQ0qI3/4CxBc6RHsOTp6wk9M0NWEdpcTl02lOQOKMifbQ=="
},
"node_modules/@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@ -1499,6 +1540,14 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"node_modules/@types/morgan": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz",
"integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "16.11.27",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz",
@ -1514,6 +1563,15 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"node_modules/@types/response-time": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.5.tgz",
"integrity": "sha512-4ANzp+I3K7sztFFAGPALWBvSl4ayaDSKzI2Bok+WNz+en2eB2Pvk6VCjR47PBXBWOkEg2r4uWpZOlXA5DNINOQ==",
"dependencies": {
"@types/express": "*",
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "1.13.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
@ -1803,6 +1861,22 @@
}
]
},
"node_modules/basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"dependencies": {
"safe-buffer": "5.1.2"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/basic-auth/node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/bech32": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
@ -2440,6 +2514,16 @@
"node": ">= 0.10.0"
}
},
"node_modules/express-validation": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/express-validation/-/express-validation-4.0.1.tgz",
"integrity": "sha512-DA8Nkl82ISo2Vi+eYjcokISz0eom1mcKW8t44Ns4MvsSEeg0WU0Ofoc0uWSZ0tv0J2Z5u7+jJ5nVmXN/tYRpfg==",
"dependencies": {
"@types/express": "^4.17.13",
"@types/hapi__joi": "16.x.x",
"joi": "^17.6.0"
}
},
"node_modules/eyes": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
@ -2801,6 +2885,18 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.48.tgz",
"integrity": "sha512-4kxzqkrpwYtn6okJUcb2lfUu9ilnb3yhUOH6qX3nug8D2DupZ2drIkff2yJzYcNJVl3begnlcaBJ7tqiTTzjnQ=="
},
"node_modules/joi": {
"version": "17.6.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz",
"integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==",
"dependencies": {
"@hapi/hoek": "^9.0.0",
"@hapi/topo": "^5.0.0",
"@sideway/address": "^4.1.3",
"@sideway/formula": "^3.0.0",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/js-base64": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
@ -3034,6 +3130,29 @@
"resolved": "https://registry.npmjs.org/mobile-detect/-/mobile-detect-1.4.5.tgz",
"integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g=="
},
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
"dependencies": {
"basic-auth": "~2.0.1",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-finished": "~2.3.0",
"on-headers": "~1.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/morgan/node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -3105,6 +3224,14 @@
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -3407,6 +3534,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/response-time": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz",
"integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=",
"dependencies": {
"depd": "~1.1.0",
"on-headers": "~1.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -4794,6 +4933,19 @@
"yargs": "^16.2.0"
}
},
"@hapi/hoek": {
"version": "9.2.1",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.2.1.tgz",
"integrity": "sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw=="
},
"@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"requires": {
"@hapi/hoek": "^9.0.0"
}
},
"@improbable-eng/grpc-web": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/@improbable-eng/grpc-web/-/grpc-web-0.14.1.tgz",
@ -4861,6 +5013,24 @@
"resolved": "https://registry.npmjs.org/@pythnetwork/pyth-sdk-js/-/pyth-sdk-js-0.1.0.tgz",
"integrity": "sha512-fsGx2vkXncoIpsrcjx6WY7JN0R74YG/lX2UA1Wz/m6MPgJrde1LHkmikOSdMZUU3KkpWGHT1ZdYoW+Ikv7Nv9g=="
},
"@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
"requires": {
"@hapi/hoek": "^9.0.0"
}
},
"@sideway/formula": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg=="
},
"@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
},
"@solana/buffer-layout": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@solana/buffer-layout/-/buffer-layout-4.0.0.tgz",
@ -5112,6 +5282,11 @@
"@types/range-parser": "*"
}
},
"@types/hapi__joi": {
"version": "16.0.12",
"resolved": "https://registry.npmjs.org/@types/hapi__joi/-/hapi__joi-16.0.12.tgz",
"integrity": "sha512-xJYifuz59jXdWY5JMS15uvA3ycS3nQYOGqoIIE0+fwQ0qI3/4CxBc6RHsOTp6wk9M0NWEdpcTl02lOQOKMifbQ=="
},
"@types/lodash": {
"version": "4.14.182",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz",
@ -5127,6 +5302,14 @@
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"@types/morgan": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.3.tgz",
"integrity": "sha512-BiLcfVqGBZCyNCnCH3F4o2GmDLrpy0HeBVnNlyZG4fo88ZiE9SoiBe3C+2ezuwbjlEyT+PDZ17//TAlRxAn75Q==",
"requires": {
"@types/node": "*"
}
},
"@types/node": {
"version": "16.11.27",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.27.tgz",
@ -5142,6 +5325,15 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"@types/response-time": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@types/response-time/-/response-time-2.3.5.tgz",
"integrity": "sha512-4ANzp+I3K7sztFFAGPALWBvSl4ayaDSKzI2Bok+WNz+en2eB2Pvk6VCjR47PBXBWOkEg2r4uWpZOlXA5DNINOQ==",
"requires": {
"@types/express": "*",
"@types/node": "*"
}
},
"@types/serve-static": {
"version": "1.13.10",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
@ -5395,6 +5587,21 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
"requires": {
"safe-buffer": "5.1.2"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
},
"bech32": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
@ -5936,6 +6143,16 @@
"vary": "~1.1.2"
}
},
"express-validation": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/express-validation/-/express-validation-4.0.1.tgz",
"integrity": "sha512-DA8Nkl82ISo2Vi+eYjcokISz0eom1mcKW8t44Ns4MvsSEeg0WU0Ofoc0uWSZ0tv0J2Z5u7+jJ5nVmXN/tYRpfg==",
"requires": {
"@types/express": "^4.17.13",
"@types/hapi__joi": "16.x.x",
"joi": "^17.6.0"
}
},
"eyes": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz",
@ -6208,6 +6425,18 @@
}
}
},
"joi": {
"version": "17.6.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz",
"integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==",
"requires": {
"@hapi/hoek": "^9.0.0",
"@hapi/topo": "^5.0.0",
"@sideway/address": "^4.1.3",
"@sideway/formula": "^3.0.0",
"@sideway/pinpoint": "^2.0.0"
}
},
"js-base64": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.2.tgz",
@ -6395,6 +6624,25 @@
"resolved": "https://registry.npmjs.org/mobile-detect/-/mobile-detect-1.4.5.tgz",
"integrity": "sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g=="
},
"morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
"integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
"requires": {
"basic-auth": "~2.0.1",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-finished": "~2.3.0",
"on-headers": "~1.0.2"
},
"dependencies": {
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
}
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -6441,6 +6689,11 @@
"ee-first": "1.1.1"
}
},
"on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -6671,6 +6924,15 @@
"supports-preserve-symlinks-flag": "^1.0.0"
}
},
"response-time": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/response-time/-/response-time-2.3.2.tgz",
"integrity": "sha1-/6cbq5UtYvfB1Jt0NDVfvGjf/Fo=",
"requires": {
"depd": "~1.1.0",
"on-headers": "~1.0.1"
}
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",

View File

@ -24,12 +24,17 @@
"@pythnetwork/pyth-sdk-js": "^0.1.0",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/morgan": "^1.9.3",
"@types/response-time": "^2.3.5",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"ethers": "^5.4.4",
"express": "^4.17.2",
"express-validation": "^4.0.1",
"http-status-codes": "^2.2.0",
"morgan": "^1.10.0",
"prom-client": "^14.0.1",
"response-time": "^2.3.2",
"winston": "^3.3.3"
},
"directories": {

View File

@ -1,6 +1,7 @@
// Time in seconds
export type TimestampInSec = number;
export type DurationInSec = number;
export type DurationInMs = number;
export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));

View File

@ -1,6 +1,7 @@
import { stat } from "fs";
import http = require("http");
import client = require("prom-client");
import { DurationInSec } from "./helpers";
import { DurationInMs, DurationInSec } from "./helpers";
import { logger } from "./logging";
// NOTE: To create a new metric:
@ -8,7 +9,7 @@ import { logger } from "./logging";
// 2) Create a method to set the metric to a value (such as `incIncoming` function below)
// 3) Register the metric using `register.registerMetric` function.
const SERVICE_PREFIX = "price_service__";
const SERVICE_PREFIX = "pyth__price_service__";
export class PromClient {
private register = new client.Registry();
@ -19,41 +20,17 @@ export class PromClient {
name: `${SERVICE_PREFIX}vaas_received`,
help: "number of Pyth VAAs received",
});
private apiLatestVaaRequestsCounter = new client.Counter({
name: `${SERVICE_PREFIX}api_latest_vaa_requests_received`,
help: "Number of requests for latest vaa of a price feed"
private apiResponseTimeSummary = new client.Summary({
name: `${SERVICE_PREFIX}api_response_time_ms`,
help: "Response time of a VAA",
labelNames: ["path", "status"]
});
private apiLatestVaaNotFoundResponseCounter = new client.Counter({
name: `${SERVICE_PREFIX}api_latest_vaa_not_found_response`,
help: "Number of not found responses for latest vaa of a price feed request"
});
private apiLatestVaaSuccessResponseCounter = new client.Counter({
name: `${SERVICE_PREFIX}api_latest_vaa_success_response`,
help: "Number of successful responses for latest vaa of a price feed request"
});
private apiLatestVaaFreshnessHistogram = new client.Histogram({
name: `${SERVICE_PREFIX}api_latest_vaa_freshness`,
private apiRequestsPriceFreshnessHistogram = new client.Histogram({
name: `${SERVICE_PREFIX}api_requests_price_freshness_seconds`,
help: "Freshness time of Vaa (time difference of Vaa and request time)",
buckets: [1, 5, 10, 15, 30, 60, 120, 180]
buckets: [1, 5, 10, 15, 30, 60, 120, 180],
labelNames: ["path", "price_id"]
});
private apiLatestPriceFeedRequestsCounter = new client.Counter({
name: `${SERVICE_PREFIX}api_latest_price_feed_requests_received`,
help: "Number of requests for latest Price Feed of a price feed id"
});
private apiLatestPriceFeedNotFoundResponseCounter = new client.Counter({
name: `${SERVICE_PREFIX}api_latest_price_feed_not_found_response`,
help: "Number of not found responses for latest Price Feed of a price feed id"
});
private apiLatestPriceFeedSuccessResponseCounter = new client.Counter({
name: `${SERVICE_PREFIX}api_latest_price_feed_success_response`,
help: "Number of successful responses for latest vaa of a price feed id"
});
private apiLatestPriceFeedFreshnessHistogram = new client.Histogram({
name: `${SERVICE_PREFIX}api_latest_price_feed_freshness`,
help: "Freshness time of Vaa (time difference of retrieval time and request time)",
buckets: [1, 5, 10, 15, 30, 60, 120, 180]
});
// End metrics
private server = http.createServer(async (req, res) => {
@ -72,16 +49,8 @@ export class PromClient {
this.collectDefaultMetrics({ register: this.register, prefix: SERVICE_PREFIX });
// Register each metric
this.register.registerMetric(this.receivedVaaCounter);
this.register.registerMetric(this.apiLatestVaaRequestsCounter);
this.register.registerMetric(this.apiLatestVaaNotFoundResponseCounter);
this.register.registerMetric(this.apiLatestVaaSuccessResponseCounter);
this.register.registerMetric(this.apiLatestVaaFreshnessHistogram);
this.register.registerMetric(this.apiLatestPriceFeedRequestsCounter);
this.register.registerMetric(this.apiLatestPriceFeedNotFoundResponseCounter);
this.register.registerMetric(this.apiLatestPriceFeedSuccessResponseCounter);
this.register.registerMetric(this.apiLatestPriceFeedFreshnessHistogram);
this.register.registerMetric(this.apiResponseTimeSummary)
this.register.registerMetric(this.apiRequestsPriceFreshnessHistogram);
// End registering metric
logger.info("prometheus client listening on port " + config.port);
@ -92,35 +61,17 @@ export class PromClient {
this.receivedVaaCounter.inc();
}
incApiLatestVaaRequests() {
this.apiLatestVaaRequestsCounter.inc();
addResponseTime(path: string, status: number, duration: DurationInMs) {
this.apiResponseTimeSummary.observe({
path: path,
status: status
}, duration);
}
incApiLatestVaaNotFoundResponse() {
this.apiLatestVaaNotFoundResponseCounter.inc();
}
incApiLatestVaaSuccessResponse() {
this.apiLatestVaaSuccessResponseCounter.inc();
}
addApiLatestVaaFreshness(duration: DurationInSec) {
this.apiLatestVaaFreshnessHistogram.observe(duration);
}
incApiLatestPriceFeedRequests() {
this.apiLatestPriceFeedRequestsCounter.inc();
}
incApiLatestPriceFeedNotFoundResponse() {
this.apiLatestPriceFeedNotFoundResponseCounter.inc();
}
incApiLatestPriceFeedSuccessResponse() {
this.apiLatestPriceFeedSuccessResponseCounter.inc();
}
addApiLatestPriceFeedFreshness(duration: DurationInSec) {
this.apiLatestPriceFeedFreshnessHistogram.observe(duration);
addApiRequestsPriceFreshness(path: string, priceId: string, duration: DurationInSec) {
this.apiRequestsPriceFreshnessHistogram.observe({
path: path,
price_id: priceId,
}, duration);
}
}

View File

@ -1,11 +1,17 @@
import express from "express";
import cors from "cors";
import { Request, Response } from "express";
import morgan from "morgan";
import responseTime from "response-time";
import { Request, Response, NextFunction } from "express";
import { PriceFeedPriceInfo } from "./listen";
import { logger } from "./logging";
import { PromClient } from "./promClient";
import { DurationInSec } from "./helpers";
import { DurationInMs, DurationInSec } from "./helpers";
import { StatusCodes } from "http-status-codes";
import { validate, ValidationError, Joi, schema } from "express-validation";
const MORGAN_LOG_FORMAT = ':remote-addr - :remote-user ":method :url HTTP/:http-version"' +
' :status :res[content-length] :response-time ms ":referrer" ":user-agent"';
export class RestAPI {
private port: number;
@ -13,7 +19,7 @@ export class RestAPI {
private isReady: (() => boolean) | undefined;
private promClient: PromClient | undefined;
constructor(config: { port: number; },
constructor(config: { port: number; },
priceFeedVaaInfo: PriceFeedPriceInfo,
isReady?: () => boolean,
promClient?: PromClient) {
@ -28,82 +34,75 @@ export class RestAPI {
const app = express();
app.use(cors());
const winstonStream = {
write: (text: string) => {
logger.info(text);
}
};
app.use(morgan(MORGAN_LOG_FORMAT, { stream: winstonStream }));
app.use(responseTime((req: Request, res: Response, time: DurationInMs) => {
if (res.statusCode !== StatusCodes.NOT_FOUND) {
this.promClient?.addResponseTime(req.path, res.statusCode, time);
}
}))
app.listen(this.port, () =>
logger.debug("listening on REST port " + this.port)
);
let endpoints: string[] = [];
const latestVaaBytesInputSchema: schema = {
query: Joi.object({
id: Joi.string().regex(/^[a-f0-9]{64}$/)
})
}
app.get("/latest_vaa_bytes", validate(latestVaaBytesInputSchema), (req: Request, res: Response) => {
let priceId = req.query.id as string;
app.get("/latest_vaa_bytes/:price_feed_id", (req: Request, res: Response) => {
this.promClient?.incApiLatestVaaRequests();
logger.info(`Received latest_vaa_bytes request for ${req.params.price_feed_id}`)
let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(req.params.price_feed_id);
let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(priceId);
if (latestPriceInfo === undefined) {
this.promClient?.incApiLatestVaaNotFoundResponse();
res.sendStatus(StatusCodes.NOT_FOUND);
res.sendStatus(StatusCodes.BAD_REQUEST);
return;
}
this.promClient?.incApiLatestVaaSuccessResponse();
const freshness: DurationInSec = (new Date).getTime()/1000 - latestPriceInfo.receiveTime;
this.promClient?.addApiLatestVaaFreshness(freshness);
const freshness: DurationInSec = (new Date).getTime() / 1000 - latestPriceInfo.receiveTime;
this.promClient?.addApiRequestsPriceFreshness(req.path, priceId, freshness);
res.send(latestPriceInfo.vaaBytes);
});
endpoints.push("latest_vaa_bytes/<price_feed_id>");
endpoints.push("latest_vaa_bytes?id=<price_feed_id>");
// It will be called with query param `id` such as: `/latest_price_feed?id=xyz&id=abc
app.get("/latest_price_feed", (req: Request, res: Response) => {
this.promClient?.incApiLatestPriceFeedRequests();
logger.info(`Received latest_price_feed request for query: ${req.query}`);
const latestPriceFeedInputSchema: schema = {
query: Joi.object({
id: Joi.array().items(Joi.string().regex(/^[a-f0-9]{64}$/))
})
}
app.get("/latest_price_feed", validate(latestPriceFeedInputSchema), (req: Request, res: Response) => {
let priceIds = req.query.id as string[];
if (req.query.id === undefined) {
res.status(StatusCodes.BAD_REQUEST).send("No id is provided");
return;
}
let priceIds: string[] = [];
if (typeof(req.query.id) === "string") {
priceIds.push(req.query.id);
} else if (Array.isArray(req.query.id)) {
for (let entry of req.query.id) {
if (typeof(entry) === "string") {
priceIds.push(entry);
} else {
res.status(StatusCodes.BAD_REQUEST).send("id is expected to be a hex string or an array of hex strings");
return;
}
}
} else {
res.status(StatusCodes.BAD_REQUEST).send("id is expected to be a hex string or an array of hex strings");
return;
}
let responseJson = []
let responseJson = [];
for (let id of priceIds) {
let latestPriceInfo = this.priceFeedVaaInfo.getLatestPriceInfo(id);
if (latestPriceInfo === undefined) {
this.promClient?.incApiLatestPriceFeedNotFoundResponse();
res.status(StatusCodes.NOT_FOUND).send(`Price Feed with id ${id} not found`);
res.status(StatusCodes.BAD_REQUEST).send(`Price Feed with id ${id} not found`);
return;
}
const freshness: DurationInSec = (new Date).getTime()/1000 - latestPriceInfo.receiveTime;
this.promClient?.addApiLatestPriceFeedFreshness(freshness);
const freshness: DurationInSec = (new Date).getTime() / 1000 - latestPriceInfo.receiveTime;
this.promClient?.addApiRequestsPriceFreshness(req.path, id, freshness);
responseJson.push(latestPriceInfo.priceFeed.toJson());
}
this.promClient?.incApiLatestPriceFeedSuccessResponse();
res.json(responseJson);
});
endpoints.push("latest_price_feed/<price_feed_id>");
endpoints.push("latest_price_feed?id[]=<price_feed_id>&id[]=<price_feed_id_2>&..");
app.get("/ready", (_, res: Response) => {
@ -124,5 +123,13 @@ export class RestAPI {
app.get("/", (_, res: Response) =>
res.json(endpoints)
);
app.use(function(err: any, _: Request, res: Response, next: NextFunction) {
if (err instanceof ValidationError) {
return res.status(err.statusCode).json(err);
}
return next(err);
})
}
}