Springboot项目使用aop切面保存详细日志到ELK日志平台
上一篇讲过了将Springboot项目中logback日志插入到ELK日志平台,它只是个示例。这一篇来看一下实际使用中,我们应该怎样通过aop切面,拦截所有请求日志插入到ELK日志系统。同时,由于往往我们有很多个服务,都需要记录日志,为每个服务都搭建一个ELK并不现实,所以我们采用集中化管理日志,将所有日志都插到同一个ELK中。这样又会遇到另一个问题,就是ES中的Index如果只有一个,那么所有日志都混杂在一个Index里,为日后的检索带来不便,我们希望能根据不同的应用来拆分为不同的ES的Index。
下面看看怎么做。
配置logback
同样的,新建一个Springboot项目,勾选aop。
首先我们来配置logback,见logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<!-- 本地日志文件 -->
<appender name="MY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<Prudent>true</Prudent>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{yyyy-MM-dd HH:mm:ss} -%msg%n</Pattern>
</layout>
<encoder>
<pattern>%-4relative [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<!-- Logstash日志 -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<!-- 远程Logstash的IP:PORT -->
<destination>IP:4560</destination>
<!-- encoder必须要有,是logback和logstash的纽带 -->
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
<!-- 用户自定义Json字符串,用于向logstash提交额外的自定义属性。可在logstash的input里接收该参数 -->
<customFields>{"appName":"testelk"}</customFields>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="LOGSTASH"/>
<appender-ref ref="CONSOLE"/>
<appender-ref ref="MY_FILE"/>
</root>
</configuration>
为了演示,我们将日志保存3份,一份在控制台打印,即appender-ref中的CONSOLE,再一份存文件,即MY_FILE,这两个在很早之前的文章Springboot使用logback详解中有介绍。
重点看一下LOGSTASH的配置,destination配置你的Logstash的ip和端口,请修改为自己服务器的ip地址,4560是Logstash默认的端口。通过LogstashTcpSocketAppender继承的类源码可以看到
注意里面有destinations的集合,用于接收xml配置的ip,说明可以配置多个。然后还有一个encoder属性,接收一个Encoder对象。见xml里配置的encoder
encoder可以接收一个customFields的属性,见xml配置,这是一个json字符串,用于给Logstash传递自定义的值,设置之后就会在kibana里默认给每条数据都带着这个属性。
这个appName就是在xml里配置的自定义属性。之前有讲过,要使用kibana管理ES,ES的index必须要有timestamp字段。如果是不通过Logstash插入ES的话,自定义的model必须要有FieldType.DATE字段,使用Logstash的话,就可以不加这个字段。原因就是见源码里的,里面有timezone,version等都是该encoder源码自动给我们补上的。
设置appName意义何在?下面请看。
配置Logstash
创建编辑一个Logstash的配置文件,不会的去看前两篇
input {
# For detail config for log4j as input,
# See: https://www.elastic.co/guide/en/logstash/current/plugins-inputs-log4j.html
tcp {
mode => "server"
host => "0.0.0.0"
port => 4560
codec => json_lines
}
}
filter {
#Only matched data are send to output.
}
output {
# For detail config for elasticsearch as output,
# See: https://www.elastic.co/guide/en/logstash/current/plugins-outputs-elasticsearch.html
elasticsearch {
index => "%{[appName]}" #The operation on ES
hosts => "localhost:9200" #ElasticSearch host, can be array.
action => "index" #The index to write data to.
}
}
input没什么说的,设置端口和codec即可,主要是output,我们看elasticsearch的index标签,这个代表将日志插入到ES的哪个index里,这里我们不写死,而是使用获取客户端传来的appName来指定index,Logstash通过动态指定ES的index,就可以完成不同的项目存储到不同的ES的index。这样既完成了日志统一管理,又区分开了不同的项目。
配置完毕,启动Logstash即可。
设置aop拦截
logging: file: ./logback.log
创建aop切面类,拦截所有Controller。
package com.tianyalei.testelk.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.ObjectError;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
/**
* Created by wuwf on 17/4/27.
* 日志切面
*/
@Aspect
@Component
public class LogAspect {
private Logger logger = LoggerFactory.getLogger(getClass().getName());
private ObjectError error;
@Pointcut("execution(public * com.tianyalei.testelk.controller.*.*(..))")
public void webLog() {
}
@Before("webLog()")
public void deBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
logger.info("-------------------用户发起请求-----------------");
// 记录下请求内容
logger.info("URL : " + request.getRequestURL().toString());
logger.info("HTTP_METHOD : " + request.getMethod());
//如果是表单,参数值是普通键值对。如果是application/json,则request.getParameter是取不到的。
logger.info("HTTP_HEAD Type : " + request.getHeader("Content-Type"));
logger.info("IP : " + request.getRemoteAddr());
logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
if ("application/json".equals(request.getHeader("Content-Type"))) {
//记录application/json时的传参,SpringMVC中使用@RequestBody接收的值
logger.info(getRequestPayload(request));
} else {
//记录请求的键值对
for (String key : request.getParameterMap().keySet()) {
logger.info(key + "----" + request.getParameter(key));
}
}
}
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info("方法的返回值 : " + ret);
logger.info("------------------请求结束------------------");
}
//后置异常通知
@AfterThrowing(throwing = "ex", pointcut = "webLog()")
public void throwss(JoinPoint jp, Exception ex){
logger.info("方法异常时执行.....");
}
//后置最终通知,final增强,不管是抛出异常或者正常退出都会执行
@After("webLog()")
public void after(JoinPoint jp){
// logger.info("方法最后执行.....");
}
private String getRequestPayload(HttpServletRequest req) {
StringBuilder sb = new StringBuilder();
try(BufferedReader reader = req.getReader()) {
char[]buff = new char[1024];
int len;
while((len = reader.read(buff)) != -1) {
sb.append(buff,0, len);
}
}catch (IOException e) {
e.printStackTrace();
}
return sb.toString();
}
}
在这里获取用户的请求地址、参数和返回值。
package com.tianyalei.testelk.controller;
import com.tianyalei.testelk.service.LoginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class IndexController {
private Logger logger = LoggerFactory.getLogger(getClass().getName());
@Autowired
LoginService loginService;
@GetMapping("index")
public Object index() {
logger.info("进入了index方法");
logger.info("开始执行业务逻辑");
logger.info("index方法结束");
return "success";
}
@PostMapping("login")
public Object login() {
logger.info("进入了login方法");
loginService.login();
logger.info("登录成功");
return "success";
}
}
Service如下
package com.tianyalei.testelk.service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
@Service
public class LoginService {
private Logger logger = LoggerFactory.getLogger(getClass().getName());
public void login() {
logger.info("进入Service方法,执行登录查询");
}
}
OK了,如果你的spring-logback.xml里配置的ip和port是对的,那么日志就能进到ELK了。
2017-08-08 17:11:52.603 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : -------------------用户发起请求----------------- 2017-08-08 17:11:52.604 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : URL : http://localhost:8080/login 2017-08-08 17:11:52.604 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : HTTP_METHOD : POST 2017-08-08 17:11:52.605 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : HTTP_HEAD Type : application/x-www-form-urlencoded 2017-08-08 17:11:52.605 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : IP : 0:0:0:0:0:0:0:1 2017-08-08 17:11:52.607 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : CLASS_METHOD : com.tianyalei.testelk.controller.IndexController.login 2017-08-08 17:11:52.607 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : key:name----value:wolf 2017-08-08 17:11:52.607 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : key:pass----value:123456 2017-08-08 17:11:52.610 INFO 89159 --- [nio-8080-exec-1] c.t.testelk.controller.IndexController : 进入了login方法 2017-08-08 17:11:52.611 INFO 89159 --- [nio-8080-exec-1] c.t.testelk.service.LoginService : 进入Service方法,执行登录查询 2017-08-08 17:11:52.611 INFO 89159 --- [nio-8080-exec-1] c.t.testelk.controller.IndexController : 登录成功 2017-08-08 17:11:52.611 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : 方法的返回值 : success 2017-08-08 17:11:52.613 INFO 89159 --- [nio-8080-exec-1] com.tianyalei.testelk.aop.LogAspect : ------------------请求结束------------------
查看ES数据:
我们可以看到index名为testelk的数据如图。说明自定义的index已经成功创建,值也插入成功了。
可以看到记录的日志了。
Springboot项目使用aop切面保存详细日志到ELK日志平台的更多相关文章
- Springboot项目启动不了。也不打印任何日志信息。
Springboot项目启动不了.也不打印任何日志信息. <!-- 在创建Spring Boot工程时,我们引入了spring-boot-starter,其中包含了spring-boot-sta ...
- Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志
其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 ...
- Spring MVC通过AOP切面编程 来拦截controller 实现日志的写入
首选需要参考的是:[参考]http://www.cnblogs.com/guokai870510826/p/5977948.html http://www.cnblogs.com/guokai8 ...
- 利用AOP切面打印项目中每个接口的运行情况
1.前言 AOP切面技术,大家应该都听知道,Spring框架的主要功能之一. AOP切面的用途很广,其中一个常见的用途就是打印接口方法的运行日志和运行时间. 日志对于一个项目很是重要,不仅有助于调错, ...
- Springboot项目搭配ELK日志平台
上一篇讲过了elasticsearch和kibana的可视化组合查询,这一篇就来看看大名鼎鼎的ELK日志平台是如何搞定的. elasticsearch负责数据的存储和检索,kibana提供图形界面便于 ...
- 搞定springboot项目连接远程服务器上kafka遇到的坑以及完整的例子
版本 springboot 2.1.5.RELEASE kafka 2.2 遇到的坑 用最新的springboot就要用最新的kafka版本! 当我启动云服务器上的zk后,再启动kafka后台日志也没 ...
- springboot项目基础面试题
1.springboot与spring的区别. 引用自官方说法: java在集成spring等框架需要作出大量的配置,开发效率低,繁琐.所以官方提出 spring boot的核心思想:习惯优于配置.可 ...
- SpringBoot微服务电商项目开发实战 --- api接口安全算法、AOP切面及防SQL注入实现
上一篇主要讲了整个项目的子模块及第三方依赖的版本号统一管理维护,数据库对接及缓存(Redis)接入,今天我来说说过滤器配置及拦截设置.接口安全处理.AOP切面实现等.作为电商项目,不仅要求考虑高并发带 ...
- 如何优雅地在 Spring Boot 中使用自定义注解,AOP 切面统一打印出入参日志 | 修订版
欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 <Java 核心知识整理&面试.pdf>资源链接!! 个人网站: https://www.ex ...
随机推荐
- UVa 10817 校长的烦恼
https://vjudge.net/problem/UVA-10817 题意: 某校有m个教师和n个求职者,需讲授s个课程,已知每人的工资c和能教的课程集合,要求支付最少的工资使得每门课都至少有两名 ...
- UVa 11134 传说中的车
https://vjudge.net/problem/UVA-11134 题意:在n*n的棋盘上放n个车,使得任意两个车不相互攻击,且第i个车在一个给定的矩形Ri之内.用4个整数xli,yli,xri ...
- Z-score(Z值)的意义--转载
http://blog.sina.com.cn/s/blog_72208a6a0101cdt1.html http://www.docin.com/p-350677620.html http://we ...
- Android广播接收器和Activity间传递数据
Activity向广播接收器传递数据很简单,只需要在发送广播前将数据put进Intent中就行了. 广播接收器怎么向Activity传送数据?这里要用到接口,通过在广播接收器里定义一个接口,然后让接收 ...
- linux——通信指令学习简单笔记
一: 指令名称:write 指令所在路径:/usr/bin/write 执行权限:All User 语法:write <用户名> 功能描述:向另外一个用户发信息,以Ctrl+D作 为结束 ...
- hdu 6020 MG loves apple 恶心模拟
题目链接:点击传送 MG loves apple Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 262144/262144 K (Ja ...
- Ubuntu 关闭防火墙
关闭防火墙:service iptables stop
- js 中的 堆栈
1.含义及对比 堆和栈都是运行时内存中分配的一个数据区,因此也被称为堆区和栈区: 二者存储的数据类型和处理速度不同: 堆(heap)用于复杂数据类型(引用类型)分配空间,例如数组对象.object对象 ...
- Linux中CentOS6.5 64位 系统下安装docker步骤
CentOS6.5 64位 (docker目前仅支持64位)内核必须在3.10及以上 1. uname -r 查看内核版本 2. 升级内核到3.10版本(带aufs模块) cd /etc ...
- MSSQL 一坑 SQL Management Studio 管理工具的快捷方式被删掉了
如果确定已经安装的情况下,到这里去找下吧(我这里用的是sql 2008) C:\Program Files\Microsoft SQL Server\100\Tools\Binn\VSShell\Co ...