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+ }
0 commit comments