ELK统一日志管理

ELK

为什么用到ELK

        一般我们需要进行日志分析场景:直接在日志文件中 grep、awk 就可以获得自己想要的信息。但在规模较大的场景中,此方法 效率低下,面临问题包括日志量太大如何归档、文本搜索太慢怎么办、如何多维度查询。需要集中化的日志管理,所有服务器 上的日志收集汇总。常见解决思路是建立集中式日志收集系统,将所有节点上的日志统一收集,管理,访问。

        一般大型系统是一个分布式部署的架构,不同的服务模块部署在不同的服务器上,问题出现时,大部分情况需要根据问题暴露 的关键信息,定位到具体的服务器和服务模块,构建一套集中式日志系统,可以提高定位问题的效率。

        一个完整的集中式日志系统,需要包含以下几个主要特点:

  • 收集-能够采集多种来源的日志数据
  • 传输-能够稳定的把日志数据传输到中央系统
  • 存储-如何存储日志数据
  • 分析-可以支持 UI 分析
  • 警告-能够提供错误报告,监控机制

        ELK提供了一整套解决方案,并且都是开源软件,之间互相配合使用,完美衔接,高效的满足了很多场合的应用。目前主流的 一种日志系统。

ELK简介

        ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana , 它们都是开源软件。

        Elasticsearch是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自动发现,索 引自动分片,索引副本机制,restful风格接口,多数据源,自动搜索负载等。

        Logstash 主要是用来日志的搜集、分析、过滤日志的工具,支持大量的数据获取方式。一般工作方式为c/s架构,client端安装 在需要收集日志的主机上,server端负责将收到的各节点日志进行过滤、修改等操作在一并发往elasticsearch上去。

        Kibana 也是一个开源和免费的工具,Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面,可以帮助 汇总、分析和搜索重要数据日志

ELK架构图

       此架构由Logstash收集各个节点上相关日志、数据,并经过分析、过滤后发送给远端服务器上的Elasticsearch进行存储。 Elasticsearch将数据以分片的形式压缩存储并提供多种API供用户查询,操作。用户亦可以更直观的通过配置Kibana Web方便 的对日志查询,并根据数据生成报表。

      但是如果远端Logstash server因故障停止运行,数据便会丢失,所以后续可能会引入redis或者kafka作为消息中间件,将队列 中消息或数据间接传递给Logstash,Logstash过滤、分析后将数据传递给Elasticsearch存储,最后由Kibana将日志和数据呈现 给用户。这样即使远端logstash宕机,消息也会存在消息中间件,等待logstash恢复,继续消费消息,从而避免数据丢失。

Logstash工作原理

      Logstash事件处理有三个阶段:inputs → filters → outputs。是一个接收,处理,转发日志的工具。支持系统日志, webserver日志,错误日志,应用日志,总之包括所有可以抛出来的日志类型。

 Input:输入数据到logstash。

一些常用的输入为:

  • file:从文件系统的文件中读取,类似于tail -f命令
  • syslog:在514端口上监听系统日志消息,并根据RFC3164标准进行解析
  • redis:从redis service中读取
  • beats:从filebeat中读取 

Filters:数据中间处理,对数据进行操作。

一些常用的过滤器为:

  • grok:解析任意文本数据,Grok 是 Logstash 最重要的插件。它的主要作用就是将文本格式的字符串,转换成为具体的结构化 的数据,配合正则表达式使用。内置120多个解析语法。

          官方提供的grok表达式:https://github.com/logstash-plugins/logstash-patterns-core/tree/master/patterns

          grok在线调试: https://grokdebug.herokuapp.com/

  • mutate:对字段进行转换。例如对字段进行删除、替换、修改、重命名等。
  • drop:丢弃一部分events不进行处理。
  • clone:拷贝 event,这个过程中也可以添加或移除字段。
  • geoip:添加地理信息(为前台kibana图形化展示使用)

Outputs:outputs是logstash处理管道的最末端组件。 

一个event可以在处理过程中经过多重输出,但是一旦所有的outputs都执行结束,这个event也就完成生命周期。 一些常见的outputs为:

  • elasticsearch:可以高效的保存数据,并且能够方便和简单的进行查询。
  • file:将event数据保存到文件中。
  • graphite:将event数据发送到图形化组件中,一个很流行的开源存储图形化展示的组件。
  • Codecs:codecs 是基于数据流的过滤器,它可以作为input,output的一部分配置。Codecs可以帮助你轻松的分割发送过来已经被序列化的数据。 

一些常见的codecs:

  • json:使用json格式对数据进行编码/解码。
  • multiline:将汇多个事件中数据汇总为一个单一的行。比如:java异常信息和堆栈信息。

安装ElasticSearch

下载安装ElasticSearch 6.3.0

mkdir es
cd es
wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.0.tar.gz
tar -zxvf elasticsearch-6.3.0.tar.gz

修改es配置文件

vim /config/elasticsearch.yml
#本机ip
network.host: xxx.xxx.xx.xxx
#默认监听端口
http.port: 9200

运行ElasticSearch 6.3.0

配置完成,es不允许root运行,新建个帐号来运行

useradd elk
passwd elk

输入俩次密码

回到上级目录并更改elasticsearch的拥有者

chown -R elk elasticsearch-6.3.0

切换到elk用户

su elk

后台运行它,并将启动日志记录到es.log(自己建log文件夹,并给elk用户赋log文件夹操作权限,当然直接后台启动默认log也可 以)

nohup ./bin/elasticsearch >/usr/local/es/log/es.log

安装Logstash

下载安装logstash

mkdir logstash
cd logstash
wget https://artifacts.elastic.co/downloads/logstash/logstash-6.3.0.tar.gz
tar -zxvf logstash-6.3.0.tar.gz
cd logstash-6.3.0/

创建一个配置文件(logstash-es.conf)

vim config/logstash-es.conf
input {
  tcp {
    port => 4560
    codec => json_lines
  }
}
output {
  elasticsearch {
    hosts => "3x.1xx.7x.1xx"
    index => "log_%{+YYYY.MM.dd}_%{[appname]}"
  }
  stdout {
    codec => rubydebug
  }
}

注意:

1.input.tcp : 中配置的是本机地址,ip和端口必须和springboot的logback.xml中的配置完全一样,不能一个配ip一个localhost

2.output.elasticsearch : 配置elasticsearch服务器的ip

3.%{[appname]} : 引用springboot的logback.xml中配置的变量

4.output.stdout : 在终端显示输出信息(可以不配置)

5.conf文件一定要严格按照tab缩进,否则启动logstash时会异常

运行logstash

nohup ./bin/logstash -f ./config/logstash-es.conf >/usr/local/logstash/log/logstash.log &

安装Kibana

mkdir kibana
cd kibana
wget https://artifacts.elastic.co/downloads/kibana/kibana-6.3.0-linux-x86_64.tar.gz
tar -zxvf kibana-6.3.0-linux-x86_64.tar.gz
cd kibana-6.3.0-linux-x86_64

修改配置文件

vim config/kibana.y
server.host: "0.0.0.0"
elasticsearch.url: "http://your es ip:port"
elasticsearch.username: "elastic"
elasticsearch.password: "changeme

运行kibana

nohup ./bin/kibana >/usr/local/kibana/log/kibana.log &

SpringBoot整合Logback,并打印log到logstash

项目pom.xml文件引入logback依赖

<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>4.11</version>
</dependency>

 resource里添加logback.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property resource="application.properties"/>
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>3x.1xx.7x.1xx:4560</destination>
<!-- <filter class="ch.qos.logback.classic.filter.LevelFilter">-->
<!-- &lt;!&ndash;过滤 INFO&ndash;&gt;-->
<!-- <level>INFO</level>-->
<!-- &lt;!&ndash;匹配到就禁止&ndash;&gt;-->
<!-- <onMatch>DENY</onMatch>-->
<!-- &lt;!&ndash;没有匹配到就允许&ndash;&gt;-->
<!-- <onMismatch>ACCEPT</onMismatch>-->
<!-- </filter>-->
<encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"appname":"app-server1"}</customFields>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder charset="UTF-8"> <!--encoder 可以指定字符集,对于中文输出有意义-->
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.ntnikka" level="Error" additivity="false">
<appender-ref ref="LOGSTASH"/>
</logger>
<logger name="com.ntnikka" level="INFO" additivity="false">
<appender-ref ref="STDOUT" />
</logger>
<!-- <root level="Error">-->
<!-- <appender-ref ref="LOGSTASH" />-->
<!-- <appender-ref ref="STDOUT" />-->
<!-- </root>-->
</configuration>

配置logstash IP 和 PORT

<destination>3x.1xx.7x.1xx:4560</destination>

多台IP或者多个端口logstash配置

<connectionStrategy>
    <roundRobin>
        <connectionTTL>5 minutes</connectionTTL>
    </roundRobin>
</connectionStrategy>

向logstash输出日志如果有多个logstash IP或端口可以轮询负载各端口

不同的应用模块可以配置不同的模块名,便于log查找

<customFields>{"appname":"app-server1"}</customFields>

{"appname":"app-server1","xxx":"xxx",..}如果有需要也可以添加其他自定义参数

测试

编写demo示例

@RestController
@RequestMapping("/test")
public class demoController {
  private static final Logger logger = LoggerFactory.getLogger(demoController.class);
  @Autowired
  demoService demoService;
  @sysLog
  @RequestMapping("/sysLog")
  public String testSysLog(){
    // demoService.demoMethod();
    divide();
    return "test demo";
  }
  public static void divide(){
    // int i = 10 /0;
    throw new MaynException("MaynException", "test MaynException",40000);
  }
}

项目中异常统一处理,编写handle类,也可以程序中自己trycatch异常,在catch代码块中自己通过logback打印异常日志

/**
* 异常处理器
*/
@RestControllerAdvice
public class MaynExceptionHandler {
  private Logger logger = LoggerFactory.getLogger(this.getClass());

  /**
  * 处理自定义异常
  */
  @ExceptionHandler(MaynException.class)
  public R handleMaynException(MaynException e) { R r = new R();
    r.put("code", e.getCode());
    r.put("msg", e.getMessage()); logger.error(e.getMessage(),e); return r;
  }

  @ExceptionHandler(DuplicateKeyException.class)
  public R handleDuplicateKeyException(DuplicateKeyException e) { logger.error(e.getMessage(), e);
    return R.error("数据库中已存在该记录");
  }


  @ExceptionHandler(Exception.class) 
public R handleException(Exception e) { System.out.println("===================== enter exceptionHandle ================="); logger.error(e.getMessage(), e); return R.error(); } }

在统一处理中记录异常日志

logger.error(e.getMessage(), e);

启动springboot项目,调用接口测试,控制台输出

08:48:23.369 [http-nio-9090-exec-5] ERROR com.ntnikka.common.RRExceptionHandler - MaynException
com.ntnikka.exception.MaynException: MaynException
at com.ntnikka.rhlogsystem.controller.demoController.divide(demoController.java:36)
at com.ntnikka.rhlogsystem.controller.demoController.testSysLog(demoController.java:30)
at
com.ntnikka.rhlogsystem.controller.demoController$$FastClassBySpringCGLIB$$dc504d05.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at
org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:7
49)
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at
org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoin
Point.java:88)
at com.ntnikka.aspect.SysLogAspect.around(SysLogAspect.java:52)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at
org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvic
e.java:644)
at
org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:633)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:70)
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:175)
at
org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:93
)
at
org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at
org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688
)
at
com.ntnikka.rhlogsystem.controller.demoController$$EnhancerBySpringCGLIB$$213fe19f.testSysLog(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at
org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189)
at
org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java
:138)
at
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(Servle
tInvocableHandlerMethod.java:102)
at
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(Req
uestMappingHandlerAdapter.java:892)
at
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestM
appingHandlerAdapter.java:797)
at
org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapte
r.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at com.alibaba.druid.support.http.WebStatFilter.doFilter(WebStatFilter.java:123)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at
org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:834)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1415)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)

查看kibana

 

 异常日志成功输出到kibana,项目整合logback成功

原文地址:https://www.cnblogs.com/superSubfn/p/14426624.html