Skip to content

Commit 7272278

Browse files
authored
[PROMETHEUS] Add prometheus to remaining services (#105)
* [PROMETHEUS] Add prometheus to remaining services * [PROMETHEUS] Add missing EOF's
1 parent 3b3ef36 commit 7272278

13 files changed

Lines changed: 287 additions & 7 deletions

File tree

client/nginx.conf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ server {
44
root /usr/share/nginx/html;
55
index index.html;
66

7+
# Health check endpoint
8+
location /health {
9+
alias /usr/share/nginx/html/health.json;
10+
add_header Content-Type application/json;
11+
add_header Cache-Control "no-cache, no-store, must-revalidate";
12+
}
13+
14+
# Basic metrics endpoint (nginx status)
15+
location /metrics {
16+
stub_status on;
17+
access_log off;
18+
add_header Content-Type text/plain;
19+
}
20+
721
# Handle client-side routing
822
location / {
923
try_files $uri $uri/ /index.html;

client/public/health.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"status": "healthy",
3+
"timestamp": "2024-01-01T00:00:00Z",
4+
"service": "client",
5+
"version": "1.0.0"
6+
}

genai/main.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,21 @@
88
import logging
99
from typing import Callable
1010

11+
# Prometheus metrics imports
12+
from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST
13+
1114
from request_models import ChatRequest, RecipeIndexRequest, RecipeDeleteRequest, RecipeSuggestionRequest
1215
from response_models import ChatResponse, RecipeIndexResponse, RecipeDeleteResponse, RecipeSuggestionResponse, HealthResponse
1316
from llm import RecipeLLM
1417

18+
# Prometheus metrics
19+
REQUEST_COUNT = Counter('genai_requests_total', 'Total number of requests', ['method', 'endpoint', 'status'])
20+
REQUEST_DURATION = Histogram('genai_request_duration_seconds', 'Request duration in seconds', ['method', 'endpoint'])
21+
ACTIVE_REQUESTS = Gauge('genai_active_requests', 'Number of active requests')
22+
LLM_OPERATIONS = Counter('genai_llm_operations_total', 'Total number of LLM operations', ['operation_type', 'status'])
23+
VECTOR_OPERATIONS = Counter('genai_vector_operations_total', 'Total number of vector operations', ['operation_type', 'status'])
24+
SERVICE_HEALTH = Gauge('genai_service_health', 'Service health status (1=healthy, 0=unhealthy)')
25+
1526
# Enhanced logging configuration
1627
class StructuredFormatter(logging.Formatter):
1728
"""Custom formatter for structured logging"""
@@ -60,12 +71,15 @@ def format(self, record):
6071

6172
logger = logging.getLogger(__name__)
6273

63-
# Add request ID tracking middleware
74+
# Add request ID tracking and Prometheus metrics middleware
6475
async def add_request_id_middleware(request: Request, call_next: Callable) -> Response:
65-
"""Add request ID to all requests for tracing"""
76+
"""Add request ID to all requests for tracing and collect Prometheus metrics"""
6677
request_id = str(uuid.uuid4())
6778
request.state.request_id = request_id
6879

80+
# Track active requests
81+
ACTIVE_REQUESTS.inc()
82+
6983
# Log incoming request
7084
start_time = time.time()
7185
structured_logger.info(
@@ -87,6 +101,18 @@ async def add_request_id_middleware(request: Request, call_next: Callable) -> Re
87101

88102
# Calculate request duration
89103
duration_ms = round((time.time() - start_time) * 1000, 2)
104+
duration_seconds = (time.time() - start_time)
105+
106+
# Update Prometheus metrics
107+
REQUEST_COUNT.labels(
108+
method=request.method,
109+
endpoint=request.url.path,
110+
status=str(response.status_code)
111+
).inc()
112+
REQUEST_DURATION.labels(
113+
method=request.method,
114+
endpoint=request.url.path
115+
).observe(duration_seconds)
90116

91117
# Log response
92118
structured_logger.info(
@@ -108,6 +134,19 @@ async def add_request_id_middleware(request: Request, call_next: Callable) -> Re
108134

109135
except Exception as e:
110136
duration_ms = round((time.time() - start_time) * 1000, 2)
137+
duration_seconds = (time.time() - start_time)
138+
139+
# Update Prometheus metrics for failed requests
140+
REQUEST_COUNT.labels(
141+
method=request.method,
142+
endpoint=request.url.path,
143+
status="500"
144+
).inc()
145+
REQUEST_DURATION.labels(
146+
method=request.method,
147+
endpoint=request.url.path
148+
).observe(duration_seconds)
149+
111150
structured_logger.error(
112151
f"Request failed: {request.method} {request.url.path} - {str(e)}",
113152
extra={
@@ -120,6 +159,9 @@ async def add_request_id_middleware(request: Request, call_next: Callable) -> Re
120159
}
121160
)
122161
raise
162+
finally:
163+
# Decrement active requests
164+
ACTIVE_REQUESTS.dec()
123165

124166
# Global LLM instance
125167
llm_instance: RecipeLLM = None
@@ -264,6 +306,22 @@ async def health_check(request: Request):
264306
services={"error": str(e)}
265307
)
266308

309+
@app.get("/metrics")
310+
async def metrics():
311+
"""Prometheus metrics endpoint"""
312+
# Update service health metric based on current status
313+
try:
314+
if llm_instance:
315+
services_status = llm_instance.get_health_status()
316+
overall_health = 1 if all("healthy" in status for status in services_status.values()) else 0
317+
else:
318+
overall_health = 0
319+
SERVICE_HEALTH.set(overall_health)
320+
except Exception:
321+
SERVICE_HEALTH.set(0)
322+
323+
return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST)
324+
267325
@app.post("/genai/chat", response_model=ChatResponse)
268326
async def chat(request: ChatRequest, http_request: Request):
269327
"""Chat with the AI assistant for recipe search and creation"""
@@ -296,6 +354,9 @@ async def chat(request: ChatRequest, http_request: Request):
296354
response = llm_instance.chat(request.message)
297355
duration_ms = round((time.time() - start_time) * 1000, 2)
298356

357+
# Update LLM operation metrics
358+
LLM_OPERATIONS.labels(operation_type='chat', status='success').inc()
359+
299360
structured_logger.info(
300361
"Chat request completed successfully",
301362
extra={
@@ -315,6 +376,9 @@ async def chat(request: ChatRequest, http_request: Request):
315376
except Exception as e:
316377
duration_ms = round((time.time() - start_time) * 1000, 2)
317378

379+
# Update LLM operation metrics for failures
380+
LLM_OPERATIONS.labels(operation_type='chat', status='error').inc()
381+
318382
logger.error(f"Error in chat: {e}", exc_info=True)
319383
structured_logger.error(
320384
f"Chat request failed: {str(e)}",
@@ -366,6 +430,9 @@ async def index_recipe(request: RecipeIndexRequest, http_request: Request):
366430
duration_ms = round((time.time() - start_time) * 1000, 2)
367431

368432
if success:
433+
# Update vector operation metrics for success
434+
VECTOR_OPERATIONS.labels(operation_type='index', status='success').inc()
435+
369436
structured_logger.info(
370437
f"Recipe indexed successfully: {request.recipe.metadata.title}",
371438
extra={
@@ -384,6 +451,9 @@ async def index_recipe(request: RecipeIndexRequest, http_request: Request):
384451
recipe_id=request.recipe.metadata.id
385452
)
386453
else:
454+
# Update vector operation metrics for failure
455+
VECTOR_OPERATIONS.labels(operation_type='index', status='error').inc()
456+
387457
structured_logger.error(
388458
f"Recipe indexing failed: {request.recipe.metadata.title}",
389459
extra={
@@ -402,6 +472,9 @@ async def index_recipe(request: RecipeIndexRequest, http_request: Request):
402472
except Exception as e:
403473
duration_ms = round((time.time() - start_time) * 1000, 2)
404474

475+
# Update vector operation metrics for exceptions
476+
VECTOR_OPERATIONS.labels(operation_type='index', status='error').inc()
477+
405478
logger.error(f"Error indexing recipe: {e}", exc_info=True)
406479
structured_logger.error(
407480
f"Recipe indexing failed: {str(e)}",
@@ -451,6 +524,9 @@ async def delete_recipe(recipe_id: str, http_request: Request):
451524
duration_ms = round((time.time() - start_time) * 1000, 2)
452525

453526
if success:
527+
# Update vector operation metrics for success
528+
VECTOR_OPERATIONS.labels(operation_type='delete', status='success').inc()
529+
454530
structured_logger.info(
455531
f"Recipe deleted successfully: {recipe_id}",
456532
extra={
@@ -468,6 +544,9 @@ async def delete_recipe(recipe_id: str, http_request: Request):
468544
recipe_id=recipe_id
469545
)
470546
else:
547+
# Update vector operation metrics for failure
548+
VECTOR_OPERATIONS.labels(operation_type='delete', status='error').inc()
549+
471550
structured_logger.error(
472551
f"Recipe deletion failed: {recipe_id}",
473552
extra={
@@ -485,6 +564,9 @@ async def delete_recipe(recipe_id: str, http_request: Request):
485564
except Exception as e:
486565
duration_ms = round((time.time() - start_time) * 1000, 2)
487566

567+
# Update vector operation metrics for exceptions
568+
VECTOR_OPERATIONS.labels(operation_type='delete', status='error').inc()
569+
488570
logger.error(f"Error deleting recipe: {e}", exc_info=True)
489571
structured_logger.error(
490572
f"Recipe deletion failed: {str(e)}",
@@ -533,6 +615,9 @@ async def suggest_recipe(request: RecipeSuggestionRequest, http_request: Request
533615
response = llm_instance.suggest_recipe(request.query)
534616
duration_ms = round((time.time() - start_time) * 1000, 2)
535617

618+
# Update LLM operation metrics for success
619+
LLM_OPERATIONS.labels(operation_type='suggest', status='success').inc()
620+
536621
structured_logger.info(
537622
"Recipe suggestion completed successfully",
538623
extra={
@@ -551,6 +636,9 @@ async def suggest_recipe(request: RecipeSuggestionRequest, http_request: Request
551636
except Exception as e:
552637
duration_ms = round((time.time() - start_time) * 1000, 2)
553638

639+
# Update LLM operation metrics for failures
640+
LLM_OPERATIONS.labels(operation_type='suggest', status='error').inc()
641+
554642
logger.error(f"Error in recipe suggestion: {e}", exc_info=True)
555643
structured_logger.error(
556644
f"Recipe suggestion failed: {str(e)}",

genai/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ langchain-community
1010
langchain-text-splitters
1111
langchain-huggingface
1212
sentence-transformers
13+
prometheus_client

helm/client/values.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ container:
2020

2121
podAnnotations:
2222
prometheus.io/scrape: "true"
23-
prometheus.io/path: "/actuator/prometheus"
24-
prometheus.io/port: "8080"
23+
prometheus.io/path: "/health"
24+
prometheus.io/port: "3000"
2525
cluster-autoscaler.kubernetes.io/safe-to-evict: "true"
2626
app.kubernetes.io/version: "1.0.0"
2727
sidecar.istio.io/inject: "true"

server/api-gateway/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ dependencies {
2727
implementation("org.springframework.cloud:spring-cloud-starter-gateway")
2828
implementation("org.springframework.boot:spring-boot-starter-oauth2-resource-server")
2929
implementation("org.springframework.boot:spring-boot-starter-actuator")
30+
implementation("io.micrometer:micrometer-registry-prometheus")
31+
implementation("org.springframework.boot:spring-boot-starter-logging")
32+
implementation("ch.qos.logback:logback-classic")
33+
implementation("net.logstash.logback:logstash-logback-encoder:7.4")
3034

3135
implementation("com.nimbusds:oauth2-oidc-sdk:11.24")
3236
implementation("org.springdoc:springdoc-openapi-starter-webflux-ui:2.3.0")

server/api-gateway/src/main/resources/application.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@ spring:
1212
uri: http://localhost:8082
1313
predicates:
1414
- Path=/version
15+
16+
logging:
17+
level:
18+
root: INFO
19+
com.recipefy.gateway: DEBUG
20+
com.recipefy.gateway.config: DEBUG
21+
com.recipefy.gateway.controller: DEBUG
22+
org.springframework.web: DEBUG
23+
org.springframework.cloud.gateway: DEBUG
24+
pattern:
25+
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
26+
1527
app:
1628
security:
1729
cors:
@@ -31,7 +43,16 @@ management:
3143
probes:
3244
enabled: true
3345
show-details: always
46+
prometheus:
47+
enabled: true
48+
metrics:
49+
export:
50+
prometheus:
51+
enabled: true
52+
distribution:
53+
percentiles-histogram:
54+
http.server.requests: true
3455
endpoints:
3556
web:
3657
exposure:
37-
include: health,info
58+
include: health,info,prometheus
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
4+
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
5+
6+
<!-- JSON Logging for production -->
7+
<springProfile name="prod">
8+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
9+
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
10+
<providers>
11+
<timestamp/>
12+
<logLevel/>
13+
<loggerName/>
14+
<message/>
15+
<mdc/>
16+
<stackTrace/>
17+
</providers>
18+
</encoder>
19+
</appender>
20+
</springProfile>
21+
22+
<!-- Development profile - standard console logging with MDC -->
23+
<springProfile name="!prod">
24+
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
25+
<encoder>
26+
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{requestId},%X{userId}] - %msg%n</pattern>
27+
</encoder>
28+
</appender>
29+
</springProfile>
30+
31+
<!-- Log levels -->
32+
<logger name="com.recipefy.gateway" level="DEBUG"/>
33+
<logger name="com.recipefy.gateway.config" level="DEBUG"/>
34+
<logger name="com.recipefy.gateway.controller" level="DEBUG"/>
35+
<logger name="org.springframework.web" level="DEBUG"/>
36+
<logger name="org.springframework.cloud.gateway" level="DEBUG"/>
37+
38+
<!-- Root logger - only console output for containerized deployment -->
39+
<root level="INFO">
40+
<appender-ref ref="STDOUT"/>
41+
</root>
42+
</configuration>

server/keycloak/build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ dependencies {
2222
implementation 'org.springframework.boot:spring-boot-starter-webflux'
2323
implementation 'org.springframework.boot:spring-boot-starter-validation'
2424
implementation 'org.springframework.boot:spring-boot-starter-actuator'
25-
25+
implementation 'io.micrometer:micrometer-registry-prometheus'
26+
implementation 'org.springframework.boot:spring-boot-starter-logging'
27+
implementation 'ch.qos.logback:logback-classic'
28+
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'
2629

2730
testImplementation 'org.springframework.boot:spring-boot-starter-test'
2831
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

server/keycloak/src/main/resources/application.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ spring:
22
application:
33
name: keycloak
44

5+
logging:
6+
level:
7+
root: INFO
8+
com.recipefy.keycloak: DEBUG
9+
com.recipefy.keycloak.controller: DEBUG
10+
com.recipefy.keycloak.service: DEBUG
11+
com.recipefy.keycloak.config: DEBUG
12+
org.springframework.web: DEBUG
13+
pattern:
14+
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
15+
516
keycloak:
617
baseUrl: https://keycloak.team-breaking-build.student.k8s.aet.cit.tum.de
718
realm: recipefy
@@ -21,8 +32,17 @@ management:
2132
probes:
2233
enabled: true
2334
show-details: always
35+
prometheus:
36+
enabled: true
37+
metrics:
38+
export:
39+
prometheus:
40+
enabled: true
41+
distribution:
42+
percentiles-histogram:
43+
http.server.requests: true
2444
endpoints:
2545
web:
2646
exposure:
27-
include: health,info
47+
include: health,info,prometheus
2848

0 commit comments

Comments
 (0)