在生產環境中,要求對日志進行分類切割及ERR例外類能及時預警,便于及時發現線上問題,
一、技術要求:
(1).日志按照以天為單位存盤,超過一定大小后要另起檔案,便于查閱,日志可設定過期時間,過期后系統可自動洗掉,避免海量存盤空間
(2) 出現線上ERROR級別例外,需要通過釘釘或者郵件及時預警
(3)把ERROR級別例外資訊存盤到資料庫,方便線上查詢
(4)要能方便區分除生產環境及開發環境,開發環境不需要郵件及釘釘預警
二、技術解決思路
對應生產環境例外錯誤預警,大概有兩種解決方
(1)采用全域攔截器,攔截所有例外,在攔截器里實作存盤、推送等操作,這個需要考慮并發
(2)采用日志系統自帶的相關功能及擴展
本論文介紹通過日志擴展來解決問題
Spring Boot 2.*默認采用了slf4j+logback的形式 ,slf4j是個通用的日志門面,logback就是個具體的日志框架了,我們記錄日志的時候采用slf4j的方法去記錄日志,底層的實作就是根據參考的不同日志jar去判定了,所以Spring Boot也能自動適配JCL、JUL、Log4J等日志框架,它的內部邏輯就是通過特定的JAR包去適配各個不同的日志框架,
logback日志集成了郵件發送、資料庫存盤、日志檔案分類存盤等功能,釘釘推送預警沒有集成,需要去擴展
分環境撰寫組態檔,springboot已有解決方案,通過application.yml里面配置不同的對應檔案,logback可讀取當前的環境引數:

解決步驟
1.在resources檔案夾力創建logback-spring.xml檔案,并在yml檔案里宣告
:注意默認檔案名是logback-spring.xml,可省略
yml檔案里宣告
#日志資訊 logging: config: classpath:logback-spring.xml #如果不配置config,默認查找logback-spring.xml path: D:/log
2.在logback.xml里配置控制臺日志和檔案輸出
<!-- 控制面板輸出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化輸出:%d表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字符寬度%msg:日志訊息,%n是換行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 按照每天生成日志檔案 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--日志檔案輸出的檔案名--> <FileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}/MIXPAY_%d{yyyy-MM-s}.log</FileNamePattern> <!--日志檔案保留天數--> <MaxHistory>50</MaxHistory> </rollingPolicy> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化輸出:%d表示日期,%thread表示執行緒名,%-5level:級別從左顯示5個字符寬度%msg:日志訊息,%n是換行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> <!--日志檔案最大的大小--> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>50MB</MaxFileSize> </triggeringPolicy> </appender><!--輸出配置 -->
<root level="INFO"> <!-- 控制面板輸出 --> <appender-ref ref="STDOUT"/> <!-- 按照每天生成日志檔案 --> <appender-ref ref="FILE"/> </root>
3.配置插入資料庫
pom.xml檔案引入資料庫相關驅動
<!--spring-jdbc驅動 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> </dependency> <!--druid 資料庫連接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency>
yml里配置相關鏈接,這里日志資料庫雖然用不到,但不配置要報啟動錯誤
spring: profiles: active: prod datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver platform: mysql url: jdbc:mysql://127.0.0.1:3306/pmlog?useUnicode=true&characterEncoding=UTF-8&useSSL=true username: root password: 111111 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT1FROMDUAL testWhileIdle: true testOnBorrow: false testOnReturn: false filters: stat,wall,log4j logSlowSql: truelog
在logback.xml檔案里配置
<!--連接資料庫配置--> <appender name="db_classic_mysql_pool" class="ch.qos.logback.classic.db.DBAppender"> <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource"> <dataSource class="com.alibaba.druid.pool.DruidDataSource"> <driverClassName>org.gjt.mm.mysql.Driver</driverClassName> <url>jdbc:mysql://127.0.0.1:3306/pmlog?useUnicode=true&characterEncoding=UTF-8&useSSL=true</url> <username>root</username> <password>111111</password> </dataSource> </connectionSource> <!--這里設定日志級別為error--> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>error</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender><root level="INFO"> <!-- 資料庫輸出 --> <appender-ref ref="db_classic_mysql_pool"/> </root>
初始化資料庫表
BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT;
BEGIN;
CREATE TABLE logging_event
(
timestmp BIGINT NOT NULL,
formatted_message TEXT NOT NULL,
logger_name VARCHAR(254) NOT NULL,
level_string VARCHAR(254) NOT NULL,
thread_name VARCHAR(254),
reference_flag SMALLINT,
arg0 VARCHAR(254),
arg1 VARCHAR(254),
arg2 VARCHAR(254),
arg3 VARCHAR(254),
caller_filename VARCHAR(254) NOT NULL,
caller_class VARCHAR(254) NOT NULL,
caller_method VARCHAR(254) NOT NULL,
caller_line CHAR(4) NOT NULL,
event_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_property
(
event_id BIGINT NOT NULL,
mapped_key VARCHAR(254) NOT NULL,
mapped_value TEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_exception
(
event_id BIGINT NOT NULL,
i SMALLINT NOT NULL,
trace_line VARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
4.配置發送到郵件
pom.xml引入mail包
<dependency> <groupId>org.codehaus.janino</groupId> <artifactId>janino</artifactId> <version>3.1.2</version> </dependency> <!-- email --> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.5</version> </dependency> <!-- email END-->
logback.xml配置郵件資訊
<!--郵件發送--> <appender name="email" class="ch.qos.logback.classic.net.SMTPAppender"> <!--smtp 服務器--> <smtpHost>smtp.qiye.aliyun.com</smtpHost> <!--port--> <smtpPort>25</smtpPort> <!-- 發給誰的郵件串列,多個人用逗號分隔 --> <to>jiangzengkui@lpcollege.com</to> <!--發件人,添加郵箱和上面的username保持一致--> <from>jyj_stuff@lpcollege.com</from> <subject>${ACTIVE_PROFILE_NAME}: %logger - %msg</subject> <!--發件人的郵箱--> <username>jyj_stuff@lpcollege.com</username> <!--發件人的郵箱密碼--> <password>F4efK7mpnrz5Etkg</password> <SSL>false</SSL> <!--是否異步--> <asynchronousSending>true</asynchronousSending> <layout class="ch.qos.logback.classic.PatternLayout"> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </layout> <cyclicBufferTracker class = "ch.qos.logback.core.spi.CyclicBufferTracker" > <bufferSize> 1 </bufferSize> </cyclicBufferTracker> <!--過濾器--> <!-- 這里采用等級過濾器 指定等級相符才發送 --> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> </appender><root level="INFO"> <appender-ref ref="email"/> </root>
5.釘釘推送等其他操作
logback沒有像郵件,資料庫一樣集成,只有繼承UnsynchronizedAppenderBase去擴展
注:關于釘釘如何發預警訊息,請參考釘釘和springboot的集成
(1)擴展類:
package com.jyj.soft.comm;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* @class: com.jyj.soft.comm.CustomizeApperder
* @description:
* logback的節點擴展類,獲取輸出,并進行異步處理
* @author: jiangzengkui
* @company: 教育家
* @create: 2020-12-05 09:13
*/
public class CustomizeApperder extends UnsynchronizedAppenderBase<ILoggingEvent> {
@Override
public void append(ILoggingEvent eventObject) {
try {
//節點輸出內容
String content = eventObject.getMessage();
//例外的IP
String ip= InetAddress.getLocalHost().getHostAddress();
String run_machine=SpringContextUtil.getActiveProfile();//運行服務器型別如在yml配置的生存、開發、測驗等環境
//System.out.println("當前運行環境: " + run_machine);
// System.out.println("content內容是: " + content);
System.out.println("服務器IP:"+ip);
if("prod".equals(run_machine)){//如果是生產環境
//1.可發郵件
//2.可釘釘推送
String title=">生產環境發生例外";
String markDown=">**服務器IP:**"+ip+"\n\n";
markDown+=">**例外原因:**"+content;
RobotUtil.sendMarkdownMsg(RobotUtil.robot_name_test,null,title,markDown);
//3.可插入資料庫
}
/** Map<String, String> map = new HashMap<String, String>();
map.put("LOG_LEVEL", eventObject.getLevel().levelStr);
map.put("CONTENT", content.replace("'", "''"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
map.put("CREATE_DATE", sdf.format(new Date()));
**/
// 拼接SQL陳述句,然后執行
// … …
} catch (Throwable e) {
String errorMsg = e.getLocalizedMessage();
System.out.println(errorMsg);
}
}
}
這里用到一個幫助類SpringContextUtil,通過非注解的方式或者bean實體和配置屬性
package com.jyj.soft.comm;
/**
* @class: com.jyj.soft.comm.SpringContextUtil
* @description:
* 作用:
* (1)不通過@Autowired注解來獲得物件實體
* (2)直接讀取propertie,yml檔案里的配置值
* @author: jiangzengkui
* @company: 教育家
* @create: 2020-12-05 11:05
*/
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 獲取Spring的ApplicationContext物件工具,可以用靜態方法的方式獲取spring容器中的bean
* @author https://blog.csdn.net/chen_2890
* @date 2019/6/26 16:20
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
/**
* 系統啟動如tomcat時會執行這個方法
* @param applicationContext
* @throws BeansException
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
/**
* 獲取applicationContext
*/
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
/**
* 通過name獲取 Bean.
*/
public static Object getBean(String name) {
Object o = null;
try {
o = getApplicationContext().getBean(name);
} catch (NoSuchBeanDefinitionException e) {
// e.printStackTrace();
}
return o;
}
/**
* 通過class獲取Bean.
*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通過name,以及Clazz回傳指定的Bean
*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
/**
* 通過name獲取 Bean.
*/
public static <T> Map<String, T> getBeansOfType(Class<T> clazz) {
return getApplicationContext().getBeansOfType(clazz);
}
/**
* 獲取組態檔配置項的值
*
* @param key 配置項key,注意這個key支撐連寫
* persoon:
* name: jzk
* 則key是persoon.name,不是name
*/
public static String getEnvironmentProperty(String key) {
return getApplicationContext().getEnvironment().getProperty(key);
}
/**
* 獲取spring.profiles.active
*/
public static String getActiveProfile() {
return getApplicationContext().getEnvironment().getActiveProfiles()[0];
}
}
(2)配置logback.xml
<!--自定義節點 -->
<appender name="CustomLog" class="com.jyj.soft.comm.CustomizeApperder">
<!--過濾類 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>error</level>
</filter>
</appender>
<root level="INFO"> <!--自定義節點 --> <appender-ref ref="CustomLog"/> </root>
5.日志在不同的環境運用
比如只有在生產環境推送釘釘預警和郵件等,開發和測驗環境不需要
要用到logback.xml里<springProfile name="xx">這個這個標簽,意思是對yml組態檔里的spring.profiles.active的資料
spring: profiles: active: prod
logback根據不同的值,呼叫不同的appender,如
<!-- 日志輸出級別 ,就是說在整個專案中,日志級別在info一上的日志都列印, root是根日志列印器,只有一個,負責整個系統的日志輸出 --> <!-- springProfile name="prod" 對應著yml組態檔spring.profiles.active: dev --> <springProfile name="prod"><!--生產環境 --> <root level="INFO"> <!-- 控制面板輸出 --> <appender-ref ref="STDOUT"/> <!-- 按照每天生成日志檔案 --> <appender-ref ref="FILE"/> <!-- 資料庫輸出 --> <appender-ref ref="db_classic_mysql_pool"/> <!--自定義節點 --> <appender-ref ref="CustomLog"/> <!--郵件輸出--> <appender-ref ref="email"/> </root> </springProfile> <springProfile name="dev"><!--開發環境 --> <root level="debug"> <!-- 控制面板輸出 --> <appender-ref ref="STDOUT"/> </root> </springProfile>
7.其他知識
(1)logback讀取yml組態檔的資料
先宣告,在用{}參考
yml:
persoon:
name: jzk
在logback 里讀取
定義
<springProperty scope="context" name="pro_name" source="persoon.name"/>
參考
<subject>${pro_name}: %logger - %msg</subject>
(3)日志簡寫
每個類都要寫
LoggerFactory.getLogger(SbDemoApplicationTests.class);很麻煩,可以省掉
//標簽
@Slf4j
@RestController
public class HelloCtrol {
@Autowired
private Persoon persoon;
@Autowired
private Dage dage;
//訪問路徑及方法
@RequestMapping(value = "/hello",method = RequestMethod.GET)
public String hello(){
dage.h();
//直接用log
log.error("error================");
log.warn("warn==============");
log.info("info==============");
log.debug("debug===================");
return "hello, "+persoon.getName()+",address:"+persoon.getAddress();
}
實作方式
| 1.使用idea首先需要安裝Lombok插件;
|
| 2..在pom檔案加入lombok的依賴
|
3.釘釘訊息推送
參考:
https://blog.csdn.net/weixin_41158378/article/details/110749806
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/231577.html
標籤:java
上一篇:【SpringBoot】SpringBoot生成二維碼
下一篇:Java撰寫簡單計算器--實作篇
