feat(api): add prometheus metrics

Currently we support metrics for the Node.js runtime and HTTP endpoints.
This commit is contained in:
Julian Tölle 2020-11-21 19:55:53 +01:00
parent 9869f0a061
commit e2056b4734
6 changed files with 118 additions and 5 deletions

View file

@ -48,6 +48,14 @@ You can use Sentry to automatically detect and report any exceptions thrown.
- `SENTRY_ENABLED`: **false**, Set to `true` to enable Sentry.
- `SENTRY_DSN`: _Required_, but only if `SENTRY_ENABLED` is `true`. The [DSN](https://docs.sentry.io/product/sentry-basics/dsn-explainer/) for your Sentry project.
#### Prometheus
You can use Prometheus to track various metrics about your Listory deployment.
The metrics will be exposed on the `/api/metrics` endpoint. Make sure that this endpoint is not publicly available in your deployment.
- `PROMETHEUS_ENABLED`: **false**, Set to `true` to enable Prometheus Metrics.
## Development
### Configure Spotify API Access

58
package-lock.json generated
View file

@ -833,6 +833,17 @@
"minimist": "^1.2.0"
}
},
"@digikare/nestjs-prom": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@digikare/nestjs-prom/-/nestjs-prom-1.0.0.tgz",
"integrity": "sha512-iuIdnZlwZ5EVHfcqXRveB+7J/xlv1LAvroW2qxmH9G+NjAHNTaouheDwivyKUfZHaXP9Q3Zp1pN24l3rvZi6+A==",
"requires": {
"@nestjs/testing": "^7.3.2",
"prom-client": "^12.0.0",
"response-time": "^2.3.2",
"url-value-parser": "^2.0.1"
}
},
"@dsherret/to-absolute-glob": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@dsherret/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz",
@ -2198,7 +2209,6 @@
"version": "7.5.2",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-7.5.2.tgz",
"integrity": "sha512-sFkkS6S97F9dFk8Kk5ksBLJGHQZ7n1S1s3BkHxwHwcJqglcpE45wyhoMQ/YzJP5uPyLkgqCQFT2Hst9ndpBV+Q==",
"dev": true,
"requires": {
"optional": "0.1.4",
"tslib": "2.0.3"
@ -2207,8 +2217,7 @@
"tslib": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz",
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==",
"dev": true
"integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ=="
}
}
},
@ -4135,6 +4144,11 @@
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ=="
},
"bintrees": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz",
"integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ="
},
"body-parser": {
"version": "1.19.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
@ -10524,6 +10538,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",
@ -10544,8 +10563,7 @@
"optional": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz",
"integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw==",
"dev": true
"integrity": "sha512-gtvrrCfkE08wKcgXaVwQVgwEQ8vel2dc5DDBn9RLQZ3YtmtkBss6A2HY6BnJH4N/4Ku97Ri/SF8sNWE2225WJw=="
},
"optionator": {
"version": "0.8.3",
@ -11087,6 +11105,14 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"prom-client": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/prom-client/-/prom-client-12.0.0.tgz",
"integrity": "sha512-JbzzHnw0VDwCvoqf8y1WDtq4wSBAbthMB1pcVI/0lzdqHGJI3KBJDXle70XK+c7Iv93Gihqo0a5LlOn+g8+DrQ==",
"requires": {
"tdigest": "^0.1.1"
}
},
"promise-inflight": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
@ -11465,6 +11491,15 @@
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
"dev": true
},
"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"
}
},
"restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@ -12483,6 +12518,14 @@
}
}
},
"tdigest": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz",
"integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=",
"requires": {
"bintrees": "1.0.1"
}
},
"terminal-link": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
@ -13312,6 +13355,11 @@
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
"dev": true
},
"url-value-parser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/url-value-parser/-/url-value-parser-2.0.1.tgz",
"integrity": "sha512-bexECeREBIueboLGM3Y1WaAzQkIn+Tca/Xjmjmfd0S/hFHSCEoFkNh0/D0l9G4K74MkEP/lLFRlYnxX3d68Qgw=="
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",

View file

@ -23,6 +23,7 @@
"test:e2e": "jest --config ./apps/listory/test/jest-e2e.json"
},
"dependencies": {
"@digikare/nestjs-prom": "^1.0.0",
"@hapi/joi": "17.1.1",
"@nestjs/common": "7.5.2",
"@nestjs/config": "0.5.0",

View file

@ -9,6 +9,7 @@ import { DatabaseModule } from "./database/database.module";
import { HealthCheckModule } from "./health-check/health-check.module";
import { ListensModule } from "./listens/listens.module";
import { LoggerModule } from "./logger/logger.module";
import { MetricsModule } from "./metrics/metrics.module";
import { MusicLibraryModule } from "./music-library/music-library.module";
import { ReportsModule } from "./reports/reports.module";
import { SourcesModule } from "./sources/sources.module";
@ -25,6 +26,7 @@ import { UsersModule } from "./users/users.module";
exclude: ["/api*"],
}),
RavenModule,
MetricsModule.forRoot(),
AuthModule,
UsersModule,
SourcesModule,

View file

@ -42,6 +42,9 @@ import { ConfigModule as NestConfigModule } from "@nestjs/config";
is: Joi.valid(true),
then: Joi.required(),
}),
// Prometheus for Metrics (Optional)
PROMETHEUS_ENABLED: Joi.boolean().default(false),
}),
}),
],

View file

@ -0,0 +1,51 @@
import { InboundMiddleware, PromModule } from "@digikare/nestjs-prom";
import { DEFAULT_PROM_OPTIONS } from "@digikare/nestjs-prom/dist/prom.constants";
import {
DynamicModule,
MiddlewareConsumer,
Module,
NestModule,
} from "@nestjs/common";
// Dirty hack because we can not conditionally import modules based on
// injected services and upstream module does not support dynamic configuration
//
// https://github.com/digikare/nestjs-prom/issues/27
const promEnabled = process.env.PROMETHEUS_ENABLED === "true";
@Module({})
export class MetricsModule implements NestModule {
static forRoot(): DynamicModule {
const module = {
imports: [],
providers: [],
};
if (promEnabled) {
const promOptions = {
metricPath: "/api/metrics",
withDefaultsMetrics: true,
withDefaultController: true,
};
module.imports.push(PromModule.forRoot(promOptions));
module.providers.push({
provide: DEFAULT_PROM_OPTIONS,
useValue: promOptions,
});
}
return {
module: MetricsModule,
...module,
};
}
configure(consumer: MiddlewareConsumer) {
if (promEnabled) {
// We register the Middleware ourselves to avoid tracking
// latency for static files served for the frontend.
consumer.apply(InboundMiddleware).forRoutes("/api");
}
}
}