031 Log4j日志框架

1、日志框架的介绍

日志对于项目的重要性不言而喻,现在市面上的日志框架多种多样:logback-classic(SLF4J的亲儿子)、java.util.logging(JDK自带记录日志类)、log4j1(不建议继续使用)、log4j2(log4j1的新一代版本)、reload4j(解决Apache log4j version 1.2.17重要安全问题的分支) 等等,如果没有真正深入了解过,可能会被搞得眼花缭乱。本文将介绍目前Java项目中最常见的Log4j2 + Slf4j的使用组合,这也是我自己项目中目前使用的:MapReduce编程。

两个官网使用教程:SLF4J user manualApache Log4j 2

用于绑定日志门面SLF4J与日志框架实现的适配器介绍
在这里插入图片描述
用于重定向第三方日志框架到日志门面SLF4J的桥接器介绍(统一多个日志框架的输出)
在这里插入图片描述
简单的说,SLF4J提供日志接口以及获取具体日志对象的方法,不与任何日志实现强行绑定,通过适配器绑定具体的日志实现以进行日志的输出,通过桥接器将第三方日志重定向到SLF4J以实现不同日志框架的统一输出。SLF4J结合使用适配器桥接器,统一了主流框架的输出(适配器+桥接器,多个日志框架输出重定向到指定的日志框架,实现日志的统一输出)、方便了日志输出的改造(桥接器,旧日志框架直接重定向到新的日志框架,不需要修改旧日志打印代码)、简化了日志打印的编程(适配器,不同日志框架使用同一套API)。

2、MapReduce使用Log4j2 + Slf4j输出日志示例

src/main/resources/log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration status="WARN" monitorInterval="30">
    <!-- 可以设置公共属性 -->
    <Properties>
        <property name="logPath">/opt/logs</property>
        <property name="charset">UTF-8</property>
        <property name="pattern">[%d{yyyy-MM-dd HH:mm:ss.SSS}][%-5p] [%t] [%c{1}:%M %L] %m %n</property>
    </Properties>
    <!--先定义所有的appender-->
    <appenders>
        <!--这个输出控制台的配置-->
        <console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式 ${pattern}使用属性变量-->
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
        </console>
        <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
        <File name="log" fileName="${logPath}/log/test.log" append="false">
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
        </File>
        <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileInfo" fileName="${logPath}/logs/info.log"
                     filePattern="${logPath}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
            <Policies>
                <!-- 按天分割 -->
                <TimeBasedTriggeringPolicy modulate="true" interval="1" />
                <SizeBasedTriggeringPolicy size="100MB" />
            </Policies>
            <!-- max 最多20个文件  IfLastModified 保留日志的天数 超过30天删除旧的日志  basePath 删除目录 maxDepth 搜索层数-->
            <DefaultRolloverStrategy max="20">
                <Delete basePath="${logPath}/logs/$${date:yyyy-MM}/" maxDepth="1">
                    <IfFileName glob="info-*.log" />
                    <IfLastModified age="30d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
        <RollingFile name="RollingFileWarn" fileName="${logPath}/logs/warn.log"
                     filePattern="${sys:user.home}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">
            <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
            <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
            <Policies>
                <!-- 按天分割 -->
                <TimeBasedTriggeringPolicy modulate="true" interval="1" />
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>
        <RollingFile name="RollingFileError" fileName="${logPath}/logs/error.log"
                     filePattern="${logPath}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">
            <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
            <Policies>
                <TimeBasedTriggeringPolicy/>
                <SizeBasedTriggeringPolicy size="100 MB"/>
            </Policies>
        </RollingFile>
        <!-- RollingFileXing日志 com.gitee.xing-->
        <RollingFile name="RollingFileWordCountDriver" fileName="${logPath}/logs/HelloWorld.log"
                     filePattern="${logPath}/logs/$${date:yyyy-MM}/xing-%d{yyyy-MM-dd}-%i.log">
            <PatternLayout pattern="${pattern}" charset="${charset}"/>
            <Policies>
                <TimeBasedTriggeringPolicy modulate="true" interval="1" /> <!-- 按天分割 -->
                <SizeBasedTriggeringPolicy size="100MB" /> <!-- 按100MB分割 -->
            </Policies>
            <DefaultRolloverStrategy max="20">
                <Delete basePath="${logPath}/logs/$${date:yyyy-MM}/" maxDepth="1">
                    <IfFileName glob="xing-*.log" />
                    <IfLastModified age="30d" />
                </Delete>
            </DefaultRolloverStrategy>
        </RollingFile>
    </appenders>
    <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
    <loggers>
        <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
        <logger name="org.springframework" level="INFO"></logger>
        <logger name="org.mybatis" level="INFO"></logger>
        <!-- 设置Logger的additivity="false"只在自定义的Appender中进行输出 -->
        <!--name属性可以指定特定类以进行特定的日志输出-->
        <Logger name="WordCountDriver" additivity="true" level="INFO">
                <appender-ref ref="RollingFileWordCountDriver" level="WARN" />
        </Logger>
        <root level="all">
            <appender-ref ref="Console"/>
            <appender-ref ref="log"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
        </root>
    </loggers>
</configuration>
pom.xml

Maven 指令 mvn dependency:tree 查看依赖Jar 的关系

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.jieky</groupId>
    <artifactId>Hadoop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <hadoop.version>3.3.3</hadoop.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!--引入hadoop客户端依赖时,需要进行log4j依赖项的排除-->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.3.3</version>
            <exclusions>
                <!--The slf4j API-->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
                <!-- As of version 1.7.35, declaring a dependency on org.slf4j:slf4j-log4j12
                redirects to org.slf4j:slf4j-reload4j by virtue of Maven <relocation> directive.-->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-log4j12</artifactId>
                </exclusion>
                <!--This artifact was moved to:org.apache.logging.log4j » log4j-core-->
                <exclusion>
                    <groupId>log4j</groupId>
                    <artifactId>log4j</artifactId>
                </exclusion>
                <!--If you wish to use reload4j as the underlying logging framework, all you need
                to do is to declare "org.slf4j:slf4j-reload4j" as a dependency in your pom.xml file
                as shown below. In addition to slf4j-reload4j-1.7.36.jar, this will pull in
                slf4j-api-1.7.36.jar as well as reload4j-1.2.19.jar into your project. Note that
                explicitly declaring a dependency on reload4j-1.2.19.jar or slf4j-api-1.7.36.jar is
                not wrong and may be necessary to impose the correct version of said artifacts by
                virtue of Maven's "nearest definition" dependency mediation rule.-->
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-reload4j</artifactId>
                </exclusion>
                <!--Initiated by Ceki Gülcü, the original author of Apache log4j 1.x, the reload4j
                project is a fork of Apache log4j version 1.2.17 with the goal of fixing pressing
                security issues. It is intended as a drop-in replacement for log4j version 1.2.17.
                By drop-in, we mean the replacement of log4j.jar with reload4j.jar in your build
                with no source code changes in .java files being necessary.

                The reload4j project offers a clear and easy migration path for the thousands
                of user who have an urgent need to fix vulnerabilities in log4j 1.2.17.-->
                <exclusion>
                    <groupId>ch.qos.reload4j</groupId>
                    <artifactId>reload4j</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--slf4j-log4j12是log4j的1.X版本,log4j-slf4j-impl是log4j的2.X版本-->
        <!--这个依赖需要放在桥接器依赖之前,不然会报错-->
        <!--The Apache Log4j SLF4J API binding to Log4j 2 Core-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>

        <!-- 面对多种日志框架同时存在的问题,Ceki 的 Slf4j 给出了解决方案,就是下文
        的桥接( Bridging legacy),简单来说就是劫持所有第三方日志输出并重定
        向至 SLF4j,最终实现统一日志上层API(编码)与下层实现(输出日志位置、格式统一)-->
        <!--JCL 1.2 implemented over SLF4J-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>1.7.36</version>
        </dependency>
        <!--JUL to SLF4J bridge-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jul-to-slf4j</artifactId>
            <version>1.7.36</version>
        </dependency>
    </dependencies>
    
</project>
WordCountMapper.java
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * 以WordCount案例 为例:
 * 自定义的Mapper类 需要继承Hadoop提供的Mapper 并且根据具体业务指定输入数据和输出数据的数据类型
 *
 * 输入数据的类型
 * KEYIN,  读取文件的偏移量  数字(LongWritable)
 * VALUEIN, 读取文件的一行数据  文本(Text)
 *
 * 输出数据的类型
 * KEYOUT,  输出数据的key的类型 就是一个单词(Text)
 * VALUEOUT 输出数据value的类型 给单词的标记 1 数字(IntWritable)
 *
 */
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

    @Override
    protected void setup(Context context) throws IOException, InterruptedException {
        System.out.println("setup方法执行了");
    }

    // 复用两个变量
    private Text outk = new Text();
    private IntWritable outv = new IntWritable(1);

    /**
     * Map阶段的核心业务处理方法,每输入一行数据会调用一次map方法
     * @param key 输入数据的key
     * @param value 输入数据的value
     * @param context 上下文对象
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        // 获取当前输入的数据(复用java实现的String处理方法)
        String line = value.toString();
        // 切割数据
        String[] datas = line.split(" ");
        // 遍历集合 封装 输出数据的key和value
        for (String data : datas) {
            outk.set(data);
            context.write(outk, outv);
        }
        System.out.println("map方法执行了");
    }

    @Override
    protected void cleanup(Context context) throws IOException, InterruptedException {
        System.out.println("cleanup执行了");
    }
}
WordCountReducer.java
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

/**
 * 以WordCount案例 为例:
 * 自定义的Reducer类 需要继承Hadoop提供的Reducer 并且根据具体业务指定输入数据和输出数据的数据类型
 *
 * 输入数据的类型
 * KEYIN,  Map端输出的key的数据类型
 * VALUEIN, Map端输出的value的数据类型
 *
 * 输出数据的类型
 * KEYOUT,  输出数据的key的类型 就是一个单词(Text)
 * VALUEOUT 输出数据value的类型 单词出现的总次数(IntWritable)
 */
public class WordCountReducer extends Reducer<Text, IntWritable,Text, IntWritable> {

    // 复用两个变量
    private Text outk = new Text();
    private IntWritable outv = new IntWritable();

    /**
     *  Reduce阶段的核心业务处理方法, 一组相同key的values会调用一次reduce方法
     * @param key
     * @param values
     * @param context
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int total = 0;
        // 遍历values
        for (IntWritable value : values) {
            // 对value累加进行累加 输出结果
            total+=value.get();
        }
        // 封装key和value
        outk.set(key);
        outv.set(total);
        context.write(outk, outv);
    }
}
WordCountDriver.java
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;


import java.io.IOException;

/**
 * MR程序的驱动类:主要用于提交MR任务
 */
public class WordCountDriver {

    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        //BasicConfigurator.configure();

        // 声明配置对象
        Configuration conf = new Configuration();
        // 声明Job对象
        Job job = Job.getInstance(conf);
        // 指定ReduceTask的数量
        job.setNumReduceTasks(3);
        // 指定当前Job的驱动类(通过反射获得对象的实例)
        job.setJarByClass(WordCountDriver.class);
        // 指定当前Job的 Mapper和Reducer
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);
        // 指定Map段输出数据的key的类型和输出数据value的类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);
        // 指定最终输出结果的key的类型和value的类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 指定输入数据的目录 和 输出数据的目录
        FileInputFormat.setInputPaths(job, new Path("src/main/data/wcinput"));
        FileOutputFormat.setOutputPath(job, new Path("src/main/data/wcoutput"));
        //FileInputFormat.setInputPaths(job, new Path(args[0]));
        //FileOutputFormat.setOutputPath(job, new Path(args[1]));
        // 提交Job
        job.waitForCompletion(true);
    }
}
参考日志:

Log4j2日志记录框架的使用教程与简单实例
Java日志体系居然这么复杂?——架构篇
SLF4J 用户使用手册
【JAVA】Log4j2日志详解
slf4j 和 log4j、log4j2、logback 依赖 jar 引用关系
Log4j2与Slf4j的最佳实践

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值