From 939610042e56b7ac5c27e0e21da23bb7a1b3e0bb Mon Sep 17 00:00:00 2001 From: Chisu Yu Date: Thu, 27 Dec 2012 09:36:45 +0000 Subject: [PATCH] =?UTF-8?q?[=EC=9C=A0=EC=B9=98=EC=88=98]=20[NOBTS]=20add?= =?UTF-8?q?=20spring=20servlet=20modifier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: http://svn.bds.nhncorp.com/pe/hippo-tomcat-profiler/trunk@1066 84d0f5b1-2673-498c-a247-62c4ff18d310 --- .../profiler/modifier/AbstractModifier.java | 7 +- .../modifier/DefaultModifierRegistry.java | 17 +- .../modifier/servlet/HttpServletModifier.java | 56 ++++++ .../SpringFrameworkServletModifier.java | 56 ++++++ .../interceptors/DoXXXInterceptor.java | 174 ++++++++++++++++++ 5 files changed, 303 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/profiler/modifier/servlet/HttpServletModifier.java create mode 100644 src/main/java/com/profiler/modifier/servlet/SpringFrameworkServletModifier.java create mode 100644 src/main/java/com/profiler/modifier/servlet/interceptors/DoXXXInterceptor.java diff --git a/src/main/java/com/profiler/modifier/AbstractModifier.java b/src/main/java/com/profiler/modifier/AbstractModifier.java index 663d47501..3d8da49da 100644 --- a/src/main/java/com/profiler/modifier/AbstractModifier.java +++ b/src/main/java/com/profiler/modifier/AbstractModifier.java @@ -1,16 +1,13 @@ package com.profiler.modifier; -import java.security.ProtectionDomain; import java.util.logging.Level; import java.util.logging.Logger; -import com.profiler.Agent; -import com.profiler.context.TraceContext; -import com.profiler.interceptor.TraceContextSupport; -import com.profiler.interceptor.bci.InstrumentException; import javassist.ClassPool; +import com.profiler.Agent; import com.profiler.interceptor.Interceptor; +import com.profiler.interceptor.TraceContextSupport; import com.profiler.interceptor.bci.ByteCodeInstrumentor; public abstract class AbstractModifier implements Modifier { diff --git a/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java b/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java index c818625af..cd167aa24 100644 --- a/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java +++ b/src/main/java/com/profiler/modifier/DefaultModifierRegistry.java @@ -31,6 +31,8 @@ import com.profiler.modifier.db.mysql.MySQLStatementModifier; import com.profiler.modifier.db.oracle.OraclePreparedStatementModifier; import com.profiler.modifier.db.oracle.OracleResultSetModifier; import com.profiler.modifier.db.oracle.OracleStatementModifier; +import com.profiler.modifier.servlet.HttpServletModifier; +import com.profiler.modifier.servlet.SpringFrameworkServletModifier; import com.profiler.modifier.tomcat.CatalinaModifier; import com.profiler.modifier.tomcat.StandardHostValveInvokeModifier; import com.profiler.modifier.tomcat.TomcatConnectorModifier; @@ -86,8 +88,19 @@ public class DefaultModifierRegistry implements ModifierRegistry { } public void addTomcatModifier() { - StandardHostValveInvokeModifier standardHostValveInvokeModifier = new StandardHostValveInvokeModifier(byteCodeInstrumentor, agent); - addModifier(standardHostValveInvokeModifier); + // TODO 포함시키면 다음 exception이 발생하여 일단 주석처리함. + // ???: Tomcat StandardHostValve trace start fail. Caused:already Trace Object exist. + // java.lang.IllegalStateException: already Trace Object exist. + // at com.profiler.context.TraceContext.attachTraceObject(TraceContext.java:51) + + // StandardHostValveInvokeModifier standardHostValveInvokeModifier = new StandardHostValveInvokeModifier(byteCodeInstrumentor, agent); + // addModifier(standardHostValveInvokeModifier); + + HttpServletModifier httpServletModifier = new HttpServletModifier(byteCodeInstrumentor, agent); + addModifier(httpServletModifier); + + SpringFrameworkServletModifier springServletModifier = new SpringFrameworkServletModifier(byteCodeInstrumentor, agent); + addModifier(springServletModifier); Modifier tomcatStandardServiceModifier = new TomcatStandardServiceModifier(byteCodeInstrumentor, agent); addModifier(tomcatStandardServiceModifier); diff --git a/src/main/java/com/profiler/modifier/servlet/HttpServletModifier.java b/src/main/java/com/profiler/modifier/servlet/HttpServletModifier.java new file mode 100644 index 000000000..580dad6db --- /dev/null +++ b/src/main/java/com/profiler/modifier/servlet/HttpServletModifier.java @@ -0,0 +1,56 @@ +package com.profiler.modifier.servlet; + +import java.security.ProtectionDomain; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.profiler.Agent; +import com.profiler.interceptor.Interceptor; +import com.profiler.interceptor.bci.ByteCodeInstrumentor; +import com.profiler.interceptor.bci.InstrumentClass; +import com.profiler.interceptor.bci.InstrumentException; +import com.profiler.modifier.AbstractModifier; + +/** + * + * @author netspider + * + */ +public class HttpServletModifier extends AbstractModifier { + + private final Logger logger = Logger.getLogger(HttpServletModifier.class.getName()); + + public HttpServletModifier(ByteCodeInstrumentor byteCodeInstrumentor, Agent agent) { + super(byteCodeInstrumentor, agent); + } + + public String getTargetClass() { + return "javax/servlet/http/HttpServlet"; + } + + public byte[] modify(ClassLoader classLoader, String javassistClassName, ProtectionDomain protectedDomain, byte[] classFileBuffer) { + if (logger.isLoggable(Level.INFO)) { + logger.info("Modifing. " + javassistClassName); + } + + byteCodeInstrumentor.checkLibrary(classLoader, javassistClassName); + + try { + Interceptor doGetInterceptor = byteCodeInstrumentor.newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.servlet.interceptors.DoXXXInterceptor"); + Interceptor doPostInterceptor = byteCodeInstrumentor.newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.servlet.interceptors.DoXXXInterceptor"); + + setTraceContext(doGetInterceptor); + setTraceContext(doPostInterceptor); + + InstrumentClass servlet = byteCodeInstrumentor.getClass(javassistClassName); + + servlet.addInterceptor("doGet", new String[] { "javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse" }, doGetInterceptor); + servlet.addInterceptor("doPost", new String[] { "javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse" }, doPostInterceptor); + + return servlet.toBytecode(); + } catch (InstrumentException e) { + logger.log(Level.WARNING, "modify fail. Cause:" + e.getMessage(), e); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/profiler/modifier/servlet/SpringFrameworkServletModifier.java b/src/main/java/com/profiler/modifier/servlet/SpringFrameworkServletModifier.java new file mode 100644 index 000000000..f67dec6d1 --- /dev/null +++ b/src/main/java/com/profiler/modifier/servlet/SpringFrameworkServletModifier.java @@ -0,0 +1,56 @@ +package com.profiler.modifier.servlet; + +import java.security.ProtectionDomain; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.profiler.Agent; +import com.profiler.interceptor.Interceptor; +import com.profiler.interceptor.bci.ByteCodeInstrumentor; +import com.profiler.interceptor.bci.InstrumentClass; +import com.profiler.interceptor.bci.InstrumentException; +import com.profiler.modifier.AbstractModifier; + +/** + * + * @author netspider + * + */ +public class SpringFrameworkServletModifier extends AbstractModifier { + + private final Logger logger = Logger.getLogger(SpringFrameworkServletModifier.class.getName()); + + public SpringFrameworkServletModifier(ByteCodeInstrumentor byteCodeInstrumentor, Agent agent) { + super(byteCodeInstrumentor, agent); + } + + public String getTargetClass() { + return "org/springframework/web/servlet/FrameworkServlet"; + } + + public byte[] modify(ClassLoader classLoader, String javassistClassName, ProtectionDomain protectedDomain, byte[] classFileBuffer) { + if (logger.isLoggable(Level.INFO)) { + logger.info("Modifing. " + javassistClassName); + } + + byteCodeInstrumentor.checkLibrary(classLoader, javassistClassName); + + try { + Interceptor doGetInterceptor = byteCodeInstrumentor.newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.servlet.interceptors.DoXXXInterceptor"); + Interceptor doPostInterceptor = byteCodeInstrumentor.newInterceptor(classLoader, protectedDomain, "com.profiler.modifier.servlet.interceptors.DoXXXInterceptor"); + + setTraceContext(doGetInterceptor); + setTraceContext(doPostInterceptor); + + InstrumentClass servlet = byteCodeInstrumentor.getClass(javassistClassName); + + servlet.addInterceptor("doGet", new String[] { "javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse" }, doGetInterceptor); + servlet.addInterceptor("doPost", new String[] { "javax.servlet.http.HttpServletRequest", "javax.servlet.http.HttpServletResponse" }, doPostInterceptor); + + return servlet.toBytecode(); + } catch (InstrumentException e) { + logger.log(Level.WARNING, "modify fail. Cause:" + e.getMessage(), e); + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/profiler/modifier/servlet/interceptors/DoXXXInterceptor.java b/src/main/java/com/profiler/modifier/servlet/interceptors/DoXXXInterceptor.java new file mode 100644 index 000000000..404c63dff --- /dev/null +++ b/src/main/java/com/profiler/modifier/servlet/interceptors/DoXXXInterceptor.java @@ -0,0 +1,174 @@ +package com.profiler.modifier.servlet.interceptors; + +import java.util.Arrays; +import java.util.Enumeration; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.servlet.http.HttpServletRequest; + +import com.profiler.common.ServiceType; +import com.profiler.context.Header; +import com.profiler.context.SpanID; +import com.profiler.context.Trace; +import com.profiler.context.TraceContext; +import com.profiler.context.TraceID; +import com.profiler.interceptor.ApiIdSupport; +import com.profiler.interceptor.ByteCodeMethodDescriptorSupport; +import com.profiler.interceptor.MethodDescriptor; +import com.profiler.interceptor.StaticAroundInterceptor; +import com.profiler.interceptor.TraceContextSupport; +import com.profiler.util.NumberUtils; +import com.profiler.util.StringUtils; + +public class DoXXXInterceptor implements StaticAroundInterceptor, ByteCodeMethodDescriptorSupport, ApiIdSupport, TraceContextSupport { + + private final Logger logger = Logger.getLogger(DoXXXInterceptor.class.getName()); + private MethodDescriptor descriptor; + private int apiId; + private TraceContext traceContext; + + @Override + public void before(Object target, String className, String methodName, String parameterDescription, Object[] args) { + if (logger.isLoggable(Level.INFO)) { + logger.info("before " + StringUtils.toString(target) + " " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args)); + } + + try { + traceContext.getActiveThreadCounter().start(); + + HttpServletRequest request = (HttpServletRequest) args[0]; + String requestURL = request.getRequestURI(); + String clientIP = request.getRemoteAddr(); + + TraceID traceId = populateTraceIdFromRequest(request); + Trace trace; + if (traceId != null) { + TraceID nextTraceId = traceId.getNextTraceId(); + if (logger.isLoggable(Level.INFO)) { + logger.info("TraceID exist. continue trace. " + nextTraceId); + logger.log(Level.FINE, "requestUrl:" + requestURL + " clientIp" + clientIP); + } + trace = new Trace(nextTraceId); + traceContext.attachTraceObject(trace); + } else { + trace = new Trace(); + if (logger.isLoggable(Level.INFO)) { + logger.info("TraceID not exist. start new trace. " + trace.getTraceId()); + logger.log(Level.FINE, "requestUrl:" + requestURL + " clientIp" + clientIP); + } + traceContext.attachTraceObject(trace); + } + + trace.markBeforeTime(); + trace.recordRpcName(ServiceType.TOMCAT, traceContext.getApplicationId(), requestURL); + + int port = request.getServerPort(); + trace.recordEndPoint(request.getProtocol() + ":" + request.getServerName() + ((port > 0) ? ":" + port : "")); + trace.recordAttribute("http.url", request.getRequestURI()); + } catch (Exception e) { + if (logger.isLoggable(Level.WARNING)) { + logger.log(Level.WARNING, "Tomcat StandardHostValve trace start fail. Caused:" + e.getMessage(), e); + } + } + } + + @Override + public void after(Object target, String className, String methodName, String parameterDescription, Object[] args, Object result) { + if (logger.isLoggable(Level.INFO)) { + logger.info("after " + StringUtils.toString(target) + " " + className + "." + methodName + parameterDescription + " args:" + Arrays.toString(args) + " result:" + result); + } + + traceContext.getActiveThreadCounter().end(); + Trace trace = traceContext.currentTraceObject(); + if (trace == null) { + return; + } + traceContext.detachTraceObject(); + + HttpServletRequest request = (HttpServletRequest) args[0]; + String parameters = getRequestParameter(request); + if (parameters != null && parameters.length() > 0) { + trace.recordAttribute("http.params", parameters); + } + + + if (trace.getCurrentStackFrame().getStackFrameId() != 0) { + logger.warning("Corrupted CallStack found. StackId not Root(0)"); + // 문제 있는 callstack을 dump하면 도움이 될듯. + } + + // trace.recordApi(descriptor); + trace.recordApi(this.apiId); + + trace.recordException(result); + + trace.markAfterTime(); + trace.traceBlockEnd(); + } + + /** + * Pupulate source trace from HTTP Header. + * + * @param request + * @return + */ + private TraceID populateTraceIdFromRequest(HttpServletRequest request) { + String strUUID = request.getHeader(Header.HTTP_TRACE_ID.toString()); + if (strUUID != null) { + UUID uuid = UUID.fromString(strUUID); + long parentSpanID = NumberUtils.parseLong(request.getHeader(Header.HTTP_PARENT_SPAN_ID.toString()), SpanID.NULL); + long spanID = NumberUtils.parseLong(request.getHeader(Header.HTTP_SPAN_ID.toString()), SpanID.NULL); + boolean sampled = Boolean.parseBoolean(request.getHeader(Header.HTTP_SAMPLED.toString())); + short flags = NumberUtils.parseShort(request.getHeader(Header.HTTP_FLAGS.toString()), (short) 0); + + TraceID id = new TraceID(uuid, parentSpanID, spanID, sampled, flags); + if (logger.isLoggable(Level.INFO)) { + logger.info("TraceID exist. continue trace. " + id); + } + return id; + } else { + return null; + } + } + + private String getRequestParameter(HttpServletRequest request) { + Enumeration attrs = request.getParameterNames(); + StringBuilder params = new StringBuilder(); + + while (attrs.hasMoreElements()) { + String keyString = attrs.nextElement().toString(); + Object value = request.getParameter(keyString); + + if (value != null) { + String valueString = value.toString(); + int valueStringLength = valueString.length(); + + if (valueStringLength > 0 && valueStringLength < 100) { + params.append(keyString).append("=").append(valueString); + } + + if (attrs.hasMoreElements()) { + params.append(", "); + } + } + } + return params.toString(); + } + + @Override + public void setMethodDescriptor(MethodDescriptor descriptor) { + this.descriptor = descriptor; + } + + @Override + public void setApiId(int apiId) { + this.apiId = apiId; + } + + @Override + public void setTraceContext(TraceContext traceContext) { + this.traceContext = traceContext; + } +}