diff --git a/src/main/java/neatlogic/framework/asynchronization/threadlocal/InterceptorContext.java b/src/main/java/neatlogic/framework/asynchronization/threadlocal/InterceptorContext.java new file mode 100644 index 0000000000000000000000000000000000000000..ab8bb9df8078e626ca9d83f61684126322e65de5 --- /dev/null +++ b/src/main/java/neatlogic/framework/asynchronization/threadlocal/InterceptorContext.java @@ -0,0 +1,73 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +package neatlogic.framework.asynchronization.threadlocal; + +import org.apache.ibatis.mapping.MappedStatement; + +import java.io.Serial; +import java.io.Serializable; + +public class InterceptorContext implements Serializable { + + private static final ThreadLocal instance = new ThreadLocal<>(); + @Serial + private static final long serialVersionUID = -5420998728515359636L; + + private MappedStatement mappedStatement; + + private Object parameter; + // 判断是否查询了数据库 + private Boolean queryFromDatabase; + + private InterceptorContext() { + + } + + public static InterceptorContext init() { + InterceptorContext interceptorContext = new InterceptorContext(); + instance.set(interceptorContext); + return instance.get(); + } + + public static InterceptorContext get() { + return instance.get(); + } + + public void release() { + instance.remove(); + } + + public MappedStatement getMappedStatement() { + return mappedStatement; + } + + public void setMappedStatement(MappedStatement mappedStatement) { + this.mappedStatement = mappedStatement; + } + + public Object getParameter() { + return parameter; + } + + public void setParameter(Object parameter) { + this.parameter = parameter; + } + + public Boolean getQueryFromDatabase() { + return queryFromDatabase; + } + + public void setQueryFromDatabase(Boolean queryFromDatabase) { + this.queryFromDatabase = queryFromDatabase; + } +} diff --git a/src/main/java/neatlogic/framework/common/config/Config.java b/src/main/java/neatlogic/framework/common/config/Config.java index 4ed2d325af5a5d6d42a8c6bbd25044cf8a5775ed..ceed926a7bbc7026508c5b6d0e3a4527b9f86b28 100644 --- a/src/main/java/neatlogic/framework/common/config/Config.java +++ b/src/main/java/neatlogic/framework/common/config/Config.java @@ -54,7 +54,7 @@ public class Config { private static String DB_HOST; private static Integer DB_PORT; private static String DB_URL; - private static String DB_TRANSACTION_TIMEOUT;// 事务超时时间 + private static Integer DB_TRANSACTION_TIMEOUT;// 事务超时时间 private static int DATASOURCE_CONNECT_TIMEOUT;//连接池连接超时时间 private static Integer DATASOURCE_MAXIMUM_POOL_SIZE;//连接数 private static Integer DATASOURCE_MAX_LIFETIME;//控制池中连接的最大生存期 @@ -62,6 +62,7 @@ public class Config { private static Integer DATASOURCE_VALIDATION_TIMEOUT;//此属性控制测试连接是否活跃的最长时间。此值必须小于 connectionTimeout private static Integer DATASOURCE_IDLE_TIMEOUT;//此属性控制允许连接在池中处于空闲状态的最长时间 private static Long DATASOURCE_KEEPALIVE_TIME;//此属性控制允许连接在池中心跳时间,不能比DATASOURCE_MAX_LIFETIME大 + private static Integer DATASOURCE_EXCEPTION_AUDIT; // 开启数据库连接获取异常日志 private static String DATA_HOME;// 存储文件路径 private static String AUDIT_HOME;// 审计日志存储文件路径 private static int SERVER_HEARTBEAT_RATE;// 心跳频率 @@ -249,7 +250,7 @@ public class Config { return DB_URL; } - public static String DB_TRANSACTION_TIMEOUT() {// root-context.xml中使用了该变量 + public static Integer DB_TRANSACTION_TIMEOUT() {// root-context.xml中使用了该变量 return DB_TRANSACTION_TIMEOUT; } @@ -281,6 +282,10 @@ public class Config { return DATASOURCE_IDLE_TIMEOUT; } + public static Integer DATASOURCE_EXCEPTION_AUDIT() { + return DATASOURCE_EXCEPTION_AUDIT; + } + public static String JMS_URL() { return JMS_URL; } @@ -628,7 +633,7 @@ public class Config { USER_EXPIRETIME = prop.getProperty("user.expiretime", "60"); LOGIN_CAPTCHA_EXPIRED_TIME = Integer.parseInt(prop.getProperty("login.captcha.expired.time", "60")); LOGIN_FAILED_TIMES_CAPTCHA = Integer.parseInt(prop.getProperty("login.failed.times.captcha", "3")); - DB_TRANSACTION_TIMEOUT = prop.getProperty("db.transaction.timeout"); + DB_TRANSACTION_TIMEOUT = Integer.parseInt(prop.getProperty("db.transaction.timeout")); DATASOURCE_CONNECT_TIMEOUT = Integer.parseInt(prop.getProperty("datasource.connect.timeout", "5000")); DATASOURCE_MAXIMUM_POOL_SIZE = Integer.parseInt(prop.getProperty("datasource.maximum.pool.size", "250")); DATASOURCE_KEEPALIVE_TIME = Long.parseLong(prop.getProperty("datasource.keepalive.time", "180000")); @@ -636,6 +641,7 @@ public class Config { DATASOURCE_MINIMUM_IDLE = Integer.parseInt(prop.getProperty("datasource.minimum.idle", "20")); DATASOURCE_VALIDATION_TIMEOUT = Integer.parseInt(prop.getProperty("datasource.validation.timeout", "5000")); DATASOURCE_IDLE_TIMEOUT = Integer.parseInt(prop.getProperty("datasource.idle.timeout", "600000")); + DATASOURCE_EXCEPTION_AUDIT = Integer.parseInt(prop.getProperty("datasource.exception.audit", "1")); DB_URL = prop.getProperty("db.url"); DB_HOST = prop.getProperty("db.host", "localhost"); DB_PORT = Integer.parseInt(prop.getProperty("db.port", "3306")); diff --git a/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml b/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml index ab4479288113786f6cc37ef43cd0452894bf33f4..72b4eca64975eae346ff7c3d6392db046836b428 100644 --- a/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml +++ b/src/main/java/neatlogic/framework/dao/config/mybatis-config.xml @@ -41,10 +41,12 @@ along with this program. If not, see .--> + + diff --git a/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java index 61b93c4218a523b64078b31921e290dfa3470af0..0f41d240536f844538f9ab545d8371d778c1fbc1 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/DataSchemaInterceptor.java @@ -12,6 +12,7 @@ package neatlogic.framework.dao.plugin; +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; import neatlogic.framework.asynchronization.threadlocal.TenantContext; import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; @@ -31,7 +32,10 @@ public class DataSchemaInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { - SqlCostInterceptor.QUERY_FROM_DATABASE_INSTANCE.set(true); + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + interceptorContext.setQueryFromDatabase(true); + } StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = statementHandler.getBoundSql(); diff --git a/src/main/java/neatlogic/framework/dao/plugin/ExecutingSQLInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ExecutingSQLInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..b609e8ee04a2d97869be400a98aca5b9ceadaf58 --- /dev/null +++ b/src/main/java/neatlogic/framework/dao/plugin/ExecutingSQLInterceptor.java @@ -0,0 +1,57 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +package neatlogic.framework.dao.plugin; + +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; +import neatlogic.framework.common.config.Config; +import neatlogic.framework.store.mysql.SQLTransientConnectionExceptionAudit; +import neatlogic.framework.util.SnowflakeUtil; +import org.apache.ibatis.executor.statement.StatementHandler; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; + +import java.sql.Statement; +import java.util.Objects; + +@Intercepts({ + @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}), + @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}), + @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), + @Signature(type = StatementHandler.class, method = "queryCursor", args = {Statement.class}), +}) +public class ExecutingSQLInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + if (Objects.equals(Config.DATASOURCE_EXCEPTION_AUDIT(), 1)) { + String key = Thread.currentThread().getName() + "#" + SnowflakeUtil.uniqueLong(); + try { + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + MappedStatement mappedStatement = interceptorContext.getMappedStatement(); + String sqlId = mappedStatement.getId(); + SQLTransientConnectionExceptionAudit.putExecutingSQL(key, sqlId); + } + return invocation.proceed(); + } finally { + SQLTransientConnectionExceptionAudit.removeExecutingSQL(key); + } + } else { + return invocation.proceed(); + } + } +} diff --git a/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java index c5dd0e73ab5ec42728c77611f1cf939f40e3d0e2..e3d95548931e7571ba8197abdb3bab0b4c543284 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/ModifyResultMapTypeHandlerInterceptor.java @@ -12,6 +12,7 @@ package neatlogic.framework.dao.plugin; +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; import org.apache.commons.collections4.CollectionUtils; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.mapping.MappedStatement; @@ -43,12 +44,15 @@ import java.util.List; public class ModifyResultMapTypeHandlerInterceptor implements Interceptor { Logger logger = LoggerFactory.getLogger(ModifyResultMapTypeHandlerInterceptor.class); - public static final ThreadLocal mappedStatementThreadLocal = new ThreadLocal<>(); @Override public Object intercept(Invocation invocation) throws Throwable { try { - MappedStatement mappedStatement = mappedStatementThreadLocal.get(); + MappedStatement mappedStatement = null; + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + mappedStatement = interceptorContext.getMappedStatement(); + } if (mappedStatement != null) { Configuration configuration = mappedStatement.getConfiguration(); int resultMappingSize = 0; @@ -79,8 +83,6 @@ public class ModifyResultMapTypeHandlerInterceptor implements Interceptor { } } catch (Exception e) { logger.error(e.getMessage(), e); - } finally { - mappedStatementThreadLocal.remove(); } return invocation.proceed(); } diff --git a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java index b5793d92a5bde472eaafd9e45607e0e1936a33d9..7cbffc1d54bc17a45ac2b5b8552dd84df2d257aa 100644 --- a/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java +++ b/src/main/java/neatlogic/framework/dao/plugin/SqlCostInterceptor.java @@ -12,6 +12,7 @@ package neatlogic.framework.dao.plugin; +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; import neatlogic.framework.asynchronization.threadlocal.RequestContext; import neatlogic.framework.asynchronization.threadlocal.TenantContext; import neatlogic.framework.asynchronization.threadlocal.UserContext; @@ -48,8 +49,6 @@ import java.util.regex.Matcher; }) public class SqlCostInterceptor implements Interceptor { Logger logger = LoggerFactory.getLogger(SqlCostInterceptor.class); - // 判断是否查询了数据库 - public static final ThreadLocal QUERY_FROM_DATABASE_INSTANCE = new ThreadLocal<>(); public static class SqlIdMap { private static final Set sqlSet = new HashSet<>(); @@ -101,7 +100,6 @@ public class SqlCostInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; - ModifyResultMapTypeHandlerInterceptor.mappedStatementThreadLocal.set(mappedStatement); long starttime = 0; SqlAuditVo sqlAuditVo = null; boolean hasCacheFirstLevel = false; @@ -150,43 +148,42 @@ public class SqlCostInterceptor implements Interceptor { } catch (Exception e) { logger.error(e.getMessage(), e); } - QUERY_FROM_DATABASE_INSTANCE.set(false); - try { - // 执行完上面的任务后,不改变原有的sql执行过程 - Object val = invocation.proceed(); - if (sqlAuditVo != null) { - if (QUERY_FROM_DATABASE_INSTANCE.get()) { - // sql语句被执行,没有使用到缓存 - sqlAuditVo.setUseCacheLevel(StringUtils.EMPTY); + InterceptorContext interceptorContext = InterceptorContext.get(); + if (interceptorContext != null) { + interceptorContext.setQueryFromDatabase(false); + } + // 执行完上面的任务后,不改变原有的sql执行过程 + Object val = invocation.proceed(); + if (sqlAuditVo != null) { + if (interceptorContext != null && interceptorContext.getQueryFromDatabase()) { + // sql语句被执行,没有使用到缓存 + sqlAuditVo.setUseCacheLevel(StringUtils.EMPTY); + } else { + if (hasCacheFirstLevel) { + sqlAuditVo.setUseCacheLevel("一级缓存"); } else { - if (hasCacheFirstLevel) { - sqlAuditVo.setUseCacheLevel("一级缓存"); - } else { - sqlAuditVo.setUseCacheLevel("二级缓存"); - } + sqlAuditVo.setUseCacheLevel("二级缓存"); } - sqlAuditVo.setTimeCost(System.currentTimeMillis() - starttime); - sqlAuditVo.setRunTime(new Date()); + } + sqlAuditVo.setTimeCost(System.currentTimeMillis() - starttime); + sqlAuditVo.setRunTime(new Date()); - if (val != null) { - if (val instanceof List) { - sqlAuditVo.setRecordCount(((List) val).size()); - } else { - sqlAuditVo.setRecordCount(1); - } - } - SqlAuditManager.addSqlAudit(sqlAuditVo); - RequestContext requestContext = RequestContext.get(); - if (requestContext != null) { - requestContext.addSqlAudit(sqlAuditVo); + if (val != null) { + if (val instanceof List) { + sqlAuditVo.setRecordCount(((List) val).size()); + } else { + sqlAuditVo.setRecordCount(1); } - //System.out.println("time cost:" + (System.currentTimeMillis() - starttime) + "ms"); - //System.out.println("###########################################################################"); } - return val; - } finally { - QUERY_FROM_DATABASE_INSTANCE.remove(); + SqlAuditManager.addSqlAudit(sqlAuditVo); + RequestContext requestContext = RequestContext.get(); + if (requestContext != null) { + requestContext.addSqlAudit(sqlAuditVo); + } + //System.out.println("time cost:" + (System.currentTimeMillis() - starttime) + "ms"); + //System.out.println("###########################################################################"); } + return val; } public static String getSql(MappedStatement mappedStatement, Object parameterObject) { diff --git a/src/main/java/neatlogic/framework/dao/plugin/ThreadLocalInterceptor.java b/src/main/java/neatlogic/framework/dao/plugin/ThreadLocalInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..eae532ea43d31b0100168bf82a8b350c4b645783 --- /dev/null +++ b/src/main/java/neatlogic/framework/dao/plugin/ThreadLocalInterceptor.java @@ -0,0 +1,42 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +package neatlogic.framework.dao.plugin; + +import neatlogic.framework.asynchronization.threadlocal.InterceptorContext; +import org.apache.ibatis.executor.Executor; +import org.apache.ibatis.mapping.MappedStatement; +import org.apache.ibatis.plugin.Interceptor; +import org.apache.ibatis.plugin.Intercepts; +import org.apache.ibatis.plugin.Invocation; +import org.apache.ibatis.plugin.Signature; +import org.apache.ibatis.session.ResultHandler; +import org.apache.ibatis.session.RowBounds; + +@Intercepts({ + @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), + @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), +}) +public class ThreadLocalInterceptor implements Interceptor { + + @Override + public Object intercept(Invocation invocation) throws Throwable { + InterceptorContext interceptorContext = InterceptorContext.init(); + try { + interceptorContext.setMappedStatement((MappedStatement) invocation.getArgs()[0]); + interceptorContext.setParameter(invocation.getArgs()[1]); + return invocation.proceed(); + } finally { + interceptorContext.release(); + } + } +} diff --git a/src/main/java/neatlogic/framework/logback/logback-base.xml b/src/main/java/neatlogic/framework/logback/logback-base.xml index de34ead75976286707ed2ad7dcfe038e0997f497..e981a24666dd9e74c44ae4dadc0773c89d88545d 100644 --- a/src/main/java/neatlogic/framework/logback/logback-base.xml +++ b/src/main/java/neatlogic/framework/logback/logback-base.xml @@ -277,6 +277,36 @@ true + + ${log4j.home}/SQLTransientConnectionException.log + + ${log4j.home}/SQLTransientConnectionException.log.%i + 1 + 5 + + + ERROR + ACCEPT + DENY + + + 100MB + + + [%-5level]%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36}[%line] [%tenant] %requestUrl- %msg%n + + + + + + + 0 + 50 + + true + + ${log4j.home}/sqlTimeout.log @@ -357,6 +387,9 @@ + + + diff --git a/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java b/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java index 097435a98eecb9f0ace778a580937008ed63ca0e..b124e19d521038b257a979d98132804abb0c2744 100644 --- a/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java +++ b/src/main/java/neatlogic/framework/store/mysql/NeatLogicBasicDataSource.java @@ -18,9 +18,11 @@ import neatlogic.framework.common.util.RC4Util; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.jdbc.CannotGetJdbcConnectionException; import java.sql.Connection; import java.sql.SQLException; +import java.sql.SQLTransientConnectionException; import java.sql.Statement; import java.util.Objects; @@ -29,7 +31,13 @@ public class NeatLogicBasicDataSource extends HikariDataSource {//替换dbcp2的 @Override public Connection getConnection() throws SQLException { - Connection conn = super.getConnection(); + Connection conn = null; + try { + conn = super.getConnection(); + } catch (CannotGetJdbcConnectionException | SQLTransientConnectionException ex) { + SQLTransientConnectionExceptionAudit.audit(); + throw ex; + } conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); try (Statement statement = conn.createStatement()) { if (Objects.equals(DatasourceManager.getDatabaseId(), DatabaseVendor.MYSQL.getDatabaseId())) { diff --git a/src/main/java/neatlogic/framework/store/mysql/SQLTransientConnectionExceptionAudit.java b/src/main/java/neatlogic/framework/store/mysql/SQLTransientConnectionExceptionAudit.java new file mode 100644 index 0000000000000000000000000000000000000000..022971998e0139aab8899bd94429a314e44944ad --- /dev/null +++ b/src/main/java/neatlogic/framework/store/mysql/SQLTransientConnectionExceptionAudit.java @@ -0,0 +1,119 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +package neatlogic.framework.store.mysql; + +import com.alibaba.fastjson.JSON; +import com.zaxxer.hikari.HikariPoolMXBean; +import neatlogic.framework.common.config.Config; +import neatlogic.framework.dto.healthcheck.DataSourceInfoVo; +import neatlogic.framework.util.ThreadUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class SQLTransientConnectionExceptionAudit { + + private static final Logger logger = LoggerFactory.getLogger(SQLTransientConnectionExceptionAudit.class); + + // 保存上次抛异常的时间毫秒数 + private final static AtomicLong lastThrowExceptionMillisecondsAtomicLong = new AtomicLong(0); + private final static AtomicInteger countAtomicInteger = new AtomicInteger(3); + private final static Map executingSQLMap = new ConcurrentHashMap<>(); + + public static Map getExecutingSQL() { + return new HashMap<>(executingSQLMap); + } + + public static void clearExecutingSQL() { + executingSQLMap.clear(); + } + + public static void putExecutingSQL(String key, String value) { + executingSQLMap.put(key, value); + } + + public static void removeExecutingSQL(String key) { + executingSQLMap.remove(key); + } + + /** + * 五分钟内只打印三次日志 + */ + public static void audit() { + if (Objects.equals(Config.DATASOURCE_EXCEPTION_AUDIT(), 1)) { + boolean flag = false; + long currentTimeMillis = System.currentTimeMillis(); + long lastThrowExceptionMilliseconds = lastThrowExceptionMillisecondsAtomicLong.getAndUpdate(operand -> currentTimeMillis); + long interval = currentTimeMillis - lastThrowExceptionMilliseconds; + if (interval > TimeUnit.MINUTES.toMillis(5)) { + if (countAtomicInteger.compareAndSet(3, 0)) { + flag = true; + } + } else { + int count = countAtomicInteger.updateAndGet(operand -> { + if (operand < 3) { + return operand + 1; + } else { + return operand; + } + }); + if (count < 3) { + flag = true; + } + } + if (flag) { + doAudit(); + } + } + } + + private static synchronized void doAudit() { + try { + DataSourceInfoVo dataSourceInfoVo = new DataSourceInfoVo(); + NeatLogicBasicDataSource datasource = DatasourceManager.getDatasource(); + dataSourceInfoVo.setPoolName(datasource.getPoolName()); + HikariPoolMXBean hikariPoolMXBean = datasource.getHikariPoolMXBean(); + if (hikariPoolMXBean != null) { + dataSourceInfoVo.setIdleConnections(hikariPoolMXBean.getIdleConnections()); + dataSourceInfoVo.setActiveConnections(hikariPoolMXBean.getActiveConnections()); + dataSourceInfoVo.setThreadsAwaitingConnection(hikariPoolMXBean.getThreadsAwaitingConnection()); + dataSourceInfoVo.setTotalConnections(hikariPoolMXBean.getTotalConnections()); + } + Map executingSQLSnapshotMap = new HashMap<>(executingSQLMap); + StringWriter writer = new StringWriter(); + ThreadUtil.dumpTraces(writer); + writer.write("=================正在执行的SQL语句有" + executingSQLSnapshotMap.size() + "条================="); + writer.write(System.lineSeparator()); + for (Map.Entry entry : executingSQLSnapshotMap.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + writer.write("[" + key + "] 线程正在执行 " + value); + writer.write(System.lineSeparator()); + } + writer.write("连接池信息: " + JSON.toJSONString(dataSourceInfoVo)); + Logger SQLTransientConnectionExceptionAuditLogger = LoggerFactory.getLogger("SQLTransientConnectionExceptionAudit"); + SQLTransientConnectionExceptionAuditLogger.error(writer.toString()); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } +} diff --git a/src/main/java/neatlogic/framework/util/ThreadUtil.java b/src/main/java/neatlogic/framework/util/ThreadUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..7e4651baea42edc4a4b3637ef7cfc5bc8fbae2b7 --- /dev/null +++ b/src/main/java/neatlogic/framework/util/ThreadUtil.java @@ -0,0 +1,71 @@ +/* + * + * Copyright (C) 2025 TechSure Co., Ltd. All Rights Reserved. + * This file is part of the NeatLogic software. + * Licensed under the NeatLogic Sustainable Use License (NSUL), Version 4.x – 2025. + * You may use this file only in compliance with the License. + * See the LICENSE file distributed with this work for the full license text. + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +package neatlogic.framework.util; + +import neatlogic.framework.asynchronization.threadlocal.RequestContext; +import neatlogic.framework.common.config.Config; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.Writer; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class ThreadUtil { + + public static void dumpTraces(Writer writer) throws IOException { + ThreadMXBean mxBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = mxBean.getThreadInfo(mxBean.getAllThreadIds(), 0); + Map threadInfoMap = new HashMap<>(); + for (ThreadInfo threadInfo : threadInfos) { + if (threadInfo != null) { + threadInfoMap.put(threadInfo.getThreadId(), threadInfo); + } + } + Map stacks = Thread.getAllStackTraces(); + long now = System.currentTimeMillis(); + String localAddr = StringUtils.EMPTY; + String url = StringUtils.EMPTY; + RequestContext requestContext = RequestContext.get(); + if (requestContext != null) { + if (requestContext.getRequest() != null && StringUtils.isNotBlank(requestContext.getRequest().getLocalAddr())) { + localAddr = requestContext.getRequest().getLocalAddr(); + } + if (StringUtils.isNotBlank(requestContext.getUrl())) { + url = requestContext.getUrl(); + } + } + writer.write("\n=================" + stacks.size() + " thread of " + localAddr + " at " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z").format(new Date(now)) + " start.serverId is " + Config.SCHEDULE_SERVER_ID + "=================\n\n"); + for (Map.Entry entry : stacks.entrySet()) { + Thread thread = entry.getKey(); + writer.write("\"" + thread.getName() + "\" prio=" + thread.getPriority() + " tid=" + thread.getId() + " " + thread.getState() + " " + (thread.isDaemon() ? "deamon" : "worker")); + ThreadInfo threadInfo = threadInfoMap.get(thread.getId()); + if (threadInfo != null) { + writer.write(" native=" + threadInfo.isInNative() + ", suspended=" + threadInfo.isSuspended() + ", block=" + threadInfo.getBlockedCount() + ", wait=" + threadInfo.getWaitedCount()); + writer.write(" lock=" + threadInfo.getLockName() + " owned by " + threadInfo.getLockOwnerName() + " (" + threadInfo.getLockOwnerId() + "), cpu=" + (mxBean.getThreadCpuTime(threadInfo.getThreadId()) / 1000000L) + ", user=" + (mxBean.getThreadUserTime(threadInfo.getThreadId()) / 1000000L) + "\n"); + } + for (StackTraceElement element : entry.getValue()) { + writer.write("\t\t"); + writer.write(element.toString()); + writer.write("\n"); + } + writer.write("\n"); + } + writer.write("=================" + stacks.size() + " thread of " + url + " at " + new SimpleDateFormat("yyyy/MM/dd HH:mm:ss z").format(new Date(now)) + " end.=================\n\n"); + } +}