对Sentry功能扩充方法实践

402 阅读3分钟

通过logback接入sentry,logback提供的原生ILoggingEvent对象相比于EventBuilder携带的信息也是有些不足的,而sentry对它进行了一定的增强 上一篇文章已经说了那些属性分别代表什么,这么就看看究竟如何进行增强EventBuilder 下面代码可以看到这个方法处于发送到Sentry服务器上时对EventBuilder的增强,框架默认就两个增强器ContextBuilderHelper,HttpEventBuilderHelper他们都实现了EventBuilderHelpervoid helpBuildingEvent(EventBuilder eventBuilder);方法 所以下面的循环也是在调用的实现方法。

public void runBuilderHelpers(EventBuilder eventBuilder) {
        for (EventBuilderHelper builderHelper : builderHelpers) {
            builderHelper.helpBuildingEvent(eventBuilder);
        }
}

如果这个时候又有其他需求,比如发个邮件或者添加一些属性信息,那么就可以如下代码:

public class DemoEventBuilderHelper implements EventBuilderHelper {
    @Override
    public void helpBuildingEvent(EventBuilder eventBuilder) {
        //一顿操作
        eventBuilder.withRelease("1.0.0");
    }
}

那么究竟怎么把手写的helper加进builderHelpers里面呢,其实他是在初始化SentryClient的时候就已经确定了,所以我们在来看看初始化的代码:

@Override
    public SentryClient createSentryClient(Dsn dsn) {
        SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn));
        //此方法就是对builderHelpers集合进行add
        sentryClient.addBuilderHelper(new HttpEventBuilderHelper());
        sentryClient.addBuilderHelper(new ContextBuilderHelper(sentryClient));
        return configureSentryClient(sentryClient, dsn);

因为我们想要自己对EventBuilder进行增强,所以得控制创建SentryClient的逻辑而不用默认的DefaultSentryClientFactory所以该这样操作:

public class DemoSentryClientFactory extends DefaultSentryClientFactory {
    @Override
    public SentryClient createSentryClient(Dsn dsn) {
        SentryClient sentryClient = new SentryClient(createConnection(dsn), getContextManager(dsn));
        //一顿骚操作,把刚刚写好的helper添加进来
        sentryClient.addBuilderHelper(new DemoEventBuilderHelper());
        return sentryClient;
    }
}

逻辑已经写好了,怎么让框架自动加载我们写好的DemoSentryClientFactory类呢,String sentryClientFactoryName = Lookup.lookup("factory", realDsn);这就是框架已经为我们预留好的接口,只要让他查找到factory这个key那么他就会通过全类名进行反射实例化,

public static SentryClient sentryClient(String dsn, SentryClientFactory sentryClientFactory) {
        //把数据源切割成Dsn对象
        Dsn realDsn = resolveDsn(dsn);

        //为空的是时候
        if (sentryClientFactory == null) {
            //查找key为factory的类,留的可扩展接口
            String sentryClientFactoryName = Lookup.lookup("factory", realDsn);
            if (Util.isNullOrEmpty(sentryClientFactoryName)) {
                // 没有找到类就new一个默认工厂来创建SentryClient
                sentryClientFactory = new DefaultSentryClientFactory();
            } else {
                // 找到key就通过类名反射实例化一个
                Class<? extends SentryClientFactory> factoryClass = null;
                try {
                    factoryClass = (Class<? extends SentryClientFactory>) Class.forName(sentryClientFactoryName);
                    sentryClientFactory = factoryClass.newInstance();
                } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                    logger.error("Error creating SentryClient using factory class: '"
                        + sentryClientFactoryName + "'.", e);
                    return null;
                }
            }
        }
        //拿到Dsn创建SentryClient
        return sentryClientFactory.createSentryClient(realDsn);
    }

想让他获取到key我们得先配置,在resource资源目录下新建sentry.properties配置文件,这也是我上一篇提到过的几种参数配置方式之一。

dsn=http://e32a375c1ffd4dc0bc7510a65dba3143:928fedbef8b440b3bd2fe5dca2d8b56f@172.16.0.132:9000/50
factory=cn.yzw.superbox.swms.api.config.DemoSentryClientFactory

这个时候就可以实例化自己的工厂。 ##Spring Boot HTTP数据 Spring Boot不会自动加载任何内容javax.servlet.ServletContainerInitializer,这意味着Sentry SDK没有机会挂钩请求周期来收集有关HTTP请求的信息。为了在Spring Boot中向Sentry事件添加HTTP请求数据,需要io.sentry.spring.SentryServletContextInitializer在应用程序中将该类注册为Bean。

<dependency>
    <groupId>io.sentry</groupId>
    <artifactId>sentry-spring</artifactId>
    <version>1.7.27</version>
</dependency>
@Bean
public ServletContextInitializer sentryServletContextInitializer() {
    return new io.sentry.spring.SentryServletContextInitializer();
}

来看一下里面写的什么内容

public class SentryServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        ctx.addListener(SentryServletRequestListener.class);
    }
}

依然还是用的TheadLocal为每一个线程存放属于自己的request,在request的生命周期结束时,可以看到就是清理了上下文,这些逻辑都是可以根据自己需求重写的,获取request的意义可不仅仅如此,通过request就可以在eventBuilder里面添加很多用户信息以及上下文信息跟踪。

public class SentryServletRequestListener implements ServletRequestListener {
    private static final Logger logger = LoggerFactory.getLogger(SentryServletRequestListener.class);

    private static final ThreadLocal<HttpServletRequest> THREAD_REQUEST = new ThreadLocal<>();

    public static HttpServletRequest getServletRequest() {
        return THREAD_REQUEST.get();
    }

    @Override
    public void requestDestroyed(ServletRequestEvent servletRequestEvent) {
        THREAD_REQUEST.remove();

        try {
            SentryClient sentryClient = Sentry.getStoredClient();
            if (sentryClient != null) {
                sentryClient.clearContext();
            }
        } catch (Exception e) {
            logger.error("Error clearing Context state.", e);
        }
    }

    @Override
    public void requestInitialized(ServletRequestEvent servletRequestEvent) {
        ServletRequest servletRequest = servletRequestEvent.getServletRequest();
        if (servletRequest instanceof HttpServletRequest) {
            THREAD_REQUEST.set((HttpServletRequest) servletRequest);
        }
    }
}

上面那种方法是官网给出的,经过我的实验官网介绍那种方法可能因为版本比较高还是什么原因,总是监听器没生效,所以我们可以换一种简单粗暴的方式,完美。

@Bean
public SentryServletRequestListener sentryServletRequestListener() {     
        return new SentryServletRequestListener();
}