/x-ray

一种监听方法执行信息的agent

Primary LanguageJavaApache License 2.0Apache-2.0

x-ray

解决问题

  • 你们项目依赖是否环境复杂,经常没有本地环境可供调试
  • 使用remote debug经常阻塞他人请求
  • 使用arthas watch命令无法获得局部变量的变量值

解决思路

  1. 类似debug我想要看到当前执行方法内的各个参数的信息
  2. 方便快速watch对应方法,并看到方法执行的结果

解决问题1,字节码插桩,在进入方法执行前和执行后分别插入方法,记录执行前后信息

public void test(Object request) {
    methodEnter();
    try {
        // do something ...
        methodExit();
    } catch (Exception exception) {
        methodException();
    }
}

解决问题2,塞入一个轻量级http服务器,使用接口watch方法和取得对应执行信息

new NanoHTTPD(port).start(5000, false);

配置

-javaagent:/x-ray-agent.jar=X_RAY_INCLUDES=xxx.xxx

agent启动参数

必填

X_RAY_INCLUDES=<需要扫描的包名eg: xxx.xxx>

选填 (默认8000)

X_RAY_PORT=<xray.http端口号>

日志

默认目录: ~/logs/x-ray/x-ray.log

接口

watch接口 (GET)

功能:监听需要watch的方法

参数:watchName<需要被watch的方法>

格式:<类名>#<方法名>

curl --request GET \
  --url 'http://127.0.0.1:8000/xray/watch?watchName=io.github.x.ray.xrayspringbootstarter.controller.CheckController%23method1' \
  --header 'User-Agent: insomnia/8.4.2'

入参

watchName=io.github.x.ray.xrayspringbootstarter.controller.CheckController#method1

出参

watch success

watch-list接口 (GET)

功能:查看当前监听的方法集合 参数:无

curl --request GET \
  --url http://127.0.0.1:8000/xray/watch/list \
  --header 'User-Agent: insomnia/8.4.2'

出参

[
  "io.github.x.ray.xrayspringbootstarter.controller.CheckController#method1"
]

list接口 (GET)

功能:查看监听方法的具体执行信息 参数:watchName<被watch的方法> 格式:<类名>#<方法名>

curl --request GET \
  --url 'http://127.0.0.1:8000/xray/list?watchName=io.github.x.ray.xrayspringbootstarter.controller.CheckController%23method1' \
  --header 'User-Agent: insomnia/8.4.2'

入参

watchName=io.github.x.ray.xrayspringbootstarter.controller.CheckController#method1

response结构

methodName: 方法名称
enter: 方法执行入口
    req: 方法入参
exit: 方法执行出口
    lineNumber: 方法出行数
    localVars: 方法内局部变量信息
    returnVar: 方法出参信息
    exceptionMsg: 异常信息
    exceptionStack: 异常堆栈

正常执行出参

[
  {
    "clazz": "io.github.x.ray.xrayspringbootstarter.controller.CheckController",
    "enter": {
      "methodVars": {
        "req": {
          "poCode": "123421",
          "poName": "测试res",
          "poType": 12138972,
          "requestId": "idgsahasgd"
        }
      }
    },
    "exit": {
      "lineNumber": 55,
      "localVars": {
        "req": {
          "poCode": "123421",
          "poName": "测试res",
          "poType": 12138972,
          "requestId": "idgsahasgd"
        },
        "i": 1,
        "j": 2,
        "sum": 3
      },
      "returnVar": "{\"poCode\":\"123421\",\"poName\":\"测试res\",\"poType\":12138972,\"requestId\":\"idgsahasgd\"}"
    },
    "methodDesc": "(Lio/github/x/ray/xrayspringbootstarter/model/XRayTestModel;)Lio/github/x/ray/xrayspringbootstarter/model/XRayTestModel;",
    "methodName": "method1"
  }
]

异常返回出参

[
  {
    "clazz": "io.github.x.ray.xrayspringbootstarter.controller.CheckController",
    "enter": {
      "methodVars": {
        "req": {
          "poCode": "123",
          "poName": "xxx",
          "poType": 123
        }
      }
    },
    "exit": {
      "exceptionMsg": "TEST EXCEPTION...",
      "exceptionStack": [
        {
          "fileName": "CheckController.java",
          "lineNumber": 49,
          "className": "io.github.x.ray.xrayspringbootstarter.controller.CheckController",
          "methodName": "method1"
        },
        {
          "fileName": "NativeMethodAccessorImpl.java",
          "lineNumber": -2,
          "className": "jdk.internal.reflect.NativeMethodAccessorImpl",
          "methodName": "invoke0"
        },
        {
          "fileName": "NativeMethodAccessorImpl.java",
          "lineNumber": 62,
          "className": "jdk.internal.reflect.NativeMethodAccessorImpl",
          "methodName": "invoke"
        },
        {
          "fileName": "DelegatingMethodAccessorImpl.java",
          "lineNumber": 43,
          "className": "jdk.internal.reflect.DelegatingMethodAccessorImpl",
          "methodName": "invoke"
        },
        {
          "fileName": "Method.java",
          "lineNumber": 566,
          "className": "java.lang.reflect.Method",
          "methodName": "invoke"
        },
        {
          "fileName": "InvocableHandlerMethod.java",
          "lineNumber": 205,
          "className": "org.springframework.web.method.support.InvocableHandlerMethod",
          "methodName": "doInvoke"
        },
        {
          "fileName": "InvocableHandlerMethod.java",
          "lineNumber": 150,
          "className": "org.springframework.web.method.support.InvocableHandlerMethod",
          "methodName": "invokeForRequest"
        },
        {
          "fileName": "ServletInvocableHandlerMethod.java",
          "lineNumber": 117,
          "className": "org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod",
          "methodName": "invokeAndHandle"
        },
        {
          "fileName": "RequestMappingHandlerAdapter.java",
          "lineNumber": 895,
          "className": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter",
          "methodName": "invokeHandlerMethod"
        },
        {
          "fileName": "RequestMappingHandlerAdapter.java",
          "lineNumber": 808,
          "className": "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter",
          "methodName": "handleInternal"
        },
        {
          "fileName": "AbstractHandlerMethodAdapter.java",
          "lineNumber": 87,
          "className": "org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter",
          "methodName": "handle"
        },
        {
          "fileName": "DispatcherServlet.java",
          "lineNumber": 1072,
          "className": "org.springframework.web.servlet.DispatcherServlet",
          "methodName": "doDispatch"
        },
        {
          "fileName": "DispatcherServlet.java",
          "lineNumber": 965,
          "className": "org.springframework.web.servlet.DispatcherServlet",
          "methodName": "doService"
        },
        {
          "fileName": "FrameworkServlet.java",
          "lineNumber": 1006,
          "className": "org.springframework.web.servlet.FrameworkServlet",
          "methodName": "processRequest"
        },
        {
          "fileName": "FrameworkServlet.java",
          "lineNumber": 909,
          "className": "org.springframework.web.servlet.FrameworkServlet",
          "methodName": "doPost"
        },
        {
          "fileName": "HttpServlet.java",
          "lineNumber": 555,
          "className": "javax.servlet.http.HttpServlet",
          "methodName": "service"
        },
        {
          "fileName": "FrameworkServlet.java",
          "lineNumber": 883,
          "className": "org.springframework.web.servlet.FrameworkServlet",
          "methodName": "service"
        },
        {
          "fileName": "HttpServlet.java",
          "lineNumber": 623,
          "className": "javax.servlet.http.HttpServlet",
          "methodName": "service"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 209,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "internalDoFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 153,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "doFilter"
        },
        {
          "fileName": "WsFilter.java",
          "lineNumber": 51,
          "className": "org.apache.tomcat.websocket.server.WsFilter",
          "methodName": "doFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 178,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "internalDoFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 153,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "doFilter"
        },
        {
          "fileName": "RequestContextFilter.java",
          "lineNumber": 100,
          "className": "org.springframework.web.filter.RequestContextFilter",
          "methodName": "doFilterInternal"
        },
        {
          "fileName": "OncePerRequestFilter.java",
          "lineNumber": 117,
          "className": "org.springframework.web.filter.OncePerRequestFilter",
          "methodName": "doFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 178,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "internalDoFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 153,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "doFilter"
        },
        {
          "fileName": "FormContentFilter.java",
          "lineNumber": 93,
          "className": "org.springframework.web.filter.FormContentFilter",
          "methodName": "doFilterInternal"
        },
        {
          "fileName": "OncePerRequestFilter.java",
          "lineNumber": 117,
          "className": "org.springframework.web.filter.OncePerRequestFilter",
          "methodName": "doFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 178,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "internalDoFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 153,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "doFilter"
        },
        {
          "fileName": "CharacterEncodingFilter.java",
          "lineNumber": 201,
          "className": "org.springframework.web.filter.CharacterEncodingFilter",
          "methodName": "doFilterInternal"
        },
        {
          "fileName": "OncePerRequestFilter.java",
          "lineNumber": 117,
          "className": "org.springframework.web.filter.OncePerRequestFilter",
          "methodName": "doFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 178,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "internalDoFilter"
        },
        {
          "fileName": "ApplicationFilterChain.java",
          "lineNumber": 153,
          "className": "org.apache.catalina.core.ApplicationFilterChain",
          "methodName": "doFilter"
        },
        {
          "fileName": "StandardWrapperValve.java",
          "lineNumber": 168,
          "className": "org.apache.catalina.core.StandardWrapperValve",
          "methodName": "invoke"
        },
        {
          "fileName": "StandardContextValve.java",
          "lineNumber": 90,
          "className": "org.apache.catalina.core.StandardContextValve",
          "methodName": "invoke"
        },
        {
          "fileName": "AuthenticatorBase.java",
          "lineNumber": 481,
          "className": "org.apache.catalina.authenticator.AuthenticatorBase",
          "methodName": "invoke"
        },
        {
          "fileName": "StandardHostValve.java",
          "lineNumber": 130,
          "className": "org.apache.catalina.core.StandardHostValve",
          "methodName": "invoke"
        },
        {
          "fileName": "ErrorReportValve.java",
          "lineNumber": 93,
          "className": "org.apache.catalina.valves.ErrorReportValve",
          "methodName": "invoke"
        },
        {
          "fileName": "StandardEngineValve.java",
          "lineNumber": 74,
          "className": "org.apache.catalina.core.StandardEngineValve",
          "methodName": "invoke"
        },
        {
          "fileName": "CoyoteAdapter.java",
          "lineNumber": 342,
          "className": "org.apache.catalina.connector.CoyoteAdapter",
          "methodName": "service"
        },
        {
          "fileName": "Http11Processor.java",
          "lineNumber": 390,
          "className": "org.apache.coyote.http11.Http11Processor",
          "methodName": "service"
        },
        {
          "fileName": "AbstractProcessorLight.java",
          "lineNumber": 63,
          "className": "org.apache.coyote.AbstractProcessorLight",
          "methodName": "process"
        },
        {
          "fileName": "AbstractProtocol.java",
          "lineNumber": 928,
          "className": "org.apache.coyote.AbstractProtocol$ConnectionHandler",
          "methodName": "process"
        },
        {
          "fileName": "NioEndpoint.java",
          "lineNumber": 1794,
          "className": "org.apache.tomcat.util.net.NioEndpoint$SocketProcessor",
          "methodName": "doRun"
        },
        {
          "fileName": "SocketProcessorBase.java",
          "lineNumber": 52,
          "className": "org.apache.tomcat.util.net.SocketProcessorBase",
          "methodName": "run"
        },
        {
          "fileName": "ThreadPoolExecutor.java",
          "lineNumber": 1191,
          "className": "org.apache.tomcat.util.threads.ThreadPoolExecutor",
          "methodName": "runWorker"
        },
        {
          "fileName": "ThreadPoolExecutor.java",
          "lineNumber": 659,
          "className": "org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker",
          "methodName": "run"
        },
        {
          "fileName": "TaskThread.java",
          "lineNumber": 61,
          "className": "org.apache.tomcat.util.threads.TaskThread$WrappingRunnable",
          "methodName": "run"
        },
        {
          "fileName": "Thread.java",
          "lineNumber": 834,
          "className": "java.lang.Thread",
          "methodName": "run"
        }
      ],
      "localVars": {},
      "methodVars": {
        "req": {
          "poCode": "123",
          "poName": "xxx",
          "poType": 123
        }
      }
    },
    "methodDesc": "(Lio/github/x/ray/xrayspringbootstarter/model/XRayTestModel;)Lio/github/x/ray/xrayspringbootstarter/model/XRayTestModel;",
    "methodName": "method1"
  }
]

list-clear接口 (GET)

功能:清除对应方法的具体执行信息 参数:watchName<被watch的方法> 格式:<类名>#<方法名>

curl --request GET \
  --url 'http://127.0.0.1:8000/xray/list/clear?watchName=io.github.x.ray.xrayspringbootstarter.controller.CheckController%23method1' \
  --header 'User-Agent: insomnia/8.4.2'

入参

watchName=io.github.x.ray.xrayspringbootstarter.controller.CheckController#method1

出参

clear success