SpringBoot系列——Logback日志,输出到文件以及实时输出到web页面
前言
SpringBoot对所有内部日志使用通用日志记录,但保留底层日志实现。为Java Util Logging、Log4J2和Logback提供了默认配置。在不同的情况下,日志记录器都预先配置为使用控制台输出,同时还提供可选的文件输出。默认情况下,SpringBoot使用Logback进行日志记录。
日志级别有(从高到低):FATAL(致命),ERROR
(
错误),
WARN
(
警告),
INFO
(
信息),
DEBUG
(
调试),
TRACE
(
跟踪)或者 OFF
(关闭),默认的日志配置在消息写入时将消息回显到控制台。默认情况下,将记录错误级别、警告级别和信息级别的消息。
PS:Logback does not have a FATAL
level. It is mapped to ERROR Logback没有FATAL致命级别。它被映射到ERROR错误级别
详情请戳官方文档:https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-logging
本文主要记录Logback日志输出到文件以及实时输出到web页面
输出到文件
我们创建SpringBoot项目时,spring-boot-starter已经包含了spring-boot-starter-logging,不需要再进行引入依赖
标准日志格式
2014-03-05 10:57:51.112 INFO 45469 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/7.0.52 2014-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2014-03-05 10:57:51.253 INFO 45469 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1358 ms 2014-03-05 10:57:51.698 INFO 45469 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/] 2014-03-05 10:57:51.702 INFO 45469 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
- Date and Time: Millisecond precision and easily sortable. 日期和时间:毫秒精度,易于排序。
- Log Level:
ERROR
,WARN
,INFO
,DEBUG
, orTRACE
. 日志级别:错误、警告、信息、调试或跟踪。 - Process ID. 进程ID。
- A
---
separator to distinguish the start of actual log messages. 分隔符,用于区分实际日志消息的开始。 - Thread name: Enclosed in square brackets (may be truncated for console output). 线程名称:括在方括号中(可能会被截断以用于控制台输出)。
- Logger name: This is usually the source class name (often abbreviated). 日志程序名称:这通常是源类名称(通常缩写)。
- The log message. 日志消息。
如何打印日志?
方法1
/** * 配置内部类 */ @Controller @Configuration class Config { /** * 获取日志对象,构造函数传入当前类,查找日志方便定位 */ private final Logger log = LoggerFactory.getLogger(this.getClass()); @Value("${user.home}") private String userName; /** * 端口 */ @Value("${server.port}") private String port; /** * 启动成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { try { InetAddress ia = InetAddress.getLocalHost(); //获取本机内网IP log.info("启动成功:" + "http://" + ia.getHostAddress() + ":" + port + "/"); log.info("${user.home} :" + userName); } catch (UnknownHostException ex) { ex.printStackTrace(); } }; } }
方法2 使用lombok的@Slf4j,帮我们创建Logger对象,效果与方法1一样
/** * 配置内部类 */ @Slf4j @Controller @Configuration class Config { @Value("${user.home}") private String userName; /** * 端口 */ @Value("${server.port}") private String port;/** * 启动成功 */ @Bean public ApplicationRunner applicationRunner() { return applicationArguments -> { try { InetAddress ia = InetAddress.getLocalHost(); //获取本机内网IP log.info("启动成功:" + "http://" + ia.getHostAddress() + ":" + port + "/"); log.info("${user.home} :" + userName); } catch (UnknownHostException ex) { ex.printStackTrace(); } }; } }
简单配置
如果不需要进行复杂的日志配置,则在配置文件中进行简单的日志配置即可,默认情况下,SpringBoot日志只记录到控制台,不写日志文件。如果希望在控制台输出之外编写日志文件,则需要进行配置
spring: logging: path: /Users/Administrator/Desktop/杂七杂八/ims #日志文件路径 file: ims.log #日志文件名称 level: root: info #日志级别 root表示所有包,也可以单独配置具体包 fatal error warn info debug trace off
重新启动项目
打开ims.log
扩展配置
Spring Boot包含许多Logback扩展,可以帮助进行高级配置。您可以在您的logback-spring.xml配置文件中使用这些扩展。如果需要比较复杂的配置,建议使用扩展配置的方式
PS:SpringBoot推荐我们使用带-spring后缀的 logback-spring.xml 扩展配置,因为默认的的logback.xml标准配置,Spring无法完全控制日志初始化。(spring扩展对springProfile节点的支持)
以下是项目常见的完整logback-spring.xml,SpringBoot默认扫描classpath下面的logback.xml、logback-spring.xml,所以不需要再指定spring.logging.config,当然,你指定也没有问题
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--日志文件主目录:这里${user.home}为当前服务器用户主目录--> <property name="LOG_HOME" value="${user.home}/log"/> <!--日志文件名称:这里spring.application.name表示工程名称--> <springProperty scope="context" name="APP_NAME" source="spring.application.name"/> <!--默认配置--> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <!--配置控制台(Console)--> <include resource="org/springframework/boot/logging/logback/console-appender.xml"/> <!--配置日志文件(File)--> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!--设置策略--> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!--日志文件路径:这里%d{yyyyMMdd}表示按天分类日志--> <FileNamePattern>${LOG_HOME}/%d{yyyyMMdd}/${APP_NAME}.log</FileNamePattern> <!--日志保留天数--> <MaxHistory>15</MaxHistory> </rollingPolicy> <!--设置格式--> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <!-- 或者使用默认配置 --> <!--<pattern>${FILE_LOG_PATTERN}</pattern>--> <charset>utf8</charset> </encoder> <!--日志文件最大的大小--> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <MaxFileSize>100MB</MaxFileSize> </triggeringPolicy> </appender> <!-- 多环境配置 按照active profile选择分支 --> <springProfile name="dev"> <!--root节点 全局日志级别,用来指定最基础的日志输出级别--> <root level="INFO"> <appender-ref ref="FILE"/> <appender-ref ref="CONSOLE"/> </root> <!-- 子节点向上级传递 局部日志级别--> <logger level="WARN" name="org.springframework"/> <logger level="WARN" name="com.netflix"/> <logger level="DEBUG" name="org.hibernate.SQL"/> </springProfile> <springProfile name="prod"> </springProfile> </configuration>
启动项目,去到${user.home}当前服务器用户主目录,日志按日期进行产生,如果项目产生的日志文件比较大,还可以按照小时进行.log文件的生成
当然,使用简单配置照样能进行按日期分类
spring:
logging: path: ${user.home}/log/%d{yyyyMMdd} #日志文件路径 这里${user.home}为当前服务器用户主目录 file: ${spring.application.name}.log #日志文件名称 ${spring.application.name}为应用名 level: root: info #日志级别 root表示所有包,也可以单独配置具体包 fatal error warn info debug trace off
输出到Web页面
我们已经有日志文件.log了,为什么还要这个功能呢?(滑稽脸)为了偷懒!
当我们把项目部署到Linux服务器,当你想看日志文件,还得打开xshell连接,定位到log文件夹,麻烦;如果我们把日志输出到Web页面,当做超级管理员或者测试账号下面的一个功能,点击就开始实时获取生成的日志并输出在Web页面,是不是爽很多呢?
PS:这个功能可得小心使用,因为日志会暴露很多信息
LoggingWebSocketServer
使用WebSocket实现实时获取,建立WebSocket连接后创建一个线程任务,每秒读取一次最新的日志文件,第一次只取后面200行,后面取相比上次新增的行,为了在页面上更加方便的阅读日志,对日志级别单词进行着色(PS:如何创建springboot的websocket,请戳:SpringBoot系列——WebSocket)
package cn.huanzi.ims.socket; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thymeleaf.util.StringUtils; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * WebSocket获取实时日志并输出到Web页面 */ @Slf4j @Component @ServerEndpoint(value = "/websocket/logging", configurator = MyEndpointConfigure.class) public class LoggingWebSocketServer { /** * 连接集合 */ private static Map<String, Session> sessionMap = new HashMap<String, Session>(); private static Map<String, Integer> lengthMap = new HashMap<String, Integer>(); /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session) { //添加到集合中 sessionMap.put(session.getId(), session); lengthMap.put(session.getId(), 1);//默认从第一行开始 //获取日志信息 new Thread(() -> { log.info("LoggingWebSocketServer 任务开始"); boolean first = true; while (sessionMap.get(session.getId()) != null) { BufferedReader reader = null; try { //日志文件路径,获取最新的 String filePath = System.getProperty("user.home") + "/log/" + new SimpleDateFormat("yyyyMMdd").format(new Date()) + "/ims.log"; //字符流 reader = new BufferedReader(new FileReader(filePath)); Object[] lines = reader.lines().toArray(); //只取从上次之后产生的日志 Object[] copyOfRange = Arrays.copyOfRange(lines, lengthMap.get(session.getId()), lines.length); for (int i = 0; i < copyOfRange.length; i++) { String line = (String) copyOfRange[i]; //按等级标颜色,更加美观 line = line.replace("DEBUG", "<span style='color: blue;'>DEBUG</span>"); line = line.replace("INFO", "<span style='color: green;'>INFO</span>"); line = line.replace("WARN", "<span style='color: orange;'>WARN</span>"); line = line.replace("ERROR", "<span style='color: red;'>ERROR</span>"); copyOfRange[i] = line; } //存储最新一行开始 lengthMap.put(session.getId(), lines.length); //第一次如果太大,截取最新的200行就够了,避免传输的数据太大 if(first && copyOfRange.length > 200){ copyOfRange = Arrays.copyOfRange(copyOfRange, copyOfRange.length - 200, copyOfRange.length); first = false; } String result = StringUtils.join(copyOfRange, "<br/>"); //发送 send(session, result); //休眠一秒 Thread.sleep(1000); } catch (Exception e) { //捕获但不处理 e.printStackTrace(); } finally { try { reader.close(); } catch (IOException ignored) { } } } log.info("LoggingWebSocketServer 任务结束"); }).start(); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(Session session) { //从集合中删除 for (Map.Entry<String, Session> entry : sessionMap.entrySet()) { if (entry.getValue() == session) { sessionMap.remove(entry.getKey()); lengthMap.remove(entry.getKey()); break; } } } /** * 发生错误时调用 */ @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } /** * 服务器接收到客户端消息时调用的方法 */ @OnMessage public void onMessage(String message, Session session) { } /** * 封装一个send方法,发送消息到前端 */ private void send(Session session, String message) { try { session.getBasicRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } }
HTML页面
页面收到数据就追加到div中,为了方便新增了几个功能:
清屏,清空div内容
滚动至底部、将div的滚动条滑到最下面
开启/关闭自动滚动,div新增内容后自动将滚动条滑到最下面,点一下开启,再点关闭,默认关闭
PS:引入公用部分,就是一些jquery等常用静态资源
<!DOCTYPE> <!--解决idea thymeleaf 表达式模板报红波浪线--> <!--suppress ALL --> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>IMS实时日志</title> <!-- 引入公用部分 --> <script th:replace="head::static"></script> </head> <body> <!-- 标题 --> <h1 style="text-align: center;">IMS实时日志</h1> <!-- 显示区 --> <div id="loggingText" contenteditable="true" style="width:100%;height: 600px;background-color: ghostwhite; overflow: auto;"></div> <!-- 操作栏 --> <div style="text-align: center;"> <button onclick="$('#loggingText').text('')" style="color: green; height: 35px;">清屏</button> <button onclick="$('#loggingText').animate({scrollTop:$('#loggingText')[0].scrollHeight});" style="color: green; height: 35px;">滚动至底部 </button> <button onclick="if(window.loggingAutoBottom){$(this).text('开启自动滚动');}else{$(this).text('关闭自动滚动');};window.loggingAutoBottom = !window.loggingAutoBottom" style="color: green; height: 35px; ">开启自动滚动 </button> </div> </body> <script th:inline="javascript"> //websocket对象 let websocket = null; //判断当前浏览器是否支持WebSocket if ('WebSocket' in window) { websocket = new WebSocket("ws://localhost:10086/websocket/logging"); } else { console.error("不支持WebSocket"); } //连接发生错误的回调方法 websocket.onerror = function (e) { console.error("WebSocket连接发生错误"); }; //连接成功建立的回调方法 websocket.onopen = function () { console.log("WebSocket连接成功") }; //接收到消息的回调方法 websocket.onmessage = function (event) { //追加 if (event.data) { //日志内容 let $loggingText = $("#loggingText"); $loggingText.append(event.data); //是否开启自动底部 if (window.loggingAutoBottom) { //滚动条自动到最底部 $loggingText.scrollTop($loggingText[0].scrollHeight); } } } //连接关闭的回调方法 websocket.onclose = function () { console.log("WebSocket连接关闭") }; </script> </html>
效果展示
后记
有了日志记录,我们以后写代码时就要注意了,应使用下面的正确示例
//错误示例,这样写只会输出到控制台,不会输出到日志中 System.out.println("XXX"); e.printStackTrace(); //正确示例,既输出到控制台,又输出到日志 log.info("XXX"); log.error("XXX报错",e);
SpringBoot日志暂时先记录到这里,点击官网了解更多:https://docs.spring.io/spring-boot/docs/2.1.5.RELEASE/reference/htmlsingle/#boot-features-logging
原文地址:https://www.cnblogs.com/huanzi-qch/p/11041300.html
- ExecuteReader在执行有输出参数的存储过程时拿不到输出参数
- CTF---Web入门第十三题 拐弯抹角
- 小案例(四):销售额下滑(python)
- 【最新TensorFlow1.4.0教程01】TF1.4.0介绍与动态图机制 Eager Execution使用
- 把插入的数据自动备份到另一个表中 ~ 语境:本地和服务器自动同步
- 数据分析小案例(三):调查问卷(python)
- CTF---Web入门第十六题 天下武功唯快不破
- 数据分析小案例(二):面包是不是变轻了(python)
- 数据分析小案例(一):商业街抽奖(python)
- Bagging算法(R语言)
- iOS @property探究(一): 基础详解你要知道的@property都在这里
- 在创建带输出参数和返回值的存储过程时---犯下的一个低级错误
- iOS block探究(二): 深入理解你要知道的block都在这里
- 使用开源人脸特征提取器进行脸部颜值评分
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 详解php用static方法的原因
- phpinfo无法显示的原因及解决办法
- 在php的yii2框架中整合hbase库的方法
- PHP安装memcache扩展的步骤讲解
- python退出循环的方法
- PHP crypt()函数的用法讲解
- Python如何自动获取目标网站最新通知
- PHP+mysql实现的三级联动菜单功能示例
- Python调用shell cmd方法代码示例解析
- 实例讲解PHP表单处理
- 用python实现名片管理系统
- keras 简单 lstm实例(基于one-hot编码)
- keras用auc做metrics以及早停实例
- Laravel中错误与异常处理的用法示例
- Laravel用户授权系统的使用方法示例