1、背景

虽然log4j很强大,可以将日志输出到文件、DB、ES等。但是有时候确难免完全适合自己,此时我们就需要自定义Appender,使日志输出到指定的位置上。

本文,将通过两个例子说明自定义APPender,一个是将日志写入文件中,另一个是将日志发送到远程Thrift服务中。

本文代码详见:https://github.com/hawkingfoo/log-demo

2、自定义文件Appender

2.1 定义文件Appender

先上代码:

  1.  
    @Plugin(name = "FileAppender", category = "Core", elementType = "appender", printObject = true)
  2.  
    public class FileAppender extends AbstractAppender {
  3.  
    private String fileName;
  4.  
     
  5.  
    /* 构造函数 */
  6.  
    public FileAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions, String fileName) {
  7.  
    super(name, filter, layout, ignoreExceptions);
  8.  
    this.fileName = fileName;
  9.  
    }
  10.  
     
  11.  
    @Override
  12.  
    public void append(LogEvent event) {
  13.  
    final byte[] bytes = getLayout().toByteArray(event);
  14.  
    writerFile(bytes);
  15.  
     
  16.  
    }
  17.  
     
  18.  
    /* 接收配置文件中的参数 */
  19.  
    @PluginFactory
  20.  
    public static FileAppender createAppender(@PluginAttribute("name") String name,
  21.  
    @PluginAttribute("fileName") String fileName,
  22.  
    @PluginElement("Filter") final Filter filter,
  23.  
    @PluginElement("Layout") Layout<? extends Serializable> layout,
  24.  
    @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
  25.  
    if (name == null) {
  26.  
    LOGGER.error("no name defined in conf.");
  27.  
    return null;
  28.  
    }
  29.  
    if (layout == null) {
  30.  
    layout = PatternLayout.createDefaultLayout();
  31.  
    }
  32.  
    // 创建文件
  33.  
    if (!createFile(fileName)) {
  34.  
    return null;
  35.  
    }
  36.  
    return new FileAppender(name, filter, layout, ignoreExceptions, fileName);
  37.  
    }
  38.  
     
  39.  
    private static boolean createFile(String fileName) {
  40.  
    Path filePath = Paths.get(fileName);
  41.  
    try {
  42.  
    // 每次都重新写文件,不追加
  43.  
    if (Files.exists(filePath)) {
  44.  
    Files.delete(filePath);
  45.  
    }
  46.  
    Files.createFile(filePath);
  47.  
    } catch (IOException e) {
  48.  
    LOGGER.error("create file exception", e);
  49.  
    return false;
  50.  
    }
  51.  
    return true;
  52.  
    }
  53.  
     
  54.  
    private void writerFile(byte[] log) {
  55.  
    try {
  56.  
    Files.write(Paths.get(fileName), log, StandardOpenOption.APPEND);
  57.  
    } catch (IOException e) {
  58.  
    LOGGER.error("write file exception", e);
  59.  
    }
  60.  
    }
  61.  
    }

上面代码有几个需要注意的地方:

  • @Plugin..注解:这个注解,是为了在之后配置log4j2.xml时,指定的Appender Tag。
  • 构造函数:除了使用父类的以外,也可以增加一些自己的配置。
  • 重写append()方法:这里面需要实现具体的逻辑,日志的去向。
  • createAppender()方法:主要是接收log4j2.xml中的配置项。

2.2 添加log4j2.xml配置

  1.  
    <?xml version="1.0" encoding="UTF-8"?>
  2.  
     
  3.  
    <configuration status="INFO" monitorInterval="30">
  4.  
    <appenders>
  5.  
    <!--这个输出控制台的配置-->
  6.  
    <console name="Console" target="SYSTEM_OUT">
  7.  
    <!--输出日志的格式-->
  8.  
    <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [%l] %m%n}"/>
  9.  
    </console>
  10.  
     
  11.  
    <!-- 这个就是自定义的Appender -->
  12.  
    <FileAppender name="File" fileName="log.log">
  13.  
    <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
  14.  
    </FileAppender>
  15.  
     
  16.  
    </appenders>
  17.  
     
  18.  
    <loggers>
  19.  
    <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
  20.  
    <logger name="org.springframework" level="INFO"></logger>
  21.  
    <logger name="org.mybatis" level="INFO"></logger>
  22.  
    <root level="all">
  23.  
    <appender-ref ref="Console"/>
  24.  
    <appender-ref ref="File"/>
  25.  
    </root>
  26.  
    </loggers>
  27.  
    </configuration>

备注:

  • 上面的log配置,一共配了2个输出。一个是终端输出,一个是采用自定义的FileAppender输出到文件中。
  • <FileAppender>标签要与自定义Appender中的类注解保持一致。

2.3 测试

  1.  
    public class TestLogFile {
  2.  
    private static final Logger logger = LogManager.getLogger(TestLogFile.class);
  3.  
     
  4.  
    public static void main(String[] args) {
  5.  
    logger.info("1");
  6.  
    logger.info("2");
  7.  
    logger.info("3");
  8.  
    }
  9.  
    }

日志输出

日志输出

可以看到,日志一共输出了2份,一份到终端中,一份到log.log中(具体的文件路径可在log4j2.xml中配置)。

3、自定义Thrift Appender

上一节,主要是日志的文件输出。有时我们需要将日志发送给日志收集服务,常见的方法可以写一个日志收集Agent,收集日志;或者将日志输出方当成客户端直接发送到远程。

下文,通过自定义Appender的方式,将日志输出到远程的RPC服务中。

3.1 Thrift RPC服务

假设现在有一个Thrift RPC服务,实时接收日志消息。它的定义是下面的样子:

  1.  
    namespace java thrift
  2.  
     
  3.  
    service LogServer {
  4.  
    string getLogRes(1:string log);
  5.  
    }

服务很简单,入参是log,返回值是String。
Thrift相关知识可以查看,Thrift RPC服务10分钟上手

3.2 定义ThriftAppender

  1.  
    @Plugin(name = "ThriftAppender", category = "Core", elementType = "appender", printObject = true)
  2.  
    public class ThriftAppender extends AbstractAppender {
  3.  
     
  4.  
    private LogServer.Client client;
  5.  
    private TTransport transport;
  6.  
     
  7.  
    /* 构造函数 */
  8.  
    public ThriftAppender(String name, Filter filter, Layout<? extends Serializable> layout, boolean ignoreExceptions, String host) {
  9.  
    super(name, filter, layout, ignoreExceptions);
  10.  
    // 创建客户端
  11.  
    createThriftClient(host);
  12.  
    }
  13.  
     
  14.  
    @Override
  15.  
    public void append(LogEvent event) {
  16.  
    final byte[] bytes = getLayout().toByteArray(event);
  17.  
    try {
  18.  
    String response = client.getLogRes(new String(bytes));
  19.  
    System.out.println(response);
  20.  
    } catch (TException e) {
  21.  
    e.printStackTrace();
  22.  
    }
  23.  
    }
  24.  
     
  25.  
    /* 接收配置文件中的参数 */
  26.  
    @PluginFactory
  27.  
    public static ThriftAppender createAppender(@PluginAttribute("name") String name,
  28.  
    @PluginAttribute("host") String host,
  29.  
    @PluginElement("Filter") final Filter filter,
  30.  
    @PluginElement("Layout") Layout<? extends Serializable> layout,
  31.  
    @PluginAttribute("ignoreExceptions") boolean ignoreExceptions) {
  32.  
    if (name == null) {
  33.  
    LOGGER.error("no name defined in conf.");
  34.  
    return null;
  35.  
    }
  36.  
    if (layout == null) {
  37.  
    layout = PatternLayout.createDefaultLayout();
  38.  
    }
  39.  
    return new ThriftAppender(name, filter, layout, ignoreExceptions, host);
  40.  
    }
  41.  
     
  42.  
    @Override
  43.  
    public void stop() {
  44.  
    if (transport != null) {
  45.  
    transport.close();
  46.  
    }
  47.  
    }
  48.  
     
  49.  
    private void createThriftClient(String host) {
  50.  
    try {
  51.  
    transport = new TFramedTransport(new TSocket(host, 9000));
  52.  
    transport.open();
  53.  
    TProtocol protocol = new TBinaryProtocol(transport);
  54.  
    client = new LogServer.Client(protocol);
  55.  
    LOGGER.info("create client success");
  56.  
    } catch (Exception e) {
  57.  
    LOGGER.error("create file exception", e);
  58.  
    }
  59.  
    }
  60.  
    }

备注:
除了和文件Appender相同的外,这里需要注意两个地方。一个是Thrift Client的创建,另一个Thrift发送log。
具体的发送逻辑,在append()方法中实现。

3.3 添加log4j2.xml配置

  1.  
    <?xml version="1.0" encoding="UTF-8"?>
  2.  
     
  3.  
    <configuration status="INFO" monitorInterval="30">
  4.  
    <appenders>
  5.  
    <!--这个输出控制台的配置-->
  6.  
    <console name="Console" target="SYSTEM_OUT">
  7.  
    <!--输出日志的格式-->
  8.  
    <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [%l] %m%n}"/>
  9.  
    </console>
  10.  
     
  11.  
    <!-- 这个就是自定义的Appender -->
  12.  
    <ThriftAppender name="Thrift" host="127.0.0.1">
  13.  
    <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] [%-5p] {%F:%L} - %m%n" />
  14.  
    </ThriftAppender>
  15.  
    </appenders>
  16.  
     
  17.  
    <loggers>
  18.  
    <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
  19.  
    <logger name="org.springframework" level="INFO"></logger>
  20.  
    <logger name="org.mybatis" level="INFO"></logger>
  21.  
    <root level="all">
  22.  
    <appender-ref ref="Console"/>
  23.  
    <appender-ref ref="Thrift"/>
  24.  
     
  25.  
    </root>
  26.  
    </loggers>
  27.  
    </configuration>

这里同样是定义了两个输出路径,一个是终端,一个是Thrift服务。

3.4 测试

  1.  
    public class TestThriftFile {
  2.  
    private static final Logger logger = LogManager.getLogger(TestThriftFile.class);
  3.  
     
  4.  
    public static void main(String[] args) {
  5.  
    logger.info("a");
  6.  
    logger.info("b");
  7.  
    logger.info("c");
  8.  
    }
  9.  
    }

Server端

Client端

可以看出,Server端成功接收到了log。

log4j2自定义Appender(输出到文件/RPC服务中)的更多相关文章

  1. Log4j/Log4j2自定义Appender来实现日志级别计数统计及监控

    一.简述 本文主要讲如何基于Log4j2来实现自定义的Appender.一般用途是用于Log4j2自带的Appender不足以满足我们的需求,或者需要我们对日志进行拦截统计等操作时,需要我们自定义Ap ...

  2. [原创]java WEB学习笔记41:简单标签之带属性的自定义标签(输出指定文件,计算并输出两个数的最大值 demo)

    本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...

  3. SpringBoot | 第二十五章:日志管理之自定义Appender

    前言 前面两章节我们介绍了一些日志框架的常见配置及使用实践.一般上,在开发过程中,像log4j2.logback日志框架都提供了很多Appender,基本上可以满足大部分的业务需求了.但在一些特殊需求 ...

  4. 软件性能测试分析与调优实践之路-JMeter对RPC服务的性能压测分析与调优-手稿节选

    一.JMeter 如何通过自定义Sample来压测RPC服务 RPC(Remote Procedure Call)俗称远程过程调用,是常用的一种高效的服务调用方式,也是性能压测时经常遇到的一种服务调用 ...

  5. 【spring boot logback】日志使用自定义的logback-spring.xml文件后,application.properties中关于日志的相关配置还会起作用么

    本篇 将针对[日志使用自定义的logback-spring.xml文件后,application.properties中关于日志的相关配置还会起作用么]这一个主题进行探索. 这个测试项目是根据[spr ...

  6. linux重定向总结:如何将shell命令的输出信息自动输出到文件中保存

    在做批量实验室,例如跑批量MR的作业,我们会写好shell脚本,然后启动脚本,等所有作业执行完再去看结果,但是这些执行时的信息如何保存下来到文件中呢?下面这个命令可以完成这个任务. sh batchj ...

  7. Java自定义日志输出文件

    Java自定义日志输出文件 日志的打印,在程序中是必不可少的,如果需要将不同的日志打印到不同的地方,则需要定义不同的Appender,然后定义每一个Appender的日志级别.打印形式和日志的输出路径 ...

  8. log4j日志输出到文件的配置

    1.Maven的dependency 2.log4j.properties的配置 3.Junit的Test类 4.web.xml的配置(非必要) 5.spring的db.config的配置(非必要) ...

  9. 基于netty轻量的高性能分布式RPC服务框架forest<上篇>

    工作几年,用过不不少RPC框架,也算是读过一些RPC源码.之前也撸过几次RPC框架,但是不断的被自己否定,最近终于又撸了一个,希望能够不断迭代出自己喜欢的样子. 顺便也记录一下撸RPC的过程,一来作为 ...

随机推荐

  1. selenium相关:通过location 和 size 获取元素所在像素位置和尺寸,截取图片ROI

    1.实验 #https://captcha.luosimao.com/demo/ chrome default: location 不滚动,直接返回相对整个html的坐标 {'x': 15.0, 'y ...

  2. [转]简单三步,用 Python 发邮件

    https://zhuanlan.zhihu.com/p/24180606 0. 前言 发送电子邮件是个很常见的开发需求.比如你写了个监控天气的脚本,发现第二天要下雨,或者网站上关注的某个商品降价了, ...

  3. 请推荐几个asp.net下做网站的好的开源框架

    1.We7 CMS We7 CMS是由西部动力开发的一款充分发掘互联网Web2.0(如博客.RSS等)的信息组织优势,将其理念利用到政府企事业网站的构建.组织.管理中的网站建设和管理方面的产品. 系统 ...

  4. http--tomcat--memcached配置

    两个tomcat节点:172.16.100.7(tomcatA.magedu.com),172.16.100.8(tomcatB.magedu.com) 两个memcached节点:172.16.10 ...

  5. Python学习(二十七)—— Django和pymysql搭建学员管理系统

    转载自http://www.cnblogs.com/liwenzhou/p/8270250.html 一.学员管理系统 1.项目规划阶段 项目背景 近年来老男孩教育的入学学员数量稳步快速增长,传统的e ...

  6. BZOJ4409 [Usaco2016 Feb]Circular barn 动态规划 斜率优化

    原文链接http://www.cnblogs.com/zhouzhendong/p/8724739.html 题目传送门 - BZOJ4409 题意 有一个N个点的环,相邻两个点距离是1.点顺时针标号 ...

  7. 2018-03-11 20165235祁瑛《Java程序设计》第二周学习总结

    2018-03-11 20165235祁瑛<Java程序设计>第二周学习总结 教材学习内容总结 第二章要点: 在这一章中我学到了很多东西: (1)布尔类型boolean,布尔类型的赋值只能 ...

  8. 设计模式之单例模式及应用demo

    单例模式是创建型模式之一. 单例模式顾名思义是单例的,也就是只有一个实例化对象,这都来源于它的私有化构造函数. 单例模式特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3. ...

  9. Java中的不同遍历方式

    已知一个Person类: public class Person implements Comparable<Person>{ String name; String id; public ...

  10. HDU 1281 棋盘游戏 (枚举+最大匹配)

    <题目链接> Problem Description 小希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的“车”,并且使得他们不能互相攻击,这当然很简单 ...