在生产环境中,要求对日志进行分类切割及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里配置控制台日志和文件输出
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n ${LOG_HOME}/%d{yyyy-MM-dd}/MIXPAY_%d{yyyy-MM-s}.log 50 %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n 50MB
3.配置插入数据库
pom.xml文件引入数据库相关驱动
org.springframework spring-jdbc com.alibaba druid-spring-boot-starter 1.1.10 mysql mysql-connector-java 5.1.48
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文件里配置
org.gjt.mm.mysql.Driver jdbc:mysql://127.0.0.1:3306/pmlog?useUnicode=true&characterEncoding=UTF-8&useSSL=true root 111111 error ACCEPT DENY
初始化数据库表
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包
org.codehaus.janino janino 3.1.2 javax.mail 1.4.5
logback.xml配置邮件信息
smtp.qiye.aliyun.com 25 [email protected] [email protected] ${ACTIVE_PROFILE_NAME}: %logger - %msg [email protected] xxxxxxx false true %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 1 ERROR ACCEPT DENY
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 {
@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 map = new HashMap();
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 getBean(Class clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*/
public static T getBean(String name, Class clazz) {
return getApplicationContext().getBean(name, clazz);
}
/**
* 通过name获取 Bean.
*/
public static Map getBeansOfType(Class 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
error
5.日志在不同的环境运用
比如只有在生产环境推送钉钉预警和邮件等,开发和测试环境不需要
要用到logback.xml里
spring: profiles: active: prod
logback根据不同的值,调用不同的appender,如
7.其他知识
(1)logback读取yml配置文件的数据
先声明,在用{}引用
yml:
persoon:
name: jzk
在logback 里读取
定义
引用
${pro_name}: %logger - %msg
(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