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