关注本头条号,每天坚持更新原创干货技术文章。
如需学习视频,请在微信搜索公众号“智传网优”直接开始自助视频学习
1. 前言
本文主要介绍如果高效地使用工具高亮显示某些输出结果和关键字。
您可以使用正则表达式和特定规则格式突出显示文本。分层规则格式易于理解,但功能非常强大。该注释将从标准输入读取每一行,每行根据FILE中定义的规则突出显示,然后打印到标准输出(屏幕)。
高效地使用工具高亮显示某些输出结果和关键字
remark命令一般很少人知道它可以把系统日志文件关键内容高亮标注颜色并打印到屏幕上,例如像ping,traceroute等等命令的输出结果。一般情况下,我们需要自己写正则表达式定义哪些关键结果应该高亮。有了remark命令后,你可能不太需要自己手动编写规则了。
2. 安装`remark`命令
Debian / Ubuntu Linux 执行以下命令安装remark:
[root@zcwyou ~]# cd /tmp [root@zcwyou ~]# wget http://savannah.nongnu.org/download/regex-markup/regex-markup_0.10.0-1_amd64.deb [root@zcwyou ~]# sudo dpkg -i regex-markup_0.10.0-1_amd64.deb
RHEL / CentOS / Fedora Linux 64用户,执行以下命令安装remark命令:
[root@zcwyou ~]# cd /tmp [root@zcwyou ~]# wget http://savannah.nongnu.org/download/regex-markup/regex-markup-0.10.0-1.x86_64.rpm [root@zcwyou ~]# rpm -ivh regex-markup-0.10.0-1.x86_64.rpm
或者你从源代码编译它
[root@zcwyou ~]# cd /tmp [root@zcwyou ~]# wget http://savannah.nongnu.org/download/regex-markup/regex-markup-0.10.0.tar.gz [root@zcwyou ~]# tar -xvf regex-markup-0.10.0.tar.gz [root@zcwyou ~]# cd regex-markup-0.10. [root@zcwyou ~]# ./configure [root@zcwyou ~]# make [root@zcwyou ~]# sudo make install
安装remark命令自带高亮语法
3. `remark`语法
command1 | remark /path/to/config
command2 arg1 arg2 | remark /path/to/config
高亮显示ping的执行结果
[root@zcwyou ~]# ping -c 4 www.linuxrumen.com | remark /usr/share/regex-markup/ping
你可以创建一个bash shell 功能和它入到~/.bashrc file的配置文件中
[root@zcwyou ~]# ping() { /bin/ping $@ | remark /usr/share/regex-markup/ping; }
remark-pin 的规则文件放在/usr/share/regex-markup/ping。它由样式和宏定义以及匹配语句组成。匹配语句的顺序很重要,因为它们是从上到下执行的。样式和宏在使用之前需要定义。规则文件的语法类似于编程语言(如C和Java)的缩写,并且使用空格并不重要:
[root@zcwyou ~]# cat /usr/share/regex-markup/ping
它的文件内容一般是这样的
# Rules to highlight the output of ping(8) include "common" # Special: Color all lines that don't match any of the rules below red /.*/ red /^PING ([-.a-zA-Z0-9]+) \(([-.a-zA-Z0-9]+)\) ([0-9]+)\(([0-9]+)\) bytes of data\.$/ { default 1 blue 2 green 3,4 red break # this is merely to skip the matches below } /^PING ([-.a-zA-Z0-9]+) \(([-.a-zA-Z0-9]+)\): ([0-9]+) data bytes$/ { default 1 blue 2 green 3 red break # this is merely to skip the matches below } /^([0-9]+) bytes from ([-.a-zA-Z0-9]+) \(([-.a-zA-Z0-9]+)\): icmp_seq=([0-9]+) ttl=([0-9]+) time=(.*)$/ { default 1,4,5,6 red 2 blue 3 green break } /^([0-9]+) bytes from ([-.a-zA-Z0-9]+): icmp_seq=([0-9]+) ttl=([0-9]+) time=(.*)$/ { default 1,3,4,5 red 2 green break } /^--- ([-.a-zA-Z0-9]+) ping statistics ---$/ { default 1 blue break } /^([0-9]+) packets transmitted, ([0-9]+) packets received, ([0-9]+)% packet loss$/ { default 1,2,3 red break } /^([0-9]+) packets transmitted, ([0-9]+) received, ([0-9]+)% packet loss, time ([0-9]+ms)$/ { default 1,2,3,4 red break } /^round-trip min\/avg\/max = ([.0-9]+)\/([.0-9]+)\/(.*)$/ { default 1,2,3 red break } /^rtt min\/avg\/max\/mdev = ([.0-9]+)\/([.0-9]+)\/([.0-9]+)\/(.*)$/ { default 1,2,3,4 red break }
默认的样式定义文件放在/usr/share/regex-markup/common
试试跟traceroute结合
[root@zcwyou ~]# traceroute www.linuxrumen.com | remark /usr/local/share/regex-markup/traceroute
执行以下命令把Linux系统日志高亮显示
[root@zcwyou ~]# grep something /var/log/syslog | remark /usr/share/regex-markup/syslog [root@zcwyou ~]# tail -f /var/log/syslog | remark /usr/share/regex-markup/syslog
使用remark命令高亮显示diff结果
先使用diff命令对比file1和file2,然后重定向到remark,由remark高亮显示结果
[root@zcwyou ~]# diff file1 file2 | remark /usr/share/regex-markup/diff
使用remark命令高亮显示mark结果
[root@zcwyou ~]# cd /path/to/build [root@zcwyou ~]# make | remark /usr/share/regex-markup/make
4. 如何定制高亮规则?
如果你有这样的需求,请参考remark命令手册:
[root@zcwyou ~]# man remark
如何定制高亮规则
本文已同步至博客站,尊重原创,转载时请在正文中附带以下链接:
https://www.linuxrumen.com/cyml/1201.html
点击了解更多,快速查看更多的技术文章列表。
日志是我们运维用来分析问题和处理问题的重要维护依据,但是Nginx的日志文件是非常庞大的,而且以文本格式显示导致Nginx的日志直接分析非常困难。有时候为了初步处理或紧急处理问题,我们也对直接打开Nginx的日志文件进行分析,这里我们就介绍如何对Nginx的默认日志格式进行分析和含义的解读。
在安装好Nginx后系统会默认给你开通access.log访问日志功能,这个配置文件在etc/nginx的文件夹下,打开配置文件后如下面是按行进行的默认格式配置。
log_format main ' $remote_addr - $remote_user [$time_local] "$request" '' $status $body_bytes_sent " $http_referer" '' "$http_user_agent" "$http_x_forwarded_for" ';
Nginx日志的配置项目:access_log /var/log/nginx/access.log main;
日志写入文本文件是一行行的,这里我们把一行日志,按3个字段进行分行进行分析。
61.135.165.19 - 这里是访问者的IP地址
- 这里是客户名称,但一般获取不到。
[17/Nov/2018:03:39:32 +0800] 这里是访问的时间
"GET /ask/0901/images/a13.jpg HTTP/1.0" 这个是这次记录的访问对象
200 返回的状态码
19916 这条记录访问对象的大小
"http://xx.xx.xx/ask/0901/index.htm?jh=haerbinlvyou-YD&gjc=7riyoudongbei&e_keywordid=%7Bkeywordid%7D" 这个是从什么入口访问的也就是来源。
"Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C;Baiduspider-ads)AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30" 这个是客户端浏览器的一些参数
"10.205.160.32" 这里是代理的地址,一般是没有的。
remote_addr 远程请求IP地址
remote_user 客户端用户的名称,没有时用-符合代替
time_local 本地时间戳
request 请求具体URI文件,例如网页中的JPG图片。
status http请求状态
body_bytes_sent 请求文件大小
http_refer 网页url跳转来源,源地址,可以伪造。例如一个HTML网页然后调用图片URI资源。
http_user_agent 用户终端浏览器UserAgent的相关信息
http_x_forwarded_for是HTTP的请求端真实的IP,只有在通过了HTTP 代理或者负载均衡服务器时才会添加该项
其他非默认的选项
host 请求host地址
request_time 整个请求的总时间
upstream_addr 后台提供服务的地址(即转发处理的目标地址)
upstream_reponse_time请求时,upstream的响应时间
upstream_status upstream状态
通过如下方式达到日志切割:
# vi logcron.sh log_dir="/data/logs/nginx" date_dir=`date +%Y%m%d` /bin/mkdir -p ${log_dir}/${date_dir} > /dev/null 2>&1 /bin/mv ${log_dir}/access.log ${log_dir}/${date_dir}/access.log
这里只需要定义一个cron,在每天晚上23:59:50执行这个脚本就可以了。
篇幅有限,关于nginx的access.log访问日志方面就介绍到这了,后面会分享更多关于devops和DBA方面内容,感兴趣的朋友可以关注下!
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: [/*]
如何打印日志?
方法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一样
/**
* 配置内部类
*/
@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();
}
};
}
}
如果不需要进行复杂的日志配置,则在配置文件中进行简单的日志配置即可,默认情况下,SpringBoot日志只记录到控制台,不写日志文件。如果希望在控制台输出之外编写日志文件,则需要进行配置
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,当然,你指定也没有问题
logging:
config: classpath:logback-spring.xml
<?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文件的生成
2021-02-24更新:
如果需要按日志级别分别输出到对应的日志文件,在appender标签新增filter标签进行指定
<!-- 时间滚动输出 level为 【debug / info / warn / error】 日志 -->
<appender name="【DEBUG / INFO / WARN / ERROR】_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 忽略其他配置 -->
<!-- 此日志文件只记录 【debug / info / warn / error】 级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>【debug / info / warn / error】</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
我们已经有日志文件.log了,为什么还要这个功能呢?(滑稽脸)为了偷懒!
当我们把项目部署到Linux服务器,当你想看日志文件,还得打开xshell连接,定位到log文件夹,麻烦;如果我们把日志输出到Web页面,当做超级管理员或者测试账号下面的一个功能,点击就开始实时获取生成的日志并输出在Web页面,是不是爽很多呢?
PS:这个功能可得小心使用,因为日志会暴露很多信息
使用WebSocket实现实时获取,建立WebSocket连接后创建一个线程任务,每秒读取一次最新的日志文件,第一次只取后面200行,后面取相比上次新增的行,为了在页面上更加方便的阅读日志,对日志级别单词进行着色(PS:如何创建springboot的websocket,请戳:SpringBoot系列——WebSocket)
package cn.huanzi.qch.springbootlogback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket获取实时日志并输出到Web页面
*/
@Slf4j
@Component
@ServerEndpoint(value = "/websocket/logging", configurator = MyEndpointConfigure.class)
public class LoggingWSServer {
@Value("${spring.application.name}")
private String applicationName;
/**
* 连接集合
*/
private static Map<String, Session> sessionMap = new ConcurrentHashMap<String, Session>();
private static Map<String, Integer> lengthMap = new ConcurrentHashMap<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()) + "/"+applicationName+".log";
//字符流
reader = new BufferedReader(new FileReader(filePath));
Object[] lines = reader.lines().toArray();
//只取从上次之后产生的日志
Object[] copyOfRange = Arrays.copyOfRange(lines, lengthMap.get(session.getId()), lines.length);
//对日志进行着色,更加美观 PS:注意,这里要根据日志生成规则来操作
for (int i = 0; i < copyOfRange.length; i++) {
String line = (String) copyOfRange[i];
//先转义
line = line.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll("\"", """);
//处理等级
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>");
//处理类名
String[] split = line.split("]");
if (split.length >= 2) {
String[] split1 = split[1].split("-");
if (split1.length >= 2) {
line = split[0] + "]" + "<span style='color: #298a8a;'>" + split1[0] + "</span>" + "-" + split1[1];
}
}
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) {
//从集合中删除
sessionMap.remove(session.getId());
lengthMap.remove(session.getId());
}
/**
* 发生错误时调用
*/
@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();
}
}
}
页面收到数据就追加到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
2019-07-03补充:我们之前只对日志等级关键字进行着色,还是觉得不够,因此又新增了类名着色跟HTML转义
主要修改:
效果:
2019-08-12补充:我发现有时候显示的时候,换行不太准确,我们原先是在行末追加<br/>,但有时候读取出来的一行记录是自动换行后的数据,页面显示效果很丑
因此我改成用正则([\d+][\d+][\d+][\d+]-[\d+][\d+]-[\d+][\d+] [\d+][\d+]:[\d+][\d+]:[\d+][\d+])去匹配日期,然后再对应的起始下标插入<br/>,从而达到与控制台输出类似的效果
匹配、插入结果
页面效果
异步输出日志
异步输出日志的方式很简单,添加一个基于异步写日志的appender,并指向原先配置的appender即可
<!-- 将文件输出设置成异步输出 -->
<appender name="ASYNC-FILE" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="FILE"/>
</appender>
<!-- 将控制台输出设置成异步输出 -->
<appender name="ASYNC-CONSOLE" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>256</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="CONSOLE"/>
</appender>
原理很简单,主线程将日志扔到阻塞队列中,然后IO操作日志写入文件是通过新起一个线程去完成的
2020-05-26补充
e.printStackTrace();会打出详细异常,异常名称,出错位置,便于调试用,但直接调用会输出到std.err,并没有输出到日志文件中,因此需要先输出到流中再转成字符串
封装工具类
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* 捕获报错日志处理工具类
*/
public class ErrorUtil {
/**
* Exception出错的栈信息转成字符串
* 用于打印到日志中
*/
public static String errorInfoToString(Throwable e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
// 将出错的栈信息输出到printWriter中
e.printStackTrace(pw);
pw.flush();
sw.flush();
} finally {
if (sw != null) {
try {
sw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
if (pw != null) {
pw.close();
}
}
return sw.toString();
}
}
也可以使用骚操作简化代码
public static String errorInfoToString(Throwable e) {
//try-with-resource语法糖 处理机制
try(StringWriter sw = new StringWriter();PrintWriter pw = new PrintWriter(sw)){
e.printStackTrace(pw);
pw.flush();
sw.flush();
return sw.toString();
}catch (Exception ignored){
throw new RuntimeException(ignored.getMessage(),ignored);
}
}
使用
try {
//省略其他代码
} catch (Throwable e) {
//之前的操作,输出控制台
e.printStackTrace();
//输出到日志文件中
log.error(ErrorUtil.errorInfoToString(e));
}
2020-08-04更新
SpringBoot默认使用内置Tomcat,那么我们如何配置Tomcat的Access Logging呢?
详情可查看官方文档:
SpringBoot配置介绍:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-configure-accesslogs
Apache Tomcat配置介绍:https://tomcat.apache.org/tomcat-8.5-doc/config/valve.html#Access_Logging
SpringBoot对Access Log的默认配置
server.tomcat.accesslog.buffered=true # Whether to buffer output such that it is flushed only periodically.
server.tomcat.accesslog.directory=logs # Directory in which log files are created. Can be absolute or relative to the Tomcat base dir.
server.tomcat.accesslog.enabled=false # Enable access log.
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd # Date format to place in the log file name.
server.tomcat.accesslog.pattern=common # Format pattern for access logs.
server.tomcat.accesslog.prefix=access_log # Log file name prefix.
server.tomcat.accesslog.rename-on-rotate=false # Whether to defer inclusion of the date stamp in the file name until rotate time.
server.tomcat.accesslog.request-attributes-enabled=false # Set request attributes for the IP address, Hostname, protocol, and port used for the request.
server.tomcat.accesslog.rotate=true # Whether to enable access log rotation.
server.tomcat.accesslog.suffix=.log # Log file name suffix.
日志格式说明(摘自上方Apache Tomcat配置介绍官方文档)
Values for the pattern attribute are made up of literal text strings, combined with pattern identifiers prefixed by the "%" character to cause replacement by the corresponding variable value from the current request and response. The following pattern codes are supported:
There is also support to write information incoming or outgoing headers, cookies, session or request attributes and special timestamp formats. It is modeled after the Apache HTTP Server log configuration syntax. Each of them can be used multiple times with different xxx keys:
All formats supported by SimpleDateFormat are allowed in %{xxx}t. In addition the following extensions have been added:
These formats cannot be mixed with SimpleDateFormat formats in the same format token.
Furthermore one can define whether to log the timestamp for the request start time or the response finish time:
By adding multiple %{xxx}t tokens to the pattern, one can also log both timestamps.
The shorthand pattern pattern="common" corresponds to the Common Log Format defined by '%h %l %u %t "%r" %s %b'.
The shorthand pattern pattern="combined" appends the values of the Referer and User-Agent headers, each in double quotes, to the common pattern.
When Tomcat is operating behind a reverse proxy, the client information logged by the Access Log Valve may represent the reverse proxy, the browser or some combination of the two depending on the configuration of Tomcat and the reverse proxy. For Tomcat configuration options see Proxies Support and the Proxy How-To. For reverse proxies that use mod_jk, see the generic proxy documentation. For other reverse proxies, consult their documentation.
我们只需要配置以下几个简单配置即可
#开启内置Tomcat请求日志 access.log
server.tomcat.accesslog.enabled=true
#日志格式
server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b
#日志输出目录
server.tomcat.accesslog.directory=${user.home}/log/accesslog/${spring.application.name}
#日志文件名
server.tomcat.accesslog.prefix=access_log
server.tomcat.accesslog.file-date-format=_yyyy-MM-dd
server.tomcat.accesslog.suffix=.log
效果
如何接口统计QPS?
如上图中,logging接口,我们只要统计同一秒中,logging接口的请求次数即是该接口的QPS
代码已经开源、托管到我的GitHub、码云:
GitHub:https://github.com/huanzi-qch/springBoot
码云:https://gitee.com/huanzi-qch/springBoot
作者:huanzi-qch
出处:https://www.cnblogs.com/huanzi-qch
若标题中有“转载”字样,则本文版权归原作者所有。若无转载字样,本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利.
*请认真填写需求信息,我们会在24小时内与您取得联系。