Skip to content

Commit 87fe264

Browse files
committed
feat: support pushgateway
1 parent 1631344 commit 87fe264

File tree

7 files changed

+334
-134
lines changed

7 files changed

+334
-134
lines changed

README.md

Lines changed: 81 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,38 @@ Effortlessly report metrics from anywhere in your codebase without complex setup
1717

1818
</div>
1919

20-
2120
## Installation
2221

2322
```bash
2423
npm install nestjs-metrics-client
2524
```
25+
2626
---
2727

2828
## Overview
2929

30-
`nestjs-metrics-client` is a lightweight, **zero-setup** alternative to [@willsoto/nestjs-prometheus](https://github.com/willsoto/nestjs-prometheus), eliminating the need for dependency injection or extensive configuration.
30+
`nestjs-metrics-client` is a lightweight, **zero-setup** alternative
31+
to [@willsoto/nestjs-prometheus](https://github.com/willsoto/nestjs-prometheus), eliminating the need for dependency
32+
injection or extensive configuration.
3133
Instantly report metrics from anywhere in your application using a global static reporter.
3234

3335
```typescript
3436
import { ReporterService } from 'nestjs-metrics-client';
3537

36-
ReporterService.counter('api_requests_total', { endpoint: '/users' });
38+
ReporterService.counter( 'api_requests_total', { endpoint: '/users' } );
3739
```
40+
3841
---
3942

4043
## Why Choose `nestjs-metrics-client`?
4144

4245
🚀 **No Dependency Injection**
43-
Unlike [@willsoto/nestjs-prometheus](https://github.com/willsoto/nestjs-prometheus), `nestjs-metrics-client` removes the need for cumbersome dependency injection, making your code cleaner and more portable.
46+
Unlike [@willsoto/nestjs-prometheus](https://github.com/willsoto/nestjs-prometheus), `nestjs-metrics-client` removes the
47+
need for cumbersome dependency injection, making your code cleaner and more portable.
4448

4549
🌟 **Effortless Integration**
46-
With zero setup, you can start tracking metrics immediately. No need to configure a service in every file—just use the global `ReporterService`.
50+
With zero setup, you can start tracking metrics immediately. No need to configure a service in every file—just use the
51+
global `ReporterService`.
4752

4853
🎯 **Focus on Simplicity**
4954
Designed for developers who want powerful metrics without the complexity of managing dependencies or boilerplate code.
@@ -60,19 +65,32 @@ Minimal setup required! Just import the `ReporterModule` in your `AppModule`.
6065
import { Module } from "@nestjs/common";
6166
import { ReporterModule } from 'nestjs-metrics-client';
6267

63-
@Module({
64-
imports: [
65-
ReporterModule.forRoot({
66-
// Default metrics are disabled by default, set to true to enable.
67-
defaultMetricsEnabled: true,
68-
defaultLabels: {
69-
app: 'my-app',
70-
environment: 'production',
71-
}
72-
}),
73-
],
74-
})
75-
export class AppModule {}
68+
@Module( {
69+
imports: [
70+
ReporterModule.forRoot( {
71+
// Default metrics are disabled by default, set to true to enable.
72+
defaultMetricsEnabled: true,
73+
defaultLabels: {
74+
app: 'my-app',
75+
environment: 'production',
76+
},
77+
// Optional: Configure Pushgateway for batch job metrics
78+
pushgatewayUrl: 'http://pushgateway:9091',
79+
pushgatewayOptions: {
80+
timeout: 5000,
81+
headers: {
82+
'Custom-Header': 'value'
83+
},
84+
auth: {
85+
username: 'user',
86+
password: 'pass'
87+
}
88+
}
89+
} ),
90+
],
91+
} )
92+
export class AppModule {
93+
}
7694
```
7795

7896
### 2. Report Metrics Anywhere
@@ -85,55 +103,66 @@ import { ReporterService } from 'nestjs-metrics-client';
85103

86104
@Injectable()
87105
export class UserService {
88-
async createUser() {
89-
// Increment user creation counter
90-
ReporterService.counter('users_created_total', {
91-
source: 'api',
92-
user_type: 'standard'
93-
});
94-
95-
// Update active user gauge
96-
ReporterService.gauge('active_users', 42, {
97-
region: 'us-east-1'
98-
});
99-
}
106+
async createUser() {
107+
// Increment user creation counter
108+
ReporterService.counter( 'users_created_total', {
109+
source: 'api',
110+
user_type: 'standard'
111+
} );
112+
113+
// Update active user gauge
114+
ReporterService.gauge( 'active_users', 42, {
115+
region: 'us-east-1'
116+
} );
117+
118+
// Push metrics to Pushgateway
119+
await ReporterService.pushMetrics( 'user_service_job' );
120+
}
100121
}
101122
```
123+
102124
---
103125

104126
## API Reference
105127

106128
The global static service for reporting metrics:
107129

108-
| Method | Description | Parameters |
109-
|--------------|-----------------------------|-------------------------------------------------------------|
110-
| `counter()` | Increment a counter metric | `key: string, labels?: Record<string, string | number>` |
111-
| `gauge()` | Set a gauge value | `key: string, value: number, labels?: Record<string, string | number>` |
112-
| `histogram()`| Record a histogram value | `key: string, value: number, labels?: Record<string, string | number>, buckets?: number[]` |
113-
| `summary()` | Record a summary value | `key: string, value: number, labels?: Record<string, string | number>, percentiles?: number[]` |
130+
| Method | Description | Parameters |
131+
|-----------------|-----------------------------|-------------------------------------------------------------|
132+
| `counter()` | Increment a counter metric | `key: string, labels?: Record<string, string \| number>`
133+
| `gauge()` | Set a gauge value | `key: string, value: number, labels?: Record<string, string \| number>`
134+
| `histogram()` | Record a histogram value | `key: string, value: number, labels?: Record<string, string \| number>, buckets?: number[]`
135+
| `summary()` | Record a summary value | `key: string, value: number, labels?: Record<string, string \| number>, percentiles?: number[]`
136+
| `pushMetrics()` | Push metrics to Pushgateway | `jobName: string` |
114137

115138
### Module Configuration
116139

117140
#### `ReporterModule.forRoot(options)`
118141

119-
| Option | Type | Default | Description |
120-
|-------------------------|----------------------------|------------|-----------------------------------------------|
121-
| `defaultMetricsEnabled` | `boolean` | `false` | Enable collection of default metrics |
122-
| `defaultLabels` | `Record<string, string>` | `{}` | Labels automatically added to all metrics |
142+
| Option | Type | Default | Description |
143+
|-------------------------|--------------------------|-------------|---------------------------------------------|
144+
| `defaultMetricsEnabled` | `boolean` | `false` | Enable collection of default metrics |
145+
| `defaultLabels` | `Record<string, string>` | `{}` | Labels automatically added to all metrics |
146+
| `pushgatewayUrl` | `string` | `undefined` | URL of the Pushgateway server |
147+
| `pushgatewayOptions` | `PushgatewayOptions` | `{}` | Additional options for Pushgateway requests |
123148

124149
#### `ReporterModule.forRootAsync(options)`
125150

126151
Supports dynamic configuration with factory providers:
127152

128153
```typescript
129-
ReporterModule.forRootAsync({
130-
useFactory: () => ({
131-
defaultLabels: {
132-
app: process.env.APP_NAME || 'default-app',
133-
environment: process.env.NODE_ENV || 'development',
134-
},
135-
}),
136-
});
154+
ReporterModule.forRootAsync( {
155+
useFactory: () => ( {
156+
defaultLabels: {
157+
app: process.env.APP_NAME || 'default-app',
158+
environment: process.env.NODE_ENV || 'development',
159+
},
160+
pushgatewayUrl: process.env.PUSHGATEWAY_URL,
161+
pushgatewayOptions: {
162+
timeout: parseInt( process.env.PUSHGATEWAY_TIMEOUT ) || 5000
163+
}
164+
} ),
165+
} );
137166
```
138167

139168
---
@@ -143,6 +172,7 @@ ReporterModule.forRootAsync({
143172
This package uses semantic versioning via commit messages:
144173

145174
### Version Bumping Commits
175+
146176
```bash
147177
# Patch Release (1.0.X)
148178
fix: message # Bug fixes
@@ -158,7 +188,9 @@ BREAKING CHANGE: message # Breaking change anywhere in the commit body
158188
```
159189

160190
### Non-Version Bumping Commits
191+
161192
Only these specific types are allowed:
193+
162194
```bash
163195
build: message # Changes to build system or dependencies
164196
chore: message # Maintenance tasks
@@ -172,6 +204,7 @@ test: message # Adding or correcting tests
172204
Any other prefix will cause the commit to be ignored by semantic-release and won't appear anywhere in release notes.
173205

174206
---
207+
175208
## Contributing
176209

177210
Contributions are welcome! Please check out our [Contributing Guide](CONTRIBUTING.md) to get started.

src/interfaces.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
export interface MetricsConfig {
22
defaultLabels?: Record<string, string>;
33
defaultMetricsEnabled?: boolean;
4+
pushgatewayUrl?: string;
5+
pushgatewayOptions?: {
6+
timeout?: number;
7+
headers?: Record<string, string>;
8+
auth?: {
9+
username: string;
10+
password: string;
11+
};
12+
};
413
}
514

615
export interface ReporterAsyncOptions {
716
imports?: any[];
817
useFactory: ( ...args: any[] )=> Promise<MetricsConfig> | MetricsConfig;
918
inject?: any[];
10-
}
19+
}
20+
21+
export interface PushgatewayResponse {
22+
status: number;
23+
success: boolean;
24+
message?: string;
25+
}

src/metrics/metrics.service.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
import { Inject, Injectable } from '@nestjs/common';
2-
import { Counter, Gauge, Histogram, Registry, Summary } from 'prom-client';
2+
import { Counter, Gauge, Histogram, Pushgateway, Registry, Summary } from 'prom-client';
3+
import { MetricsConfig, PushgatewayResponse } from '../interfaces';
4+
import { CONFIG_OPTIONS } from '../constants';
35

46
@Injectable()
57
export class MetricsService {
6-
private readonly counter: { [ key: string ]: Counter<string> } = {};
7-
private readonly gauge: { [ key: string ]: Gauge<string> } = {};
8-
private readonly histogram: { [ key: string ]: Histogram<string> } = {};
9-
private readonly summary: { [ key: string ]: Summary<string> } = {};
8+
private readonly counter: Record<string, Counter<string>> = {};
9+
private readonly gauge: Record<string, Gauge<string>> = {};
10+
private readonly histogram: Record<string, Histogram<string>> = {};
11+
private readonly summary: Record<string, Summary<string>> = {};
12+
private readonly pushgateway: Pushgateway;
1013

11-
constructor( @Inject( Registry ) private readonly registry: Registry ) {}
1214

15+
constructor(
16+
@Inject( Registry ) private readonly registry: Registry,
17+
@Inject( CONFIG_OPTIONS ) private readonly config: MetricsConfig
18+
) {
19+
if ( this.config.pushgatewayUrl ) {
20+
this.pushgateway = new Pushgateway(
21+
this.config.pushgatewayUrl,
22+
this.config.pushgatewayOptions || [],
23+
this.registry,
24+
);
25+
}
26+
}
27+
1328
public incCounter( key: string, labels?: Record<string, string | number> ): void {
1429
if ( ! this.counter[ key ] ) {
1530
this.counter[ key ] = new Counter( {
@@ -21,7 +36,7 @@ export class MetricsService {
2136
}
2237
this.counter[ key ].inc( labels || {} );
2338
}
24-
39+
2540
public setGauge( key: string, value: number, labels?: Record<string, string | number> ): void {
2641
if ( ! this.gauge[ key ] ) {
2742
this.gauge[ key ] = new Gauge( {
@@ -33,7 +48,7 @@ export class MetricsService {
3348
}
3449
this.gauge[ key ].set( labels || {}, value );
3550
}
36-
51+
3752
public observeHistogram(
3853
key: string,
3954
value: number,
@@ -51,7 +66,7 @@ export class MetricsService {
5166
}
5267
this.histogram[ key ].observe( labels || {}, value );
5368
}
54-
69+
5570
public observeSummary(
5671
key: string,
5772
value: number,
@@ -69,4 +84,25 @@ export class MetricsService {
6984
}
7085
this.summary[ key ].observe( labels || {}, value );
7186
}
87+
88+
public async pushMetrics( jobName: string ): Promise<PushgatewayResponse> {
89+
if ( !this.pushgateway ) {
90+
return {
91+
status: 400,
92+
success: false,
93+
message: 'Pushgateway is not configured'
94+
};
95+
}
96+
97+
try {
98+
await this.pushgateway.pushAdd( { jobName } );
99+
return { status: 200, success: true };
100+
} catch ( error ) {
101+
return {
102+
status: 500,
103+
success: false,
104+
message: error instanceof Error ? error.message : String( error )
105+
};
106+
}
107+
}
72108
}

src/reporter/reporter.module.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { CONFIG_OPTIONS } from '../constants';
1010
@Module( {} )
1111
export class ReporterModule {
1212
static forRoot( config: MetricsConfig = {} ): DynamicModule {
13-
const registry = this.configureRegistry( config );
13+
const registry: Registry = this.configureRegistry( config );
1414

1515
return {
1616
module: ReporterModule,
@@ -19,14 +19,18 @@ export class ReporterModule {
1919
provide: Registry,
2020
useValue: registry
2121
},
22+
{
23+
provide: CONFIG_OPTIONS,
24+
useValue: config
25+
},
2226
MetricsService,
2327
ReporterService
2428
],
2529
controllers: [ MetricsController ],
2630
exports: [ ReporterService ]
2731
};
2832
}
29-
33+
3034
static forRootAsync( options: ReporterAsyncOptions ): DynamicModule {
3135
return {
3236
module: ReporterModule,
@@ -53,7 +57,7 @@ export class ReporterModule {
5357
}
5458

5559
private static configureRegistry( config: MetricsConfig = {} ): Registry {
56-
const registry = new Registry();
60+
const registry: Registry = new Registry();
5761

5862
if ( config.defaultLabels ) {
5963
registry.setDefaultLabels( config.defaultLabels );

src/reporter/reporter.service.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,20 @@ export class ReporterService implements OnApplicationBootstrap {
4444
}
4545
}
4646

47-
private static logError( action: string, key: string, labels: Record<string, string | number> | undefined, error: unknown ): void {
47+
static async pushMetrics( jobName: string ): Promise<void> {
48+
try {
49+
await ReporterService.metricsService.pushMetrics( jobName );
50+
} catch ( e ) {
51+
this.logger.error( `Error pushing metrics: ${ e }` );
52+
}
53+
}
54+
55+
private static logError(
56+
action: string,
57+
key: string,
58+
labels: Record<string, string | number> | undefined,
59+
error: unknown
60+
): void {
4861
this.logger.error( {
4962
message: `Failed to ${action}`,
5063
metric: key,

0 commit comments

Comments
 (0)