在上一篇博客 《Zipkin原理學習–日誌追蹤 MySQL 執行語句》 中我們已經瞭解學習到 Zipkin 官方提供的針對 MySQL 數據庫 sql 語句執行的追蹤攔截器,現在我們基於數據庫連接池 Druid 的 Filter 機制 寫一個能支持多種數據庫(mysql,pg、oracle 等)日誌追蹤攔截器。
Druid 過濾器 Filter
DruidDataSource支持通過Filter-Chain模式進行擴展,類似Serlvet的Filter,擴展十分方便,你可以攔截任何JDBC的方法。
有兩種配置Filter的方式,一種是配置filters屬性,一種是配置proxyFilters屬性。filters和proxyFilters的配置是組合關係,而不是替換關係。
配置filters屬性
配置filters屬性比較簡單,filters的類型是字符串,多個filter使用逗號隔開。例如:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="jdbc:derby:memory:spring-test;create=true" />
<property name="initialSize" value="1" />
<property name="maxActive" value="20" />
<property name="filters" value="stat,log4j" />
</bean>
filters屬性的配置使用別名或者全類名,stat是com.alibaba.druid.filter.stat.StatFilter的別名。 內置Filter的別名 查看內置Filter的別名。
配置proxyFilters屬性
proxyFilters的類型是List,使用proxyFilters配置,可以有更多的配置選項。
<bean id="stat-filter" class="com.alibaba.druid.filter.stat.StatFilter">
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="jdbc:derby:memory:spring-test;create=true" />
<property name="initialSize" value="1" />
<property name="maxActive" value="20" />
<property name="proxyFilters">
<list>
<ref bean="stat-filter" />
</list>
</property>
</bean>
Zipkin 日誌追蹤過濾器 TracingStatementFilter
TracingStatementFilter 繼承抽象類 FilterEventAdapter 實現以下接口:
protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
}
protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
}
protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
}
protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
}
protected void statementExecuteBefore(StatementProxy statement, String sql) {
}
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {
}
protected void statementExecuteBatchBefore(StatementProxy statement) {
}
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
}
protected void statement_executeErrorAfter(StatementProxy statement, String sql, Throwable error) {
}
實現以上接口,在執行SQL語句之前、之後或拋出異常時都會執行上面接口方法,這樣就可以追蹤 SQL 語句的執行過程了。
完整 Zipkin 日誌追蹤代碼:
public class TracingStatementFilter extends FilterEventAdapter {
private String zipkinServiceName;
public TracingStatementFilter(String zipkinServiceName){
this.zipkinServiceName = zipkinServiceName;
}
public TracingStatementFilter(){
}
@Override
protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
super.statementExecuteUpdateBefore(statement,sql);
try {
Before(statement,sql);
}catch (Exception e){
}
}
@Override
protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
super.statementExecuteUpdateAfter(statement,sql,updateCount);
try {
After(statement,sql);
}catch (Exception e){
}
}
@Override
protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
super.statementExecuteQueryBefore(statement,sql);
try {
Before(statement,sql);
}catch (Exception e){
}
}
@Override
protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
super.statementExecuteQueryAfter(statement,sql,resultSet);
try {
After(statement,sql);
}catch (Exception e){
}
}
@Override
protected void statementExecuteBefore(StatementProxy statement, String sql) {
super.statementExecuteBefore(statement,sql);
try {
Before(statement,sql);
}catch (Exception e){
}
}
@Override
protected void statementExecuteAfter(StatementProxy statement, String sql, boolean result) {
super.statementExecuteAfter(statement,sql,result);
try {
After(statement,sql);
}catch (Exception e){
}
}
@Override
protected void statementExecuteBatchBefore(StatementProxy statement) {
super.statementExecuteBatchBefore(statement);
try {
Before(statement , null);
}catch (Exception e){
}
}
@Override
protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
super.statementExecuteBatchAfter(statement , result);
try {
After(statement , null);
}catch (Exception e){
}
}
@Override
protected void statement_executeErrorAfter(StatementProxy statement, String sql, Throwable error) {
super.statement_executeErrorAfter(statement , sql , error);
try {
ErrorAfter(statement , sql , error);
}catch (Exception e){
}
}
protected void Before(StatementProxy statement, String sql) {
Span span = ThreadLocalSpan.CURRENT_TRACER.next();
if (span == null || span.isNoop()) {
return;
}
if(sql == null){
sql = statement.getLastExecuteSql();
}
// Allow span names of single-word statements like COMMIT
int spaceIndex = sql.indexOf(' ');
span.kind(Span.Kind.CLIENT).name(spaceIndex == -1 ? sql : sql.substring(0, spaceIndex));
span.tag("sql.query", sql);
parseServerIpAndPort(statement,span);
span.start();
}
protected void After(StatementProxy statement, String sql) {
Span span = ThreadLocalSpan.CURRENT_TRACER.remove();
if (span == null || span.isNoop()) {
return;
}
span.finish();
return ;
}
protected void ErrorAfter(StatementProxy statement, String sql, Throwable error) {
Span span = ThreadLocalSpan.CURRENT_TRACER.remove();
if (span == null || span.isNoop()) {
return;
}
if (error instanceof SQLException) {
span.tag("error", Integer.toString(((SQLException) error).getErrorCode()));
}
span.finish();
return ;
}
public void parseServerIpAndPort(StatementProxy statement, Span span) {
try {
URI url = URI.create(statement.getConnection().getMetaData().getURL().substring(5));
if (getZipkinServiceName() == null || "".equals(getZipkinServiceName())) {
try {
zipkinServiceName = "DB"+url.getPath();
}catch (Exception e){
;
}
}
span.remoteServiceName(getZipkinServiceName());
String host = url.getHost();
if (host != null) {
span.remoteIpAndPort(host, url.getPort());
}
} catch (Exception e) {
// remote address is optional
}
}
public String getZipkinServiceName() {
return zipkinServiceName;
}
public void setZipkinServiceName(String zipkinServiceName) {
this.zipkinServiceName = zipkinServiceName;
}
}
總結:
簡單來說就是利用 Druid 提供的過濾器機制,在 SQL 語句執行前後添加追蹤日誌。