Skip to content

Commit 2377a88

Browse files
Copilotphrocker
andcommitted
Add Jaeger OpenTelemetry UI feature - core implementation
Co-authored-by: phrocker <[email protected]>
1 parent 14f902b commit 2377a88

File tree

6 files changed

+730
-4
lines changed

6 files changed

+730
-4
lines changed
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package io.sentrius.sso.controllers.api;
2+
3+
import io.sentrius.sso.core.config.SystemOptions;
4+
import io.sentrius.sso.core.controllers.BaseController;
5+
import io.sentrius.sso.core.services.ErrorOutputService;
6+
import io.sentrius.sso.core.services.UserService;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.http.HttpEntity;
10+
import org.springframework.http.HttpHeaders;
11+
import org.springframework.http.HttpMethod;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.web.bind.annotation.*;
14+
import org.springframework.web.client.RestTemplate;
15+
16+
import java.util.*;
17+
18+
@Slf4j
19+
@RestController
20+
@RequestMapping("/api/v1/telemetry")
21+
public class TelemetryApiController extends BaseController {
22+
23+
private final RestTemplate restTemplate = new RestTemplate();
24+
25+
@Value("${jaeger.query.url:http://localhost:16686}")
26+
private String jaegerQueryUrl;
27+
28+
protected TelemetryApiController(
29+
UserService userService,
30+
SystemOptions systemOptions,
31+
ErrorOutputService errorOutputService
32+
) {
33+
super(userService, systemOptions, errorOutputService);
34+
}
35+
36+
@GetMapping("/traces")
37+
public ResponseEntity<?> getTraces(
38+
@RequestParam(required = false) String service,
39+
@RequestParam(required = false) String operation,
40+
@RequestParam(defaultValue = "1h") String lookback,
41+
@RequestParam(required = false) Long minDuration,
42+
@RequestParam(required = false) Long maxDuration,
43+
@RequestParam(required = false) String tags
44+
) {
45+
try {
46+
String jaegerApiUrl = buildJaegerApiUrl(service, operation, lookback, minDuration, maxDuration, tags);
47+
log.info("Querying Jaeger at: {}", jaegerApiUrl);
48+
49+
HttpHeaders headers = new HttpHeaders();
50+
headers.set("Accept", "application/json");
51+
HttpEntity<String> entity = new HttpEntity<>(headers);
52+
53+
ResponseEntity<Map> response = restTemplate.exchange(
54+
jaegerApiUrl,
55+
HttpMethod.GET,
56+
entity,
57+
Map.class
58+
);
59+
60+
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
61+
Map<String, Object> jaegerResponse = response.getBody();
62+
List<Map<String, Object>> processedTraces = processJaegerResponse(jaegerResponse);
63+
64+
Map<String, Object> result = new HashMap<>();
65+
result.put("traces", processedTraces);
66+
result.put("status", "success");
67+
result.put("count", processedTraces.size());
68+
69+
return ResponseEntity.ok(result);
70+
} else {
71+
return ResponseEntity.status(response.getStatusCode())
72+
.body(Map.of("error", "Failed to query Jaeger", "status", "error"));
73+
}
74+
75+
} catch (Exception e) {
76+
log.error("Error querying Jaeger traces", e);
77+
return ResponseEntity.internalServerError()
78+
.body(Map.of("error", "Internal server error: " + e.getMessage(), "status", "error"));
79+
}
80+
}
81+
82+
@GetMapping("/services")
83+
public ResponseEntity<?> getServices() {
84+
try {
85+
String servicesUrl = jaegerQueryUrl + "/api/services";
86+
log.info("Fetching services from Jaeger at: {}", servicesUrl);
87+
88+
HttpHeaders headers = new HttpHeaders();
89+
headers.set("Accept", "application/json");
90+
HttpEntity<String> entity = new HttpEntity<>(headers);
91+
92+
ResponseEntity<Map> response = restTemplate.exchange(
93+
servicesUrl,
94+
HttpMethod.GET,
95+
entity,
96+
Map.class
97+
);
98+
99+
return ResponseEntity.ok(response.getBody());
100+
101+
} catch (Exception e) {
102+
log.error("Error fetching services from Jaeger", e);
103+
return ResponseEntity.internalServerError()
104+
.body(Map.of("error", "Failed to fetch services: " + e.getMessage(), "status", "error"));
105+
}
106+
}
107+
108+
@GetMapping("/trace/{traceId}")
109+
public ResponseEntity<?> getTrace(@PathVariable String traceId) {
110+
try {
111+
String traceUrl = jaegerQueryUrl + "/api/traces/" + traceId;
112+
log.info("Fetching trace from Jaeger at: {}", traceUrl);
113+
114+
HttpHeaders headers = new HttpHeaders();
115+
headers.set("Accept", "application/json");
116+
HttpEntity<String> entity = new HttpEntity<>(headers);
117+
118+
ResponseEntity<Map> response = restTemplate.exchange(
119+
traceUrl,
120+
HttpMethod.GET,
121+
entity,
122+
Map.class
123+
);
124+
125+
return ResponseEntity.ok(response.getBody());
126+
127+
} catch (Exception e) {
128+
log.error("Error fetching trace from Jaeger", e);
129+
return ResponseEntity.internalServerError()
130+
.body(Map.of("error", "Failed to fetch trace: " + e.getMessage(), "status", "error"));
131+
}
132+
}
133+
134+
private String buildJaegerApiUrl(String service, String operation, String lookback,
135+
Long minDuration, Long maxDuration, String tags) {
136+
StringBuilder url = new StringBuilder(jaegerQueryUrl + "/api/traces?");
137+
138+
if (service != null && !service.isEmpty()) {
139+
url.append("service=").append(service).append("&");
140+
}
141+
142+
if (operation != null && !operation.isEmpty()) {
143+
url.append("operation=").append(operation).append("&");
144+
}
145+
146+
url.append("lookback=").append(lookback).append("&");
147+
148+
if (minDuration != null) {
149+
url.append("minDuration=").append(minDuration).append("us&");
150+
}
151+
152+
if (maxDuration != null) {
153+
url.append("maxDuration=").append(maxDuration).append("us&");
154+
}
155+
156+
if (tags != null && !tags.isEmpty()) {
157+
url.append("tags=").append(tags).append("&");
158+
}
159+
160+
// Add limit to prevent too many results
161+
url.append("limit=100");
162+
163+
return url.toString();
164+
}
165+
166+
private List<Map<String, Object>> processJaegerResponse(Map<String, Object> jaegerResponse) {
167+
List<Map<String, Object>> processedTraces = new ArrayList<>();
168+
169+
try {
170+
Object dataObj = jaegerResponse.get("data");
171+
if (dataObj instanceof List) {
172+
List<Map<String, Object>> traces = (List<Map<String, Object>>) dataObj;
173+
174+
for (Map<String, Object> trace : traces) {
175+
Map<String, Object> processedTrace = new HashMap<>();
176+
processedTrace.put("traceID", trace.get("traceID"));
177+
178+
// Calculate duration and other metrics
179+
Object spansObj = trace.get("spans");
180+
if (spansObj instanceof List) {
181+
List<Map<String, Object>> spans = (List<Map<String, Object>>) spansObj;
182+
processedTrace.put("spans", spans);
183+
processedTrace.put("spanCount", spans.size());
184+
185+
// Find root span for start time and total duration
186+
Optional<Map<String, Object>> rootSpan = spans.stream()
187+
.filter(span -> {
188+
Object refs = span.get("references");
189+
return refs == null || (refs instanceof List && ((List<?>) refs).isEmpty());
190+
})
191+
.findFirst();
192+
193+
if (rootSpan.isPresent()) {
194+
processedTrace.put("startTime", rootSpan.get().get("startTime"));
195+
processedTrace.put("duration", rootSpan.get().get("duration"));
196+
}
197+
}
198+
199+
processedTraces.add(processedTrace);
200+
}
201+
}
202+
} catch (Exception e) {
203+
log.warn("Error processing Jaeger response", e);
204+
}
205+
206+
return processedTraces;
207+
}
208+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.sentrius.sso.controllers.view;
2+
3+
import io.sentrius.sso.core.config.SystemOptions;
4+
import io.sentrius.sso.core.controllers.BaseController;
5+
import io.sentrius.sso.core.services.ErrorOutputService;
6+
import io.sentrius.sso.core.services.UserService;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.stereotype.Controller;
9+
import org.springframework.web.bind.annotation.GetMapping;
10+
import org.springframework.web.bind.annotation.RequestMapping;
11+
12+
@Slf4j
13+
@Controller
14+
@RequestMapping("/sso")
15+
public class TelemetryController extends BaseController {
16+
17+
protected TelemetryController(
18+
UserService userService,
19+
SystemOptions systemOptions,
20+
ErrorOutputService errorOutputService
21+
) {
22+
super(userService, systemOptions, errorOutputService);
23+
}
24+
25+
@GetMapping("/v1/telemetry")
26+
public String telemetry() {
27+
return "sso/telemetry";
28+
}
29+
}

api/src/main/resources/application.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ otel.resource.attributes.service.name=sentrius-api
8989
otel.traces.sampler=always_on
9090
otel.exporter.otlp.timeout=10s
9191

92+
# Jaeger Query API URL
93+
jaeger.query.url=${JAEGER_QUERY_URL:http://localhost:16686}
94+
9295
sentrius.agent.register.bootstrap.allow=true
9396
sentrius.agent.bootstrap.policy=default-policy.yaml
9497
# Optional: set the identity lifetime

api/src/main/resources/templates/fragments/sidebar.html

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,15 @@
5555
<i class="fa-solid fa-user"></i> <span class="ms-1 d-none d-sm-inline">Manage Agent/Users</span>
5656
</a>
5757
</li>
58-
<li th:if="${#sets.contains(operatingUser.authorizationType.accessSet, 'CAN_MANAGE_APPLICATION')}">
59-
<a href="/sso/v1/atpl/" class="nav-link px-0 align-middle">
60-
<i class="fas fa-shield-alt"></i> <span class="ms-1 d-none d-sm-inline">Trust Policies</span>
61-
</a>
58+
<li th:if="${#sets.contains(operatingUser.authorizationType.accessSet, 'CAN_MANAGE_APPLICATION')}">
59+
<a href="/sso/v1/atpl/" class="nav-link px-0 align-middle">
60+
<i class="fas fa-shield-alt"></i> <span class="ms-1 d-none d-sm-inline">Trust Policies</span>
61+
</a>
62+
</li>
63+
<li th:if="${#sets.contains(operatingUser.authorizationType.accessSet, 'CAN_MANAGE_APPLICATION')}">
64+
<a href="/sso/v1/telemetry" class="nav-link px-0 align-middle">
65+
<i class="fas fa-chart-line"></i> <span class="ms-1 d-none d-sm-inline">Telemetry</span>
66+
</a>
6267
</li>
6368
</ul>
6469
<hr>

0 commit comments

Comments
 (0)