在生产环境中,要求对日志进行分类切割及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文件里声明
2.在logback.xml里配置控制台日志和文件输出#日志信息 logging: config: classpath:logback-spring.xml #如果不配置config,默认查找logback-spring.xml path: D:/log
3.配置插入数据库
%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
pom.xml文件引入数据库相关驱动
yml里配置相关链接,这里日志数据库虽然用不到,但不配置要报启动错误
org.springframework spring-jdbccom.alibaba druid-spring-boot-starter1.1.10 mysql mysql-connector-java5.1.48
在logback.xml文件里配置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
初始化数据库表
org.gjt.mm.mysql.Driver jdbc:mysql://127.0.0.1:3306/pmlog?useUnicode=true& characterEncoding=UTF-8& useSSL=true root 111111error ACCEPT DENY
4.配置发送到邮件
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
(
timestmpBIGINT NOT NULL,
formatted_messageTEXT NOT NULL,
logger_nameVARCHAR(254) NOT NULL,
level_stringVARCHAR(254) NOT NULL,
thread_nameVARCHAR(254),
reference_flagSMALLINT,
arg0VARCHAR(254),
arg1VARCHAR(254),
arg2VARCHAR(254),
arg3VARCHAR(254),
caller_filenameVARCHAR(254) NOT NULL,
caller_classVARCHAR(254) NOT NULL,
caller_methodVARCHAR(254) NOT NULL,
caller_lineCHAR(4) NOT NULL,
event_idBIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_property
(
event_idBIGINT NOT NULL,
mapped_keyVARCHAR(254) NOT NULL,
mapped_valueTEXT,
PRIMARY KEY(event_id, mapped_key),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
BEGIN;
CREATE TABLE logging_event_exception
(
event_idBIGINT NOT NULL,
iSMALLINT NOT NULL,
trace_lineVARCHAR(254) NOT NULL,
PRIMARY KEY(event_id, i),
FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
);
COMMIT;
pom.xml引入mail包
logback.xml配置邮件信息
org.codehaus.janino janino3.1.2 javax.mail mail1.4.5
5.钉钉推送等其他操作smtp.qiye.aliyun.com25
jiangzengkui@lpcollege.com jyj_stuff@lpcollege.com ${ACTIVE_PROFILE_NAME}: %logger - %msgjyj_stuff@lpcollege.com xxxxxxx falsetrue%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n 1 ERROR ACCEPT DENY
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 {@Overridepublic void append(ILoggingEvent eventObject) {try { //节点输出内容
String content = eventObject.getMessage();
//异常的IP
Stringip= 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
5.日志在不同的环境运用
error
比如只有在生产环境推送钉钉预警和邮件等,开发和测试环境不需要
要用到logback.xml里这个这个标签,意思是对yml配置文件里的spring.profiles.active的数据
logback根据不同的值,调用不同的appender,如spring: profiles: active: prod
7.其他知识
(1)logback读取yml配置文件的数据
先声明,在用{}引用
yml:
persoon:在logback 里读取
name: jzk
定义(3)日志简写
引用
${pro_name}: %logger - %msg
每个类都要写
LoggerFactory.getLogger(SbDemoApplicationTests.class); 很麻烦,可以省掉
//标签
@Slf4j
@RestController
public class HelloCtrol {@Autowired
private Persoon persoon;
@Autowired
private Dage dage;
//访问路径及方法
@RequestMapping(value = "https://www.it610.com/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
【springboot|SpringBoot2.x系列(二)生产环境日志及预警】
推荐阅读
- springboot|SpringBoot 日志系列:(二)日志配置
- SrpingBoot技术总结|构建SpringBoot实战项目 系列文章之日志配置
- redis|Redis面试题
- 力扣|力扣打卡之最小栈
- java|解决错误(org.apache.ibatis.binding.BindingException)
- vue|vue: 解决错误 RunScriptError: post install error, please remove node_modules before retry!
- java|解决错误(Cannot find module ‘fs/promises‘)
- java|出现错误java:警告:源反行版 9,需要目标发行版1.9
- java|解决实例化Servlet类[com.mu.servlet.HelloServlet]异常