Skip to content
This repository was archived by the owner on Sep 24, 2025. It is now read-only.

Commit 244ec60

Browse files
author
trzysiek
committed
metrics from Frontend + dashboard
1 parent 136b57c commit 244ec60

File tree

7 files changed

+725
-11
lines changed

7 files changed

+725
-11
lines changed

docker-compose.yml

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ services:
2727
- order_data:/var/lib/postgresql/data
2828
networks:
2929
- grafana_network
30+
pgadmin:
31+
image: dpage/pgadmin4:latest
32+
container_name: pgadmin
33+
environment:
34+
PGADMIN_DEFAULT_EMAIL: [email protected]
35+
PGADMIN_DEFAULT_PASSWORD: admin
36+
PGADMIN_CONFIG_SERVER_MODE: 'False'
37+
ports:
38+
- "5050:80"
39+
networks:
40+
- grafana_network
41+
depends_on:
42+
- product-db
43+
- order-db
44+
3045
#########################################################
3146
# SERVICES
3247
product-service:
@@ -49,8 +64,7 @@ services:
4964
- order-db
5065
networks:
5166
- grafana_network
52-
#########################################################
53-
# HEALTH MONITOR
67+
5468
frontend-service:
5569
build: ./frontend-service
5670
ports:
@@ -73,6 +87,8 @@ services:
7387
networks:
7488
- grafana_network
7589

90+
#########################################################
91+
# HEALTH MONITOR
7692
health-monitor:
7793
build: ./health-monitor
7894
container_name: health-monitor
@@ -96,14 +112,12 @@ services:
96112
retries: 3
97113
restart: unless-stopped
98114

99-
######################
100-
# MONITORING
101-
# - loki
102-
# - prometheus
103-
# - grafana
104-
105115
#########################################################
106116
# MONITORING
117+
# - loki
118+
# - prometheus
119+
# - grafana
120+
#########################################################
107121
prometheus:
108122
image: prom/prometheus:latest
109123
container_name: prometheus

frontend-service/health-server.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,56 @@
11
const express = require('express')
2+
const client = require('prom-client')
23
const app = express()
34
const port = 3001
45

6+
// Create a Registry to register metrics
7+
const register = new client.Registry()
8+
9+
// Add default metrics
10+
client.collectDefaultMetrics({ register })
11+
12+
// Create custom metrics for frontend orders
13+
const orderMetrics = {
14+
// Counter for total orders created
15+
ordersCreated: new client.Counter({
16+
name: 'frontend_orders_created_total',
17+
help: 'Total number of orders created from frontend',
18+
labelNames: ['user_email', 'status']
19+
}),
20+
21+
// Counter for cart operations
22+
cartOperations: new client.Counter({
23+
name: 'frontend_cart_operations_total',
24+
help: 'Total number of cart operations',
25+
labelNames: ['operation', 'user_email', 'status']
26+
}),
27+
28+
// Gauge for current cart items
29+
cartItems: new client.Gauge({
30+
name: 'frontend_cart_items_current',
31+
help: 'Current number of items in user carts',
32+
labelNames: ['user_email']
33+
}),
34+
35+
// Counter for user actions
36+
userActions: new client.Counter({
37+
name: 'frontend_user_actions_total',
38+
help: 'Total number of user actions',
39+
labelNames: ['action', 'user_email', 'tab', 'status']
40+
}),
41+
42+
// Histogram for action response times
43+
actionDuration: new client.Histogram({
44+
name: 'frontend_action_duration_seconds',
45+
help: 'Duration of frontend actions',
46+
labelNames: ['action', 'status'],
47+
buckets: [0.1, 0.5, 1, 2, 5, 10]
48+
})
49+
}
50+
51+
// Register custom metrics
52+
Object.values(orderMetrics).forEach(metric => register.registerMetric(metric))
53+
554
// Enable JSON parsing
655
app.use(express.json())
756

@@ -26,6 +75,49 @@ app.get('/health', (req, res) => {
2675
})
2776
})
2877

78+
// Metrics endpoint for Prometheus
79+
app.get('/metrics', async (req, res) => {
80+
try {
81+
res.set('Content-Type', register.contentType)
82+
res.end(await register.metrics())
83+
} catch (error) {
84+
console.error('Error generating metrics:', error)
85+
res.status(500).end('Error generating metrics')
86+
}
87+
})
88+
89+
// Endpoint to record metrics from frontend
90+
app.post('/api/metrics', (req, res) => {
91+
try {
92+
const { type, name, labels = {}, value = 1 } = req.body
93+
94+
if (!orderMetrics[name]) {
95+
return res.status(400).json({ error: 'Unknown metric name' })
96+
}
97+
98+
const metric = orderMetrics[name]
99+
100+
switch (type) {
101+
case 'counter':
102+
metric.inc(labels, value)
103+
break
104+
case 'gauge':
105+
metric.set(labels, value)
106+
break
107+
case 'histogram':
108+
metric.observe(labels, value)
109+
break
110+
default:
111+
return res.status(400).json({ error: 'Unknown metric type' })
112+
}
113+
114+
res.status(200).json({ success: true })
115+
} catch (error) {
116+
console.error('Error recording metric:', error)
117+
res.status(500).json({ error: 'Internal server error' })
118+
}
119+
})
120+
29121
// Proxy endpoint to forward logs to Loki
30122
app.post('/api/logs', async (req, res) => {
31123
try {

frontend-service/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"axios": "^1.6.0",
1717
"express": "^4.18.2",
1818
"vite": "^5.0.0",
19-
"node-fetch": "^3.3.2"
19+
"node-fetch": "^3.3.2",
20+
"prom-client": "^15.1.0"
2021
},
2122
"devDependencies": {
2223
"@types/react": "^18.2.37",

frontend-service/src/App.jsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { useState, useEffect } from 'react'
22
import axios from 'axios'
33
import { logger } from './logger'
4+
import { metrics } from './metrics'
45

56
const API_CONFIG = {
67
PRODUCT_SERVICE: 'http://localhost:3001',
@@ -178,6 +179,10 @@ function App() {
178179
setCart(cartResponse.data)
179180
setOrders(ordersResponse.data)
180181

182+
// Record login metrics
183+
await metrics.recordUserAction('login', userResponse.data.email, activeTab, 'success')
184+
await metrics.updateCartItems(userResponse.data.email, cartResponse.data.length)
185+
181186
await logger.info('User logged in successfully', {
182187
action: 'login',
183188
user: userResponse.data.email
@@ -216,13 +221,21 @@ function App() {
216221
return
217222
}
218223

224+
const timer = metrics.createTimer('addToCart')
225+
219226
try {
220227
const headers = { Authorization: `Bearer ${token}` }
221228
await axios.post(`${API_CONFIG.ORDER_SERVICE}/users/${user.id}/cart/items`, {
222229
product_id: productId,
223230
quantity: quantity
224231
}, { headers })
225232
loadUserData()
233+
234+
// Record metrics
235+
await metrics.recordCartOperation('add', user.email, 'success')
236+
await metrics.recordUserAction('addToCart', user.email, activeTab, 'success')
237+
await timer.end('success')
238+
226239
await logger.info('Item added to cart successfully', {
227240
action: 'addToCart',
228241
productId,
@@ -233,6 +246,11 @@ function App() {
233246
// Reset quantity to 1 after adding
234247
setQuantities(prev => ({ ...prev, [productId]: 1 }))
235248
} catch (error) {
249+
// Record failed metrics
250+
await metrics.recordCartOperation('add', user.email, 'error')
251+
await metrics.recordUserAction('addToCart', user.email, activeTab, 'error')
252+
await timer.end('error')
253+
236254
await handleError(error, 'Failed to add item to cart', {
237255
action: 'addToCart',
238256
productId,
@@ -297,17 +315,38 @@ function App() {
297315
return
298316
}
299317

318+
const timer = metrics.createTimer('createOrder')
319+
300320
try {
301321
const headers = { Authorization: `Bearer ${token}` }
302322
await axios.post(`${API_CONFIG.ORDER_SERVICE}/orders`, {
303323
items: cart,
304324
shipping_address: { address: '123 Main St', city: 'City', country: 'Country' }
305325
}, { headers })
306326
loadUserData()
327+
328+
// Record successful order metrics
329+
await metrics.recordOrderCreated(user.email, 'success')
330+
await metrics.recordUserAction('createOrder', user.email, activeTab, 'success')
331+
await metrics.updateCartItems(user.email, 0) // Cart should be empty after order
332+
await timer.end('success')
333+
334+
await logger.info('Order created successfully', {
335+
action: 'createOrder',
336+
user: user.email,
337+
itemCount: cart.length
338+
})
307339
alert('Order created successfully!')
308340
} catch (error) {
309-
console.error('Error creating order:', error)
310-
alert('Error creating order')
341+
// Record failed order metrics
342+
await metrics.recordOrderCreated(user.email, 'error')
343+
await metrics.recordUserAction('createOrder', user.email, activeTab, 'error')
344+
await timer.end('error')
345+
346+
await handleError(error, 'Failed to create order', {
347+
action: 'createOrder',
348+
itemCount: cart.length
349+
})
311350
}
312351
}
313352

frontend-service/src/metrics.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Frontend metrics client that sends metrics to Prometheus via health server
2+
class MetricsClient {
3+
constructor() {
4+
this.metricsUrl = 'http://localhost:3004/api/metrics'
5+
}
6+
7+
async sendMetric(type, name, labels = {}, value = 1) {
8+
try {
9+
await fetch(this.metricsUrl, {
10+
method: 'POST',
11+
headers: {
12+
'Content-Type': 'application/json',
13+
},
14+
body: JSON.stringify({
15+
type,
16+
name,
17+
labels,
18+
value
19+
})
20+
})
21+
} catch (error) {
22+
console.error('Failed to send metric:', error)
23+
}
24+
}
25+
26+
// Order-related metrics
27+
async recordOrderCreated(userEmail, status = 'success') {
28+
await this.sendMetric('counter', 'ordersCreated', {
29+
user_email: userEmail,
30+
status
31+
})
32+
}
33+
34+
// Cart operations
35+
async recordCartOperation(operation, userEmail, status = 'success') {
36+
await this.sendMetric('counter', 'cartOperations', {
37+
operation,
38+
user_email: userEmail,
39+
status
40+
})
41+
}
42+
43+
// Update cart items count
44+
async updateCartItems(userEmail, count) {
45+
await this.sendMetric('gauge', 'cartItems', {
46+
user_email: userEmail
47+
}, count)
48+
}
49+
50+
// User actions
51+
async recordUserAction(action, userEmail, tab, status = 'success') {
52+
await this.sendMetric('counter', 'userActions', {
53+
action,
54+
user_email: userEmail,
55+
tab,
56+
status
57+
})
58+
}
59+
60+
// Action duration
61+
async recordActionDuration(action, durationSeconds, status = 'success') {
62+
await this.sendMetric('histogram', 'actionDuration', {
63+
action,
64+
status
65+
}, durationSeconds)
66+
}
67+
68+
// Helper to time actions
69+
createTimer(action) {
70+
const startTime = Date.now()
71+
return {
72+
end: async (status = 'success') => {
73+
const duration = (Date.now() - startTime) / 1000
74+
await this.recordActionDuration(action, duration, status)
75+
}
76+
}
77+
}
78+
}
79+
80+
export const metrics = new MetricsClient()
81+
export default metrics

0 commit comments

Comments
 (0)