後臺管理系統(二)之SQL監控
阿新 • • 發佈:2022-05-18
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.17</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.12</version> </dependency> <!-- 工具類 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dependency>
修改application.properties檔案
server: port: 7001 spring: datasource: name: druidDataSource type: com.alibaba.druid.pool.DruidDataSource druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/qx?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=falseusername: root password: root filters: stat,wall,slf4j,config #配置監控統計攔截的filters,去掉後監控介面SQL無法進行統計,wall用於防火牆。 max-active: 100 #最大連線數 initial-size: 1 #初始化大小 max-wait: 60000 #獲取連線等待超時時間 min-idle: 1 #最小連線數 time-between-eviction-runs-millis: 60000 #間隔多久才進行一次檢測,檢測需要關閉的空閒連線,單位是毫秒。 min-evictable-idle-time-millis: 300000 #一個連線在池中最小生存的時間,單位是毫秒。 validation-query: select 'x' test-while-idle: true test-on-borrow: false test-on-return: false pool-prepared-statements: true max-pool-prepared-statement-per-connection-size: 20 logging: config: classpath:logback.xml # level: # com.springframe.festmon.dao: trace
config
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import javax.servlet.Servlet; import javax.sql.DataSource; import java.sql.SQLException; import java.util.Collections; import java.util.HashMap; import java.util.Map; @Configuration @EnableConfigurationProperties({DruidDataSourceProperties.class}) //@EnableConfigurationProperties註解用於匯入上一步自定義的Druid的配置資訊。 public class DruidConfig { @Autowired private DruidDataSourceProperties properties; @Bean @ConditionalOnMissingBean public DataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(properties.getDriverClassName()); druidDataSource.setUrl(properties.getUrl()); druidDataSource.setUsername(properties.getUsername()); druidDataSource.setPassword(properties.getPassword()); druidDataSource.setInitialSize(properties.getInitialSize()); druidDataSource.setMinIdle(properties.getMinIdle()); druidDataSource.setMaxActive(properties.getMaxActive()); druidDataSource.setMaxWait(properties.getMaxWait()); druidDataSource.setTimeBetweenEvictionRunsMillis(properties.getTimeBetweenEvictionRunsMillis()); druidDataSource.setMinEvictableIdleTimeMillis(properties.getMinEvictableIdleTimeMillis()); druidDataSource.setValidationQuery(properties.getValidationQuery()); druidDataSource.setTestWhileIdle(properties.isTestWhileIdle()); druidDataSource.setTestOnBorrow(properties.isTestOnBorrow()); druidDataSource.setTestOnReturn(properties.isTestOnReturn()); druidDataSource.setPoolPreparedStatements(properties.isPoolPreparedStatements()); druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(properties.getMaxPoolPreparedStatementPerConnectionSize()); try { druidDataSource.setFilters(properties.getFilters()); druidDataSource.init(); } catch (SQLException e) { e.printStackTrace(); } return druidDataSource; } /** * 配置 Druid 監控管理後臺的Servlet; * 內建 Servler 容器時沒有web.xml檔案,所以使用 Spring Boot 的註冊 Servlet 方式 * public ServletRegistrationBean druidServlet()相當於WebServlet配置。 */ @Bean @ConditionalOnMissingBean public ServletRegistrationBean<Servlet> druidServlet(){ ServletRegistrationBean<Servlet> servletServletRegistrationBean = new ServletRegistrationBean<Servlet>(new StatViewServlet(), "/druid/*"); //白名單 servletServletRegistrationBean.addInitParameter("allow","127.0.0.1"); //表示只有本機可以訪問,為空或者為null時,表示允許所有訪問 //ip黑名單(存在共同時,deny優先於allow) //如果滿足deny的話會提示,sorry, you are not permitted to view this page servletServletRegistrationBean.addInitParameter("deny","172.13.13.31"); //登入檢視資訊的賬號和密碼,用於登入Druid監控後臺 servletServletRegistrationBean.addInitParameter("loginUsername","admin"); servletServletRegistrationBean.addInitParameter("loginPassword","admin"); //是否能重置資料 servletServletRegistrationBean.addInitParameter("resetEnable","true"); return servletServletRegistrationBean; } /** * 配置 Druid 監控 之 web 監控的 filter * WebStatFilter:用於配置Web和Druid資料來源之間的管理關聯監控統計 * public FilterRegistrationBean filterRegistrationBean()相當於Web Filter配置。 */ @Bean @ConditionalOnMissingBean public FilterRegistrationBean<Filter> filterFilterRegistrationBean(){ FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<Filter>(); bean.setFilter(new WebStatFilter()); //exclusions:設定哪些請求進行過濾排除掉,從而不進行統計 Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/*"); bean.setInitParameters(initParams); //"/*" 表示過濾所有請求 bean.setUrlPatterns(Collections.singletonList("/*")); return bean; }
DruidDataSourceProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.datasource.druid") //掃描配置類的屬性字首 public class DruidDataSourceProperties { // jdbc private String driverClassName; private String url; private String username; private String password; // jdbc connection pool private int initialSize; private int minIdle; private int maxActive = 100; private long maxWait; private long timeBetweenEvictionRunsMillis; private long minEvictableIdleTimeMillis; private String validationQuery; private boolean testWhileIdle; private boolean testOnBorrow; private boolean testOnReturn; private boolean poolPreparedStatements; private int maxPoolPreparedStatementPerConnectionSize; // filter private String filters; public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getInitialSize() { return initialSize; } public void setInitialSize(int initialSize) { this.initialSize = initialSize; } public int getMinIdle() { return minIdle; } public void setMinIdle(int minIdle) { this.minIdle = minIdle; } public int getMaxActive() { return maxActive; } public void setMaxActive(int maxActive) { this.maxActive = maxActive; } public long getMaxWait() { return maxWait; } public void setMaxWait(long maxWait) { this.maxWait = maxWait; } public long getTimeBetweenEvictionRunsMillis() { return timeBetweenEvictionRunsMillis; } public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) { this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; } public long getMinEvictableIdleTimeMillis() { return minEvictableIdleTimeMillis; } public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) { this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; } public String getValidationQuery() { return validationQuery; } public void setValidationQuery(String validationQuery) { this.validationQuery = validationQuery; } public boolean isTestWhileIdle() { return testWhileIdle; } public void setTestWhileIdle(boolean testWhileIdle) { this.testWhileIdle = testWhileIdle; } public boolean isTestOnBorrow() { return testOnBorrow; } public void setTestOnBorrow(boolean testOnBorrow) { this.testOnBorrow = testOnBorrow; } public boolean isTestOnReturn() { return testOnReturn; } public void setTestOnReturn(boolean testOnReturn) { this.testOnReturn = testOnReturn; } public boolean isPoolPreparedStatements() { return poolPreparedStatements; } public void setPoolPreparedStatements(boolean poolPreparedStatements) { this.poolPreparedStatements = poolPreparedStatements; } public int getMaxPoolPreparedStatementPerConnectionSize() { return maxPoolPreparedStatementPerConnectionSize; } public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) { this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize; } public String getFilters() { return filters; } public void setFilters(String filters) { this.filters = filters; } }
配置日誌管理
新建logback.xml檔案
大家可以根據專案修改LOG_HOME的value值,還有開發環境建議把下面這個註釋掉,使用控制檯輸出日誌,方便定位,真實環境,把控制檯註釋掛掉,改為日誌檔案輸出
logback.xml
<?xml version="1.0" encoding="UTF-8"?> <!--dev env--> <configuration> <!-- fileError對應error級別,檔名以log-error-xxx.log形式命名 fileWarn對應warn級別,檔名以log-warn-xxx.log形式命名 fileInfo對應info級別,檔名以log-info-xxx.log形式命名 fileDebug對應debug級別,檔名以log-debug-xxx.log形式命名 stdout將日誌資訊輸出到控制上,為方便開發測試使用 --> <contextName>data_server</contextName> <property name="LOG_HOME" value="wyq_log" /> <property name="log.maxHistory" value="1" /> <property name="log.lever" value="info" /> <appender name="fileError" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在記錄的日誌檔案的路徑及檔名 --> <file>${LOG_HOME}/log_error.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <MaxHistory>${log.maxHistory}</MaxHistory> <!-- 配置日誌檔案不能超過100M,若超過100M,日誌檔案會以索引0開始--> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 追加方式記錄日誌 --> <append>true</append> <!-- 日誌檔案的格式 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern> <!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>--> <charset>utf-8</charset> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>error</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <appender name="fileWarn" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_HOME}/log_warn.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/warn/log-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <MaxHistory>${log.maxHistory}</MaxHistory> <!-- 配置日誌檔案不能超過100M,若超過100M,日誌檔案會以索引0開始--> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 追加方式記錄日誌 --> <append>true</append> <!-- 日誌檔案的格式 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern> <!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>--> <charset>utf-8</charset> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warn</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <appender name="fileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_HOME}/log_info.log</file> <!-- 日誌記錄器的滾動策略,按日期,按大小記錄 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <MaxHistory>${log.maxHistory}</MaxHistory> <!-- 配置日誌檔案不能超過100M,若超過100M,日誌檔案會以索引0開始--> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> </rollingPolicy> <!-- 追加方式記錄日誌 --> <append>true</append> <!-- 日誌檔案的格式 --> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern> <!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>--> <charset>utf-8</charset> </encoder> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>info</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <!--encoder 預設配置為PatternLayoutEncoder--> <encoder> <!--<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern>--> <pattern>%date{yyyy-MM-dd HH:mm:ss} | %highlight(%p) | %boldYellow(%c) | %M:%boldGreen(%L) | %m%n</pattern> <!--<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger Line:%-3L - %msg%n</pattern>--> <charset>utf-8</charset> </encoder> </appender> <logger name="org.springframework" level="${log.lever}" /> <!-- 生產環境下,將此級別配置為適合的級別,以免日誌檔案太多或影響程式效能 --> <root level="INFO"> <!--<appender-ref ref="fileError" /> <appender-ref ref="fileWarn" /> <appender-ref ref="fileInfo" />--> <appender-ref ref="STDOUT" /> </root> </configuration>