Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add last fetch and last update #190

Merged
merged 2 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,15 @@ Connection: keep-alive
Keep-Alive: timeout=5
```

**Prometheus endpoint**

The proxy has a prometheus metrics endpoint available at http://localhost:3000/proxy/internal-backstage/prometheus
With the following metrics
* `unleash_proxy_up` a [counter](https://prometheus.io/docs/concepts/metric_types/#counter) which is set to 1 when proxy is running
* `last_metrics_update_epoch_timestamp_ms` a [gauge](https://prometheus.io/docs/concepts/metric_types/#gauge) set to the epoch timestamp in ms when the proxy last received a feature update
* `last_metrics_fetch_epoch_timestamp_ms` a [gauge](https://prometheus.io/docs/concepts/metric_types/#gauge) set to the epoch timestamp in ms when the proxy last checked for updates


### Run with Node.js:

**STEP 1: Install dependency**
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"express": "^4.18.2",
"json-schema-to-ts": "^2.3.0",
"openapi-types": "^11.0.0",
"prom-client": "^15.1.3",
"qs": "^6.9.7",
"type-is": "^1.6.18",
"unleash-client": "^6.1.1"
Expand All @@ -54,7 +55,7 @@
"@types/compression": "^1.7.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/jest": "^27.5.0",
"@types/jest": "^29.5.12",
"@types/node": "18.7.14",
"@types/supertest": "^2.0.12",
"@types/type-is": "^1.6.3",
Expand All @@ -67,12 +68,12 @@
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^28.0.3",
"jest": "^29.7.0",
"prettier": "^2.6.2",
"semver": "^7.3.7",
"supertest": "^6.2.3",
"ts-jest": "^28.0.1",
"ts-node-dev": "^1.1.8",
"ts-jest": "^29.2.4",
"ts-node-dev": "^2.0.0",
"typescript": "^4.6.4"
},
"prettier": {
Expand Down
11 changes: 10 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import EventEmitter from 'events';
import { Context, Unleash, Variant } from 'unleash-client';
import { Context, Unleash, UnleashEvents, Variant } from 'unleash-client';
import { FeatureInterface } from 'unleash-client/lib/feature';
import Metrics from 'unleash-client/lib/metrics';
import { getDefaultVariant } from 'unleash-client/lib/variant';
import { IProxyConfig } from './config';
import { Logger } from './logger';
import { lastMetricsFetch, lastMetricsUpdate } from './prom-metrics';

export type FeatureToggleStatus = {
name: string;
Expand Down Expand Up @@ -77,6 +78,14 @@ class Client extends EventEmitter implements IClient {
this.ready = true;
this.metrics.start();
});
this.unleash.on(UnleashEvents.Unchanged, () => {
lastMetricsFetch.set(new Date().getTime());
});
this.unleash.on(UnleashEvents.Changed, () => {
const updatedAt = new Date().getTime();
lastMetricsFetch.set(updatedAt);
lastMetricsUpdate.set(updatedAt);
});
}

setUnleashApiToken(unleashApiToken: string): void {
Expand Down
64 changes: 64 additions & 0 deletions src/prom-metrics/createCounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Counter as PromCounter, type CounterConfiguration } from 'prom-client';

/**
* A wrapped instance of prom-client's Counter, overriding some of its methods for enhanced functionality and type-safety.
*/
export type Counter<T extends string = string> = {
counter: PromCounter<T>;
labels: (labels: Record<T, string | number>) => PromCounter.Internal;
inc: (value?: number | undefined) => void;
increment: (labels: Record<T, string | number>, value?: number) => void;
};

/**
* Creates a wrapped instance of prom-client's Counter, overriding some of its methods for enhanced functionality and type-safety.
*
* @param options - The configuration options for the Counter, as defined in prom-client's CounterConfiguration.
* See prom-client documentation for detailed options: https://github.com/siimon/prom-client#counter
* @returns An object containing the wrapped Counter instance and custom methods.
*/
export const createCounter = <T extends string>(
options: CounterConfiguration<T>,
): Counter<T> => {
/**
* The underlying instance of prom-client's Counter.
*/
const counter = new PromCounter<T>(options);

/**
* Applies given labels to the counter. Labels are key-value pairs.
* This method wraps the original Counter's labels method for additional type-safety, requiring all configured labels to be specified.
*
* @param labels - An object where keys are label names and values are the label values.
* @returns The Counter instance with the applied labels, allowing for method chaining.
*/
// eslint-disable-next-line @typescript-eslint/no-shadow
const labels = (labels: Record<T, string | number>) =>
counter.labels(labels);

/**
* Increments the counter by a specified value or by 1 if no value is provided.
* Wraps the original Counter's inc method.
*
* @param value - (Optional) The value to increment the counter by. If not provided, defaults to 1.
*/
const inc = (value?: number | undefined) => counter.inc(value);

/**
* A convenience method that combines setting labels and incrementing the counter.
* Useful for incrementing with labels in a single call.
*
* @param labels - An object where keys are label names and values are the label values.
* @param value - (Optional) The value to increment the counter by. If not provided, defaults to 1.
*/
// eslint-disable-next-line @typescript-eslint/no-shadow
const increment = (labels: Record<T, string | number>, value?: number) =>
counter.labels(labels).inc(value);

return {
counter,
labels,
inc,
increment,
};
};
51 changes: 51 additions & 0 deletions src/prom-metrics/createGauge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Gauge as PromGauge, type GaugeConfiguration } from 'prom-client';

/**
* A wrapped instance of prom-client's Gauge, overriding some of its methods for enhanced functionality and type-safety.
*/
export type Gauge<T extends string = string> = {
gauge: PromGauge<T>;
labels: (labels: Record<T, string | number>) => PromGauge.Internal<T>;
reset: () => void;
set: (value: number) => void;
};

export const createGauge = <T extends string>(
options: GaugeConfiguration<T>,
): Gauge<T> => {
/**
* The underlying instance of prom-client's Gauge.
*/
const gauge = new PromGauge(options);

/**
* Applies given labels to the gauge. Labels are key-value pairs.
* This method wraps the original Gauge's labels method for additional type-safety, requiring all configured labels to be specified.
*
* @param labels - An object where keys are label names and values are the label values.
* @returns The Gauge instance with the applied labels, allowing for method chaining.
*/
// eslint-disable-next-line @typescript-eslint/no-shadow
const labels = (labels: Record<T, string | number>) => gauge.labels(labels);

/**
* Resets the gauge value.
* Wraps the original Gauge's reset method.
*/
const reset = () => gauge.reset();

/**
* Sets the gauge to a specified value.
* Wraps the original Gauge's set method.
*
* @param value - The value to set the gauge to.
*/
const set = (value: number) => gauge.set(value);

return {
gauge,
labels,
reset,
set,
};
};
20 changes: 20 additions & 0 deletions src/prom-metrics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createGauge, type Gauge } from './createGauge';
import { createCounter } from './createCounter';

export * from './createCounter';
export * from './createGauge';

export const lastMetricsUpdate: Gauge = createGauge({
name: 'last_metrics_update_epoch_timestamp_ms',
help: 'An epoch timestamp (in milliseconds) set to when our unleash-client last got an update from upstream Unleash',
});

export const lastMetricsFetch: Gauge = createGauge({
name: 'last_metrics_fetch_epoch_timestamp_ms',
help: 'An epoch timestamp (in milliseconds) set to when our unleash-client last checked (regardless if there was an update or not)',
});

createCounter({
name: 'unleash_proxy_up',
help: 'Indication that the service is up.',
}).inc(1);
Loading
Loading