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