skywalking的相关概念我就不介绍了,有兴趣可以参看官网文档
以下提供以下简单示例手工上报一些对问题排查比较有用的一些信息。当然这些内容你也可以写成探针插件的形式,怎么开发探针插件也自行参考官方文档。此处仅在项目框架层面提供一些简单的示例,助于你快速入门。
手动上报异常信息
一般我们在项目中都会做全局异常拦截处理,最早我的方案是在gateway统一拦截封装,这样skywalking的agent组件就会不会到对应的异常信息进行上报到skywalkingserver端。但是接手了个没有gateway的微服务项目,统一由nginx进行转发,此时我们如果不将异常收敛在当前服务处理,异常信息势必会被抛到前端页面,显然是很不友好的行为。所以此时我们就得在当前服务内处理skywalking的异常信息上报。
正常我们的全局异常拦截处理类如下
@RestControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class GlobalExceptionHandler implements ExcetionHandler {
@ExceptionHandler(value = Exception.class)
public JsonResult unhandledException(HttpServletResponse resp, Exception e){
log.error("未处理异常:", e);
JsonResult result = new JsonResult();
result.setMessage(ErrorCode.UNCATCH_EXCEPTION.getMsg());
result.setCode(ErrorCode.UNCATCH_EXCEPTION.getCode());
return result;
}
}
此时由于异常被收敛在这个类统一处理,所以我们可以写个切面横切这个类的所有方法,拿到异常信息手动上报给skywalking组件,当然也可以写出插件的形式。不用在框架代码中手动上报。
切面代码如下:
@Aspect
public class ExceptionHandlerAspect {
@Around(value = "@annotation(org.springframework.web.bind.annotation.ExceptionHandler)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof Throwable) {
ActiveSpan.error((Throwable)arg);
}
}
return joinPoint.proceed();
}
}
是不是很简单。此处我们处理拦截异常之外,我们还可以对sql进行拦截,并将sql信息补全之后也统一上报给skywalking。这样出现业务问题时假如没有异常而是数据等问题,我们很容易可以通过链路追踪到对应的sql信息,除了sql我们还可以跟踪mq信息缓存信息等等都是一样的道理
手动上报sql信息
sql上报,sql上报我们可以写个sql拦截器,拦截statmentHander阶段或者拦截ParameterHandler阶段这两个阶段都是可以进行sql补全的。
ParameterHandler代码如下
@Intercepts({
@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class})
}
)
public class MybatisLogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
PreparedStatement statement = (PreparedStatement) invocation.getArgs()[0];
PreparedStatement sql = null;
Object proceed = invocation.proceed();
if (Proxy.isProxyClass(statement.getClass())) {
InvocationHandler handler = Proxy.getInvocationHandler(statement);
if (handler.getClass().getName().endsWith(".PreparedStatementLogger")) {
Field field = handler.getClass().getDeclaredField("statement");
field.setAccessible(true);
sql = (PreparedStatement) field.get(handler);
}
}
ActiveSpan.tag(SpanConstant.ORM_TYPE, "mybatis");
ActiveSpan.tag(SpanConstant.SQL_STATEMENT, sql);
return proceed;
}
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
StatmentHander代码如下
@Intercepts(
@Signature(
type = StatementHandler.class,
method = "parameterize",
args = Statement.class
)
)
public class MybatisTraceInterceptor implements Interceptor {
private final static Logger log= LoggerFactory.getLogger(MybatisTraceInterceptor.class);
public static <T> T realTarget(Object target) {
if (Proxy.isProxyClass(target.getClass())) {
MetaObject metaObject = SystemMetaObject.forObject(target);
return realTarget(metaObject.getValue("h"));
}
return (T) target;
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object proceed = null;
String sql=null;
Statement statement = (Statement) invocation.getArgs()[0];
proceed = invocation.proceed();
try {
PreparedStatementLogger psl = realTarget(statement);
PreparedStatement ps = psl.getPreparedStatement();
String pss = ps.toString();
sql = pss.substring(pss.indexOf(":") + 1);
ActiveSpan.tag(SpanConstant.ORM_TYPE, "mybatis");
ActiveSpan.tag(SpanConstant.SQL_STATEMENT, sql);
}catch (Exception e){
log.error("sql上报skyWalking异常",e.getMessage());
}
return proceed;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
手动上报出入参信息
这里同样有两种方案,一个使用切面的方式进行处理,一个使用Filter或者拦截器的方式进行处理,但是后两个阶段存在一个问题就是流的读取问题,所以如果选择后两者进行操作,还需要进行流的缓存封装,是比较不可取的方案。代码都差不多,这个我就不贴了。
注意:
过滤器和拦截器一般我们用来处理头部信息会比较好点。