@onebun/metrics 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +13 -3
- package/src/docs-examples.test.ts +315 -0
package/README.md
CHANGED
|
@@ -242,4 +242,8 @@ The metrics endpoint is compatible with:
|
|
|
242
242
|
- **Prometheus** - For scraping and storage
|
|
243
243
|
- **Grafana** - For visualization and dashboards
|
|
244
244
|
- **AlertManager** - For alerting based on metrics
|
|
245
|
-
- Any other Prometheus-compatible monitoring solution
|
|
245
|
+
- Any other Prometheus-compatible monitoring solution
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
[LGPL-3.0](../../LICENSE)
|
package/package.json
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebun/metrics",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Prometheus-compatible metrics
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Prometheus-compatible metrics for OneBun framework - HTTP, system, and custom metrics",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"author": "RemRyahirev",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"onebun",
|
|
9
|
+
"metrics",
|
|
10
|
+
"prometheus",
|
|
11
|
+
"monitoring",
|
|
12
|
+
"observability",
|
|
13
|
+
"bun",
|
|
14
|
+
"typescript",
|
|
15
|
+
"effect"
|
|
16
|
+
],
|
|
7
17
|
"repository": {
|
|
8
18
|
"type": "git",
|
|
9
19
|
"url": "git+https://github.com/RemRyahirev/onebun.git",
|
|
@@ -30,7 +40,7 @@
|
|
|
30
40
|
"dependencies": {
|
|
31
41
|
"effect": "^3.13.10",
|
|
32
42
|
"prom-client": "^15.1.3",
|
|
33
|
-
"@onebun/requests": "
|
|
43
|
+
"@onebun/requests": "workspace:^"
|
|
34
44
|
},
|
|
35
45
|
"devDependencies": {
|
|
36
46
|
"bun-types": "1.2.2"
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation Examples Tests for @onebun/metrics
|
|
3
|
+
*
|
|
4
|
+
* This file tests code examples from:
|
|
5
|
+
* - packages/metrics/README.md
|
|
6
|
+
* - docs/api/metrics.md
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
describe,
|
|
11
|
+
it,
|
|
12
|
+
expect,
|
|
13
|
+
beforeEach,
|
|
14
|
+
afterEach,
|
|
15
|
+
} from 'bun:test';
|
|
16
|
+
import { Effect } from 'effect';
|
|
17
|
+
|
|
18
|
+
import type { MetricsService as MetricsServiceInterface } from './metrics.service';
|
|
19
|
+
|
|
20
|
+
import {
|
|
21
|
+
MeasureTime,
|
|
22
|
+
CountCalls,
|
|
23
|
+
MetricsMiddleware,
|
|
24
|
+
createMetricsService,
|
|
25
|
+
} from './';
|
|
26
|
+
|
|
27
|
+
// Helper to create metrics service for tests
|
|
28
|
+
const getMetricsService = (): MetricsServiceInterface => {
|
|
29
|
+
return Effect.runSync(createMetricsService({ prefix: 'test_' }));
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('Metrics README Examples', () => {
|
|
33
|
+
describe('Method Decorators (README)', () => {
|
|
34
|
+
it('should have @MeasureTime decorator available', () => {
|
|
35
|
+
// From README: Method Decorators - @MeasureTime
|
|
36
|
+
expect(MeasureTime).toBeDefined();
|
|
37
|
+
expect(typeof MeasureTime).toBe('function');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should have @CountCalls decorator available', () => {
|
|
41
|
+
// From README: Method Decorators - @CountCalls
|
|
42
|
+
expect(CountCalls).toBeDefined();
|
|
43
|
+
expect(typeof CountCalls).toBe('function');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should use decorators on controller methods', () => {
|
|
47
|
+
// From README: Method Decorators example
|
|
48
|
+
class ApiController {
|
|
49
|
+
@MeasureTime('heavy_operation_duration')
|
|
50
|
+
@CountCalls('heavy_operation_calls')
|
|
51
|
+
async heavyOperation(): Promise<unknown> {
|
|
52
|
+
// This method's execution time and call count will be automatically tracked
|
|
53
|
+
return { success: true };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
expect(ApiController).toBeDefined();
|
|
58
|
+
expect(typeof new ApiController().heavyOperation).toBe('function');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Configuration Options (README)', () => {
|
|
63
|
+
it('should define valid metrics options', () => {
|
|
64
|
+
// From README: Configuration Options
|
|
65
|
+
const metricsOptions = {
|
|
66
|
+
// Enable/disable metrics collection (default: true)
|
|
67
|
+
enabled: true,
|
|
68
|
+
|
|
69
|
+
// HTTP path for metrics endpoint (default: '/metrics')
|
|
70
|
+
path: '/metrics',
|
|
71
|
+
|
|
72
|
+
// Default labels for all metrics
|
|
73
|
+
defaultLabels: {
|
|
74
|
+
service: 'my-service',
|
|
75
|
+
environment: 'development',
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
// Enable HTTP request metrics (default: true)
|
|
79
|
+
collectHttpMetrics: true,
|
|
80
|
+
|
|
81
|
+
// Enable system metrics (default: true)
|
|
82
|
+
collectSystemMetrics: true,
|
|
83
|
+
|
|
84
|
+
// Enable GC metrics (default: true)
|
|
85
|
+
collectGcMetrics: true,
|
|
86
|
+
|
|
87
|
+
// System metrics collection interval in ms (default: 5000)
|
|
88
|
+
systemMetricsInterval: 5000,
|
|
89
|
+
|
|
90
|
+
// Metric name prefix (default: 'onebun_')
|
|
91
|
+
prefix: 'myapp_',
|
|
92
|
+
|
|
93
|
+
// HTTP duration histogram buckets
|
|
94
|
+
httpDurationBuckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5, 10],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
expect(metricsOptions.enabled).toBe(true);
|
|
98
|
+
expect(metricsOptions.path).toBe('/metrics');
|
|
99
|
+
expect(metricsOptions.prefix).toBe('myapp_');
|
|
100
|
+
expect(metricsOptions.systemMetricsInterval).toBe(5000);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('Metrics API Documentation Examples', () => {
|
|
106
|
+
let metricsService: MetricsServiceInterface;
|
|
107
|
+
|
|
108
|
+
beforeEach(() => {
|
|
109
|
+
metricsService = getMetricsService();
|
|
110
|
+
metricsService.clear();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
metricsService.clear();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('MetricsService (docs/api/metrics.md)', () => {
|
|
118
|
+
it('should create metrics service instance', () => {
|
|
119
|
+
// From docs: MetricsService usage
|
|
120
|
+
// Use createMetricsService() to get an Effect that yields the service
|
|
121
|
+
const service = Effect.runSync(createMetricsService());
|
|
122
|
+
|
|
123
|
+
expect(service).toBeDefined();
|
|
124
|
+
expect(typeof service.createCounter).toBe('function');
|
|
125
|
+
expect(typeof service.createGauge).toBe('function');
|
|
126
|
+
expect(typeof service.createHistogram).toBe('function');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('Counter (docs/api/metrics.md)', () => {
|
|
131
|
+
it('should create and use counter', async () => {
|
|
132
|
+
// From docs: Counter example
|
|
133
|
+
// createCounter returns a prom-client Counter object
|
|
134
|
+
const requestsCounter = metricsService.createCounter({
|
|
135
|
+
name: 'requests_total',
|
|
136
|
+
help: 'Total requests',
|
|
137
|
+
labelNames: ['method', 'endpoint'],
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Increment counter using prom-client API
|
|
141
|
+
requestsCounter.inc({ method: 'GET', endpoint: '/api/users' });
|
|
142
|
+
|
|
143
|
+
const metrics = await metricsService.getMetrics();
|
|
144
|
+
expect(metrics).toContain('requests_total');
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('Gauge (docs/api/metrics.md)', () => {
|
|
149
|
+
it('should create and use gauge', async () => {
|
|
150
|
+
// From docs: Gauge example
|
|
151
|
+
// createGauge returns a prom-client Gauge object
|
|
152
|
+
const connectionsGauge = metricsService.createGauge({
|
|
153
|
+
name: 'active_connections',
|
|
154
|
+
help: 'Active connections',
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Use prom-client Gauge API
|
|
158
|
+
connectionsGauge.set(42);
|
|
159
|
+
connectionsGauge.inc();
|
|
160
|
+
connectionsGauge.dec();
|
|
161
|
+
|
|
162
|
+
const metrics = await metricsService.getMetrics();
|
|
163
|
+
expect(metrics).toContain('active_connections');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('Histogram (docs/api/metrics.md)', () => {
|
|
168
|
+
it('should create and use histogram', async () => {
|
|
169
|
+
// From docs: Histogram example
|
|
170
|
+
// createHistogram returns a prom-client Histogram object
|
|
171
|
+
const responseTimeHistogram = metricsService.createHistogram({
|
|
172
|
+
name: 'response_time_seconds',
|
|
173
|
+
help: 'Response time in seconds',
|
|
174
|
+
buckets: [0.1, 0.5, 1, 2, 5],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Observe value using prom-client API
|
|
178
|
+
responseTimeHistogram.observe(1.23);
|
|
179
|
+
|
|
180
|
+
const metrics = await metricsService.getMetrics();
|
|
181
|
+
expect(metrics).toContain('response_time_seconds');
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
describe('Custom Metrics in Services (README)', () => {
|
|
186
|
+
it('should register custom metrics pattern', async () => {
|
|
187
|
+
// From README: Custom Metrics in Services example
|
|
188
|
+
// Create custom metrics - returns prom-client metric objects
|
|
189
|
+
const loginsCounter = metricsService.createCounter({
|
|
190
|
+
name: 'user_logins_total',
|
|
191
|
+
help: 'Total number of user logins',
|
|
192
|
+
labelNames: ['method', 'status'],
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const activeUsersGauge = metricsService.createGauge({
|
|
196
|
+
name: 'active_users',
|
|
197
|
+
help: 'Number of currently active users',
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Simulate login using prom-client API
|
|
201
|
+
loginsCounter.inc({ method: 'password', status: 'success' });
|
|
202
|
+
activeUsersGauge.inc();
|
|
203
|
+
|
|
204
|
+
const metrics = await metricsService.getMetrics();
|
|
205
|
+
expect(metrics).toContain('user_logins_total');
|
|
206
|
+
expect(metrics).toContain('active_users');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('Service Metrics Pattern (docs/api/metrics.md)', () => {
|
|
211
|
+
it('should implement payment service metrics pattern', async () => {
|
|
212
|
+
// From docs: Service Metrics Pattern
|
|
213
|
+
// Register custom metrics on service init
|
|
214
|
+
const paymentsCounter = metricsService.createCounter({
|
|
215
|
+
name: 'payments_processed_total',
|
|
216
|
+
help: 'Total number of payments processed',
|
|
217
|
+
labelNames: ['status', 'method'],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const processingHistogram = metricsService.createHistogram({
|
|
221
|
+
name: 'payment_processing_seconds',
|
|
222
|
+
help: 'Payment processing duration',
|
|
223
|
+
buckets: [0.1, 0.5, 1, 2, 5, 10],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const queueGauge = metricsService.createGauge({
|
|
227
|
+
name: 'payment_queue_size',
|
|
228
|
+
help: 'Current payment queue size',
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Simulate payment processing
|
|
232
|
+
const startTime = Date.now();
|
|
233
|
+
|
|
234
|
+
// Record success using prom-client API
|
|
235
|
+
paymentsCounter.inc({ status: 'success', method: 'credit_card' });
|
|
236
|
+
|
|
237
|
+
// Record duration
|
|
238
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
239
|
+
processingHistogram.observe(duration);
|
|
240
|
+
|
|
241
|
+
// Set queue size
|
|
242
|
+
queueGauge.set(5);
|
|
243
|
+
|
|
244
|
+
const metrics = await metricsService.getMetrics();
|
|
245
|
+
expect(metrics).toContain('payments_processed_total');
|
|
246
|
+
expect(metrics).toContain('payment_processing_seconds');
|
|
247
|
+
expect(metrics).toContain('payment_queue_size');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('Built-in Metrics Format (docs/api/metrics.md)', () => {
|
|
252
|
+
it('should describe HTTP metrics format', () => {
|
|
253
|
+
// From docs: Built-in Metrics - HTTP Metrics
|
|
254
|
+
// These are the metrics that would be automatically collected
|
|
255
|
+
const expectedMetrics = [
|
|
256
|
+
'http_request_duration_seconds_bucket',
|
|
257
|
+
'http_request_duration_seconds_sum',
|
|
258
|
+
'http_request_duration_seconds_count',
|
|
259
|
+
'http_requests_total',
|
|
260
|
+
];
|
|
261
|
+
|
|
262
|
+
// Just verifying the expected metric names
|
|
263
|
+
expectedMetrics.forEach((metric) => {
|
|
264
|
+
expect(typeof metric).toBe('string');
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should describe System metrics format', () => {
|
|
269
|
+
// From docs: Built-in Metrics - System Metrics
|
|
270
|
+
const expectedMetrics = [
|
|
271
|
+
'process_cpu_seconds_total',
|
|
272
|
+
'process_cpu_user_seconds_total',
|
|
273
|
+
'process_cpu_system_seconds_total',
|
|
274
|
+
'process_memory_bytes',
|
|
275
|
+
'nodejs_eventloop_lag_seconds',
|
|
276
|
+
'nodejs_active_handles_total',
|
|
277
|
+
'nodejs_active_requests_total',
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
// Just verifying the expected metric names
|
|
281
|
+
expectedMetrics.forEach((metric) => {
|
|
282
|
+
expect(typeof metric).toBe('string');
|
|
283
|
+
});
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe('Middleware (docs/api/metrics.md)', () => {
|
|
288
|
+
it('should have MetricsMiddleware class', () => {
|
|
289
|
+
expect(MetricsMiddleware).toBeDefined();
|
|
290
|
+
expect(typeof MetricsMiddleware).toBe('function');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
describe('Prometheus Format (docs/api/metrics.md)', () => {
|
|
296
|
+
it('should produce Prometheus-compatible output', async () => {
|
|
297
|
+
const service = getMetricsService();
|
|
298
|
+
service.clear();
|
|
299
|
+
|
|
300
|
+
const counter = service.createCounter({
|
|
301
|
+
name: 'test_counter',
|
|
302
|
+
help: 'Test counter',
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Use prom-client API to increment
|
|
306
|
+
counter.inc();
|
|
307
|
+
|
|
308
|
+
const metrics = await service.getMetrics();
|
|
309
|
+
|
|
310
|
+
// Prometheus format should contain HELP and TYPE comments
|
|
311
|
+
expect(metrics).toContain('# HELP');
|
|
312
|
+
expect(metrics).toContain('# TYPE');
|
|
313
|
+
expect(metrics).toContain('test_counter');
|
|
314
|
+
});
|
|
315
|
+
});
|