Browse Source

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

Ali Behjati 3 years ago
parent
commit
5b13be3bbf

+ 262 - 0
third_party/pyth/price-service/package-lock.json

@@ -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",

+ 5 - 0
third_party/pyth/price-service/package.json

@@ -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": {

+ 1 - 0
third_party/pyth/price-service/src/helpers.ts

@@ -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));

+ 23 - 72
third_party/pyth/price-service/src/promClient.ts

@@ -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]
-  });
-  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"
+    buckets: [1, 5, 10, 15, 30, 60, 120, 180],
+    labelNames: ["path", "price_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();
-  }
-
-  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();
+  addResponseTime(path: string, status: number, duration: DurationInMs) {
+    this.apiResponseTimeSummary.observe({
+      path: path,
+      status: status
+    }, duration);
   }
 
-  addApiLatestPriceFeedFreshness(duration: DurationInSec) {
-    this.apiLatestPriceFeedFreshnessHistogram.observe(duration);
+  addApiRequestsPriceFreshness(path: string, priceId: string, duration: DurationInSec) {
+    this.apiRequestsPriceFreshnessHistogram.observe({
+      path: path,
+      price_id: priceId,
+    }, duration);
   }
 }

+ 58 - 51
third_party/pyth/price-service/src/rest.ts

@@ -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>");
-
-    // 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}`);
-
-      if (req.query.id === undefined) {
-        res.status(StatusCodes.BAD_REQUEST).send("No id is provided");
-        return;
-      }
+    endpoints.push("latest_vaa_bytes?id=<price_feed_id>");
 
-      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;
-      }
+    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[];
 
-      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);
+    })
   }
 }