Skip to content

Commit 58cb91c

Browse files
committed
apiwiz tracing implementation
1 parent 234d619 commit 58cb91c

File tree

11 files changed

+622
-0
lines changed

11 files changed

+622
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
}
7+
8+
dependencies {
9+
compileOnly("jakarta.servlet:jakarta.servlet-api:5.0.0")
10+
compileOnly("com.google.auto.value:auto-value-annotations")
11+
annotationProcessor("com.google.auto.value:auto-value")
12+
compileOnly("org.springframework:spring-web:6.0.0")
13+
compileOnly("com.fasterxml.jackson.core:jackson-databind")
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package io.opentelemetry.javaagent.instrumentation.apiwiz_logging;
2+
3+
import static net.bytebuddy.matcher.ElementMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.returns;
5+
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import io.opentelemetry.api.trace.Span;
8+
import io.opentelemetry.javaagent.bootstrap.context.TraceContext;
9+
import io.opentelemetry.javaagent.bootstrap.context.TraceContextHolder;
10+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
11+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
12+
import io.opentelemetry.javaagent.instrumentation.apiwiz_logging.model.ComplianceCheckDto;
13+
import io.opentelemetry.javaagent.instrumentation.apiwiz_logging.model.Request;
14+
import io.opentelemetry.javaagent.instrumentation.apiwiz_logging.model.Response;
15+
import jakarta.servlet.http.HttpServletRequest;
16+
import jakarta.servlet.http.HttpServletResponse;
17+
import java.io.BufferedReader;
18+
import java.io.InputStreamReader;
19+
import java.io.OutputStream;
20+
import java.io.UnsupportedEncodingException;
21+
import java.net.HttpURLConnection;
22+
import java.net.URL;
23+
import java.nio.charset.Charset;
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.Collection;
26+
import java.util.Enumeration;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
import net.bytebuddy.asm.Advice;
30+
import net.bytebuddy.description.type.TypeDescription;
31+
import net.bytebuddy.matcher.ElementMatcher;
32+
import org.springframework.web.util.ContentCachingRequestWrapper;
33+
import org.springframework.web.util.ContentCachingResponseWrapper;
34+
import io.opentelemetry.javaagent.bootstrap.context.config.Config;
35+
36+
//import static org.apache.http.protocol.HTTP.CONTENT_TYPE;
37+
//import static org.springframework.http.HttpMethod.POST;
38+
//import static org.springframework.http.MediaType.APPLICATION_JSON;
39+
40+
41+
public class ApiLoggingInstrumentation implements TypeInstrumentation {
42+
43+
@Override
44+
public ElementMatcher<TypeDescription> typeMatcher() {
45+
return named("org.springframework.web.servlet.DispatcherServlet");
46+
}
47+
48+
@Override
49+
public void transform(TypeTransformer transformer) {
50+
transformer.applyAdviceToMethod(
51+
named("doDispatch").and(returns(void.class)),
52+
ApiLoggingInstrumentation.class.getName() + "$ApiLoggingAdvice"
53+
);
54+
}
55+
56+
@SuppressWarnings("unused")
57+
public static class ApiLoggingAdvice {
58+
59+
60+
public static final String HOST = "Host";
61+
public static final String REFERER = "Referer";
62+
public static final String X_CLIENT_ID = "x-client-id";
63+
public static final String X_CLIENT_SECRET = "x-client-secret";
64+
65+
@Advice.OnMethodEnter(suppress = Throwable.class)
66+
public static void onEnter(@Advice.Origin String method,
67+
@Advice.Local("requestTimestamp") long requestTimestamp,
68+
@Advice.Argument(value = 0, readOnly = false) HttpServletRequest request,
69+
@Advice.Argument(value = 1, readOnly = false) HttpServletResponse response) {
70+
71+
requestTimestamp = System.currentTimeMillis();
72+
if (!(request instanceof ContentCachingRequestWrapper)) {
73+
request = new ContentCachingRequestWrapper(request);
74+
}
75+
if (!(response instanceof ContentCachingResponseWrapper)) {
76+
response = new ContentCachingResponseWrapper(response);
77+
}
78+
TraceContext.extractTraceContext(request.getHeader(Config.traceIdKey), request.getHeader(Config.spanIdKey), Config.traceIdKey, Config.spanIdKey);
79+
}
80+
81+
@Advice.OnMethodExit(suppress = Throwable.class)
82+
public static void onExit(@Advice.Origin String method,
83+
@Advice.Local("requestTimestamp") long requestTimestamp,
84+
@Advice.Argument(value = 0, readOnly = false) HttpServletRequest request,
85+
@Advice.Argument(value = 1, readOnly = false) HttpServletResponse response) {
86+
try {
87+
ComplianceCheckDto complianceCheckDto = sendDataCompliance(request, response, requestTimestamp, System.currentTimeMillis());
88+
sendApiLog(complianceCheckDto);
89+
} catch (Exception e) {
90+
System.err.println("[API LOG] [ERROR] Failed to copy response body: " + e.getMessage());
91+
}
92+
System.out.println("[API LOG] Response Sent: " + method);
93+
}
94+
95+
public static String getRequestBody(ContentCachingRequestWrapper request) {
96+
byte[] content = request.getContentAsByteArray();
97+
try {
98+
return content.length > 0 ? new String(content, request.getCharacterEncoding())
99+
: "[EMPTY BODY]";
100+
} catch (UnsupportedEncodingException e) {
101+
return "[ERROR: Unsupported Encoding]";
102+
}
103+
}
104+
105+
public static ComplianceCheckDto sendDataCompliance(HttpServletRequest request,
106+
HttpServletResponse response, long requestTimestamp, long responseTimestamp) throws Exception {
107+
ContentCachingRequestWrapper requestWrapper = (ContentCachingRequestWrapper) request;
108+
ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;
109+
110+
ComplianceCheckDto checkDto = new ComplianceCheckDto();
111+
Request requestDto = new Request();
112+
Map<String, Object> params = new HashMap<>();
113+
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
114+
while (headerNames.hasMoreElements()) {
115+
String header = headerNames.nextElement();
116+
params.put(header, requestWrapper.getHeader(header));
117+
}
118+
119+
TraceContextHolder traceContextHolder = TraceContext.currentTraceContext();
120+
params.put(Config.traceIdKey, traceContextHolder.getTraceId());
121+
params.put(Config.spanIdKey, traceContextHolder.getSpanId());
122+
params.put(Config.parentSpanIdKey, traceContextHolder.getParentSpanId());
123+
params.put(Config.requestTimeStampKey, requestTimestamp);
124+
params.put(Config.responseTimeStampKey, responseTimestamp);
125+
params.put(Config.gatewayTypeKey, "Apiwiz-Java-Agent");
126+
127+
requestDto.setHeaderParams(params);
128+
requestDto.setVerb(requestWrapper.getMethod());
129+
requestDto.setHostname(requestWrapper.getHeader(HOST));
130+
requestDto.setScheme(requestWrapper.getScheme());
131+
requestDto.setPort(requestWrapper.getServerPort());
132+
requestDto.setQueryParams(new HashMap<>());
133+
requestDto.getQueryParams().putAll(requestWrapper.getParameterMap());
134+
requestDto.setPath(requestWrapper.getRequestURI());
135+
requestDto.setRequestBody(getRequestBody(requestWrapper));
136+
Response responseDto = new Response();
137+
responseDto.setStatusCode(String.valueOf(responseWrapper.getStatus()));
138+
responseDto.setResponseBody(
139+
new String(responseWrapper.getContentAsByteArray(), StandardCharsets.UTF_8));
140+
responseWrapper.copyBodyToResponse();
141+
142+
responseDto.setHeaderParams(new HashMap<>());
143+
Collection<String> responseHeaderNames = responseWrapper.getHeaderNames();
144+
for (String header : responseHeaderNames) {
145+
responseDto.getHeaderParams().put(header, responseWrapper.getHeader(header));
146+
}
147+
checkDto.setRequest(requestDto);
148+
checkDto.setResponse(responseDto);
149+
checkDto.setServerIp(Config.serverIp);
150+
checkDto.setClientIp(requestWrapper.getRemoteAddr());
151+
checkDto.setServerHost(responseWrapper.getHeader(HOST));
152+
checkDto.setClientHost(requestWrapper.getHeader(REFERER));
153+
checkDto.setOtelTraceId(Span.current().getSpanContext().getTraceId());
154+
checkDto.setOtelSpanId(Span.current().getSpanContext().getSpanId());
155+
return checkDto;
156+
}
157+
158+
public static void sendApiLog(ComplianceCheckDto complianceCheckDto) {
159+
try {
160+
ObjectMapper objectMapper = new ObjectMapper();
161+
String jsonPayload = objectMapper.writeValueAsString(complianceCheckDto);
162+
163+
URL url = new URL(Config.apiwizDetectUrl); // Replace with your API URL
164+
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
165+
conn.setRequestMethod("POST");
166+
conn.setRequestProperty("content-type", "application/json");
167+
conn.setRequestProperty(X_CLIENT_ID, Config.workspaceId);
168+
conn.setRequestProperty(X_CLIENT_SECRET, Config.apiKey);
169+
conn.setDoOutput(true);
170+
171+
try (OutputStream os = conn.getOutputStream()) {
172+
os.write(jsonPayload.getBytes(StandardCharsets.UTF_8));
173+
os.flush();
174+
}
175+
176+
int responseCode = conn.getResponseCode();
177+
try (BufferedReader in = new BufferedReader(new InputStreamReader(
178+
responseCode == HttpURLConnection.HTTP_OK ? conn.getInputStream()
179+
: conn.getErrorStream(), Charset.defaultCharset()))) {
180+
String inputLine;
181+
StringBuilder responseBody = new StringBuilder();
182+
while ((inputLine = in.readLine()) != null) {
183+
responseBody.append(inputLine);
184+
}
185+
// Print the response body
186+
System.out.println(
187+
"[API LOG] Sent log to API, Response Code: " + responseCode + " Response: "
188+
+ responseBody.toString());
189+
}
190+
191+
} catch (Exception e) {
192+
System.err.println("[API LOG] [ERROR] Failed to send API log: " + e.getMessage());
193+
}
194+
}
195+
196+
}
197+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.opentelemetry.javaagent.instrumentation.apiwiz_logging;
2+
3+
import com.google.auto.service.AutoService;
4+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
5+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
6+
import java.util.Collections;
7+
import java.util.List;
8+
9+
@AutoService(InstrumentationModule.class)
10+
public class ApiLoggingInstrumentationModule extends InstrumentationModule {
11+
12+
public ApiLoggingInstrumentationModule() {
13+
super("apiwiz-tracing", "apiwiz-tracing-1.0");
14+
System.out.println("--------------Apiwiz ApiLoggingInstrumentationModule-----------------");
15+
}
16+
17+
@Override
18+
public List<TypeInstrumentation> typeInstrumentations() {
19+
return Collections.singletonList(new ApiLoggingInstrumentation());
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.opentelemetry.javaagent.instrumentation.apiwiz_logging.model;
2+
3+
4+
import java.io.Serializable;
5+
6+
public class ComplianceCheckDto implements Serializable {
7+
8+
private Request request;
9+
private Response response;
10+
private String clientIp;
11+
private String clientHost;
12+
private String serverIp;
13+
private String serverHost;
14+
15+
private String otelTraceId;
16+
private String otelSpanId;
17+
18+
public Request getRequest() {
19+
return request;
20+
}
21+
22+
public void setRequest(Request request) {
23+
this.request = request;
24+
}
25+
26+
public Response getResponse() {
27+
return response;
28+
}
29+
30+
public void setResponse(Response response) {
31+
this.response = response;
32+
}
33+
34+
public String getClientIp() {
35+
return clientIp;
36+
}
37+
38+
public void setClientIp(String clientIp) {
39+
this.clientIp = clientIp;
40+
}
41+
42+
public String getClientHost() {
43+
return clientHost;
44+
}
45+
46+
public void setClientHost(String clientHost) {
47+
this.clientHost = clientHost;
48+
}
49+
50+
public String getServerIp() {
51+
return serverIp;
52+
}
53+
54+
public void setServerIp(String serverIp) {
55+
this.serverIp = serverIp;
56+
}
57+
58+
public String getServerHost() {
59+
return serverHost;
60+
}
61+
62+
public void setServerHost(String serverHost) {
63+
this.serverHost = serverHost;
64+
}
65+
66+
public String getOtelTraceId() {
67+
return otelTraceId;
68+
}
69+
70+
public void setOtelTraceId(String otelTraceId) {
71+
this.otelTraceId = otelTraceId;
72+
}
73+
74+
public String getOtelSpanId() {
75+
return otelSpanId;
76+
}
77+
78+
public void setOtelSpanId(String otelSpanId) {
79+
this.otelSpanId = otelSpanId;
80+
}
81+
}

0 commit comments

Comments
 (0)