logback整合rabbit

Posted 我的梦想是宇宙和平

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了logback整合rabbit相关的知识,希望对你有一定的参考价值。

一、前言

最近在做日志采集的通用解决方案,需要解决一个比较小的需求:

在每次代码使用logger.error("msg")的时候将该日志信息发送到rabbit,并通过logstash传输到es做存储,传输的字段主要为以下几个:

level:日志等级(logback自带)
message:日志消息(logback自带)
time:日志时间(logback自带)
class-line:发送日志的代码位置(logback自带)
ip:发送日志的服务器ip(需要额外配置)
applicationName:服务名(需要额外配置)


二、logback

在网上找到了一些解决方案,大致思路如下:

1.导入logback、spring-amqp依赖

2.编写logback-spring.xml,并在application.yml中配置该文件路径

(由于需要额外配置ip,所以还需要额外多1步)

3.编写类继承ClassConverter,返回当前服务器ip


接下来一步步看:

1.导入依赖

<dependency>
  <groupId>org.springframework.amqp</groupId>
  <artifactId>spring-rabbit</artifactId>
  <version>2.2.6.RELEASE</version>
</dependency>


2.编写logback-rabbit.xml,配置application.yml

logbak-rabbit.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <!-- 引入ip converter以获取服务器ip -->
   <conversionRule conversionWord="ip" converterClass="cn.pubinfo.log_helper.config.IPLogConfig" />
   <!-- 引入spring中rabbit相关的配置 -->
   <springProperty scope="context" name="log_dir" source="log.path"/>
   <springProperty name="rabbitmqHost" source="spring.rabbitmq.host"/>
   <springProperty name="rabbitmqPort" source="spring.rabbitmq.port"/>
   <springProperty name="rabbitmqUsername" source="spring.rabbitmq.username"/>
   <springProperty name="rabbitmqPassword" source="spring.rabbitmq.password"/>
   <springProperty name="applicationName" source="spring.application.name"/>
   <!-- Console log -->
   <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
       <encoder>
           <pattern>%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) [%class:%line] %highlight(%-5level) - %cyan(%msg%n)
           </pattern>
           <charset>UTF-8</charset>
       </encoder>
   </appender>

   <appender name="AMQP" class="org.springframework.amqp.rabbit.logback.AmqpAppender">
       <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
          //过滤warn以下等级的日志不会发送到rabbit
           <level>WARN</level>
       </filter>
       <layout>
           <!-- 以json格式配置消息体发送到rabbitmq -->
           <pattern>
               <![CDATA[
               {
                   "time":
                       "%d{yyyy-MM-dd HH:mm:ss}",
                   "class-line":
                       "%class.%method:%line",
                   "level":
                       "%-5level",
                   "message":
                       "%msg",
                   "applicationName":
                       "${applicationName}",
                   "ip":
                       "%ip"
               }]]>
           </pattern>
       </layout>
       <!-- 配置rabbit -->
       <addresses>${rabbitmqHost}:${rabbitmqPort}</addresses>
       <username>${rabbitmqUsername}</username>
       <password>${rabbitmqPassword}</password>

       <!-- 自动生成exchange -->
       <declareExchange>true</declareExchange>
       <!-- 指定应用id -->
       <applicationId>logback</applicationId>
       <!-- 设置交换器类型 -->
       <exchangeType>direct</exchangeType>
       <!-- 设置交换器名称 -->
       <exchangeName>logback</exchangeName>
       <!-- 设置路由键 -->
       <routingKeyPattern>logback</routingKeyPattern>
       <generateId>true</generateId>
       <charset>UTF-8</charset>
       <!-- 设置为持久化 -->
       <durable>true</durable>
       <deliveryMode>NON_PERSISTENT</deliveryMode>
   </appender>

   <!-- The file record only records the log of the specified package -->
  //这里制定了controller,若想整个项目都发送日志,则指定对应包名即可,如com.example
   <logger name="com.example.es.controller" level="warn" additivity="false">
       <appender-ref ref="STDOUT"/>
       <appender-ref ref="AMQP"/>
   </logger>

   <root level="INFO">
       <appender-ref ref="STDOUT"/>
   </root>
</configuration>

application.yml

追加以下内容:
logging:
config: classpath:logback-rabbit.xml


3.编写类继承ClassConverter,返回当前服务器ip

package cn.pubinfo.logback.config;

import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;

public class IPLogConfig extends ClassicConverter {
   private static final Logger logger = LoggerFactory.getLogger(IPLogConfig.class);
   private volatile static Inet4Address inetAddr = null;

   @Override
   public String convert(ILoggingEvent iLoggingEvent) {
       try {
           if (inetAddr==null){
               inetAddr = getLocalIp4Address().get();
          }
           return inetAddr.getHostAddress();
      } catch (SocketException e) {
           e.printStackTrace();
      }
       return null;
  }

   public static List<Inet4Address> getLocalIp4AddressFromNetworkInterface() throws SocketException {
       List<Inet4Address> addresses = new ArrayList<>(1);
       Enumeration e = NetworkInterface.getNetworkInterfaces();
       if (e == null) {
           return addresses;
      }
       while (e.hasMoreElements()) {
           NetworkInterface n = (NetworkInterface) e.nextElement();
           if (!isValidInterface(n)) {
               continue;
          }
           Enumeration ee = n.getInetAddresses();
           while (ee.hasMoreElements()) {
               InetAddress i = (InetAddress) ee.nextElement();
               if (isValidAddress(i)) {
                   addresses.add((Inet4Address) i);
              }
          }
      }
       return addresses;
  }

   /**
    * 过滤回环网卡、点对点网卡、非活动网卡、虚拟网卡并要求网卡名字是eth或ens开头
    *
    * @param ni 网卡
    * @return 如果满足要求则true,否则false
    */
   private static boolean isValidInterface(NetworkInterface ni) throws SocketException {
       return !ni.isLoopback() && !ni.isPointToPoint() && ni.isUp() && !ni.isVirtual()
               && (ni.getName().startsWith("eth") || ni.getName().startsWith("ens") ||ni.getName().startsWith("wlan"));
  }

   /**
    * 判断是否是IPv4,并且内网地址并过滤回环地址.
    */
   private static boolean isValidAddress(InetAddress address) {
       return address instanceof Inet4Address && address.isSiteLocalAddress() && !address.isLoopbackAddress();
  }


   private static Optional<Inet4Address> getIpBySocket() throws SocketException {
       try (final DatagramSocket socket = new DatagramSocket()) {
           socket.connect(InetAddress.getByName("www.baidu.com"), 10002);
           if (socket.getLocalAddress() instanceof Inet4Address) {
               return Optional.of((Inet4Address) socket.getLocalAddress());
          }
      } catch (UnknownHostException e) {
           throw new RuntimeException(e);
      }
       return Optional.empty();
  }

   public static Optional<Inet4Address> getLocalIp4Address() throws SocketException {
       final List<Inet4Address> ipByNi = getLocalIp4AddressFromNetworkInterface();
       if (ipByNi.isEmpty() || ipByNi.size() > 1) {
           final Optional<Inet4Address> ipBySocketOpt = getIpBySocket();
           if (ipBySocketOpt.isPresent()) {
               return ipBySocketOpt;
          } else {
               return ipByNi.isEmpty() ? Optional.empty() : Optional.of(ipByNi.get(0));
          }
      }
       return Optional.of(ipByNi.get(0));
  }

   public static void main(String[] args) throws UnknownHostException, SocketException {
       System.out.println(getIpBySocket().get().getHostAddress());
  }
}


三、配置logstash

input{
rabbitmq{
      host => "*.*.*.*"
      port => 5672
      user => ""
      password => ""
      queue => "logback"
      key => "logback"
      exchange => "logback"
      durable => true
      subscription_retry_interval_seconds => 5
      type => logback
}
}

output{
if[type] == "logback"{
  elasticsearch {
    hosts => ["127.0.0.1:9200"]
    user => "elastic"
    password => "changeme"
    index => "logback"
  }
}
}


四、大功告成

这样就差不多结束了,如果想要做es的自定义mapping,则需要额外操作es,不过对于一些简单的需求来说,es的自动生成映射也足够应付了。以下给出我的es mapping:

PUT /logback
{
"mappings": {
  "properties": {
    "time":{
      "format": "yyyy-MM-dd HH:mm:ss",
      "type":"date"
    },
    "applicationName":{"type":"text"},
    "class-line":{"type":"text"},
    "ip":{"type":"text"},
    "level":{"type":"keyword"},
    "message":{"type":"text"}
  }
}
}


五、其他

如果项目不使用配置中心如nacos,那么以上的操作就完全满足需求了,但是若使用nacos,则yml中关于logging和rabbit的配置则需要全部转移至nacos,否则会产生undefined错误,目前原因还没有找到。


以上是关于logback整合rabbit的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot整合+logback日志配置

SpringBoot整合+logback日志配置

Rabbitmq基本使用 SpringBoot整合Rabbit SpringCloud Stream+Rabbit

Spring Boot:Spring Boot整合Logback和PageHelper

springboot整合rabbit,支持消息确认机制

Spring Cloud Stream整合Rabbit之重复投递