精华内容
下载资源
问答
  • log的使用

    2020-03-26 16:27:10
    设想如果是自己实现一个日志系统,只能用io来写改怎么设计呢? 需要满足 1.日志可以打印到任何位置如:控制台,文件,邮件等 2.日志可以用任何格式:如文本、html等 3.日志可以区别产生来源,如不同class、...

    项目总是出现意想不到的情况需要单独处理日志,记录一下log的理解

    log的设计

    设想如果是自己实现一个日志系统,只能用io来写改怎么设计呢?

    需要满足

    1.日志可以打印到任何位置如:控制台,文件,邮件等

    2.日志可以用任何格式:如文本、html等

    3.日志可以区别产生的来源,如不同class、不同package、产生的日志有所区别(如文件不同)

    这三点也是相互不影响的,换句话说是相互正交的。

    Appender负责日志的打印位置,Formatter负责管理日志的格式、Logger负责日志的来源区分。

    log目前的实现

    log4j也是最早遵循这种分层出来的log框架,随后日志的框架也逐渐增加(如logback,java库的日志),慢慢就有了SLF4J定义一个日志标准,也可以说是适配不同的log框架,现在因为logback和SLF4J无论是适配还是实际效率都是最优的,一般来说处理日志使用他们即可。

    springboot+logback的使用

    springboot默认使用的就是springboot+logback,在项目资源目录resource下新建日志配置文件logback-spring.xml

    常用配置如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration scan="true">
        <!-- 按天输入至文件 -->
        <appender name="DayFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <append>true</append>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>equipment/%d{yyyy-MM-dd}.log</fileNamePattern>
            </rollingPolicy>
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <maxFileSize>100MB</maxFileSize>
            </triggeringPolicy>
            <encoder>
                <!--<pattern>%d %p (%file:%line\)- %m%n</pattern>-->
                <!--格式化输出:%d:表示日期    %thread:表示线程名     %-5level:级别从左显示5个字符宽度  %msg:日志消息    %n:是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
        <!-- 普通的输入至文件 -->
    
        <appender name="CommonFile" class="ch.qos.logback.core.FileAppender">
            <file>equipment/log.log</file>
            <append>true</append>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %msg%n</pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
    
        <!-- 控制台彩色输出 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <!--<pattern>%d %p (%file:%line\)- %m%n</pattern>-->
                <!--格式化输出:%d:表示日期    %thread:表示线程名     %-5level:级别从左显示5个字符宽度  %msg:日志消息    %n:是换行符-->
                <pattern>%green(%d{yyyy-MM-dd HH:mm:ss}) %white([%thread]) %highlight(%-5level) %boldMagenta(%logger) -
                    %cyan(%msg%n)
                </pattern>
                <charset>UTF-8</charset>
            </encoder>
        </appender>
    
        <!-- info以上日志的信息 控制台输出  -->
        <root level="Info">
            <appender-ref ref="STDOUT"/>
        </root>
        <!-- com.hhxk.equipment包下日志的信息 按天进行分文件输出  -->
        <logger name="com.hhxk.equipment" level="Info" additivity="false">
            <appender-ref ref="DayFile"/>
        </logger>
    
    </configuration>

     

     

    展开全文
  • 现在的项目越来越复杂,谁也不能保证自己的项目在运行过程中不出现错误,出现错误并不可怕,问题是要及时的排除错误,让项目更加健壮并继续运行。排除这些错误就需要获取错误信息,信息从哪里来呢,一个设计良好的...

    现在的项目越来越复杂,谁也不能保证自己的项目在运行过程中不出现错误,出现错误并不可怕,问题是要及时的排除错误,让项目更加健壮并继续运行。排除这些错误就需要获取错误信息,信息从哪里来呢,一个设计良好的项目,肯定记录了项目运行的日志。那么我们怎么样来设计这个日志类呢?
     


      对于不同的人可能设计出来的方案就不一样,至于孰好孰坏是仁者见仁智者见智。关公面前耍大刀,我也来卖弄下,出出洋相,下面的Log演化过程见证了我的程序员历程,来介绍我的希望大家指正。
     


      1. 入行初始感觉写代码很神奇,没有理解封装概念,哪里需要记录日志就在那里写个方法,最后发现很多时候就是复制黏贴,突然有一天项目经理说“现在我们的日志不打算记录在文本文件里面,要把它记录到数据库中”,听后我瞬时崩溃!没有办法一个个改呗,好是费劲还可能有漏网之鱼。

      

    1. Public void WriteLog()  
    2.   
    3.   {  
    4.   
    5.   //代码  
    6.   
    7.   }  

      2. 吃一堑长一智,这回我学乖了,意识到了其实记录日志的操作是一样的,于是干脆把它提去出来放在一个单独的类中,然后在项目中使用。         

    1. Public class Logger  
    2.   
    3. {  
    4.   
    5. Public void WriteLog()  
    6.   
    7. {  
    8.   
    9. //代码  
    10.   
    11. }  
    12.   
    13. }  

      每一处调用方式如下:   

    1. Logger log=new Logger();  
    2.   
    3.   log. WriteLog();  

      提取出来后,自我感觉良好,小小满足了下。

     

      3. 得瑟、真的不能得瑟啊、一得瑟就有问题,新的要求来了。有时候真的觉得经理的要求是不可理喻 "这个日志类你不能写的太死了,要保证它的可扩展性,我现在可能是要记录在数据库,可能在下一次部署的时候要记录在文本文件或者其它文件格式里面了"。我现在又用不着,至于吗?能解决现在的问题不就可以了吗,要求真多。 没有办法啊,继续冥思苦想……翻阅资料,最后还真的找到了一条设计原则:依赖倒置,具体依赖抽象,抽象不依赖具体。

      前面的设计就是每个使用到Logger类的类都是依赖它,这明显不符合依赖倒置原则吗?那为了解除这种耦合、满足依赖倒置原则,只能让类依赖抽象。.net中这种抽象的选择之一就是接口。那就定义一个日志接口,然后日志类实现接口。   

    1. Interface  ILogger  
    2.   
    3.   {  
    4.   
    5.   void WriteLog();  
    6.   
    7.   }  
    8.   
    9.   Public class DBLogger: ILogger  
    10.   
    11.   {  
    12.   
    13.   Public void WriteLog()  
    14.   
    15.   {  
    16.   
    17.   //代码  
    18.   
    19.   }  
    20.   
    21.   }  

      每一处调用方式如下:      

    1. ILogger log=new DBLogger();  
    2.   
    3.   log. WriteLog();  
          咋一看好像还像那么回事,再仔细一看,还是不行,每一处用到的地方还是要使用ILogger log=new DBLogger(),如果现在要改成File Logger记录日志、还是要在每一个使用的地方更改。烦啊、怎么这么多问题呢!无奈长叹一声。

      不过有问题就有解决的办法,既然每次要切换记录日志方式时都要改变实例化日志类的方式、那么就把这个实例化对象的代码 ILogger log=new DBLogger();封装起来,这叫封装变化点,仔细再一想着不就是简单工厂模式吗? 那就动手吧。    

    1. Public class LoggerFactoy  
    2.   
    3.   {  
    4.   
    5.   Public static ILogger GetLogger(string logType)  
    6.   
    7.   {  
    8.   
    9.   //根据logType来实力化具体的类  
    10.   
    11.   }  
    12.   
    13.   }  

      每一处调用方式如下:      

    1. ILogger logLoggerFactoy. GetLogger(“DBLog”);  
    2.   
    3.  log. WriteLog();  

      这下看上去比原来的要好点了。不过还是要传个参数、每次切换日志类型的时候还是要改代码。那能不能不传参数了,那就用配置文件配置使用的日志类型吧:

      

    1. Public class LoggerFactoy  
    2.   
    3.   {  
    4.   
    5.   Public static ILogger GetLogger()  
    6.   
    7.   {  
    8.   
    9.   string logType=ConfigurationManager.……  
    10.   
    11.   //根据logType来实力化具体的类 通过反射来实例化日志对象  
    12.   
    13.   }  
    14.   
    15.   }  

      每一处调用方式如下:   

    1. ILogger logLoggerFactoy. GetLogger();  
    2.   
    3.   log. WriteLog();  

      好到目前为止Logger类基本上设计完成了,下面是一个简单的设计图   1.jpg

    转载于:https://www.cnblogs.com/bin1991/p/3636651.html

    展开全文
  • 源码 如果是你设计一个框架,你日志系统会怎么设计?是自己实现还是依赖日志门面 slf4j commons-logging jul 或者是直接依赖实现 log4j log4j2 logback stdout 或者是干脆不输出日志。其实还是挺令人头疼一件事...

    源码

    如果是你设计一个框架,你的日志系统会怎么设计?是自己实现还是依赖日志门面 slf4j commons-logging jul 或者是直接依赖实现 log4j log4j2 logback stdout 或者是干脆不输出日志。其实还是挺令人头疼的一件事,接下来,我们来看看 Mybatis 这款优秀的 ORM 框架是如何做的。

    5c72dae95f7f52670252a9f84d6c1514.png
    Mybatis-Log-Uml

    通过上图可以看到,Log 一共有 7 大实现类:

    1. StdOutImpl

    2. Log4jImpl

    3. NoLoggingImpl

    4. Jdk14LoggingImpl

    5. JakartaCommonsLoggingImpl

    6. Slf4jImpl

    • Slf4jLocationAwareLoggerImpl

    • Slf4jLoggerImpl

    Log4j2Impl

    • Log4j2AbstractLoggerImpl

    • Log4j2LoggerImpl

    接下来我们就逐个分析每一种实现

    StdOutImpl

    从名字来看,很容易能够看出这个是通过 JDK  原生的 API System.out.println 来实现的,接下来我们看看源码。

    package org.apache.ibatis.logging.stdout;

    import org.apache.ibatis.logging.Log;

    /**
     * @author Clinton Begin
     */

    public class StdOutImpl implements Log {

      public StdOutImpl(String clazz) {
        // Do Nothing
      }

      @Override
      public boolean isDebugEnabled() {
        return true;
      }

      @Override
      public boolean isTraceEnabled() {
        return true;
      }

      @Override
      public void error(String s, Throwable e) {
        System.err.println(s);
        e.printStackTrace(System.err);
      }

      @Override
      public void error(String s) {
        System.err.println(s);
      }

      //省略下面部分代码
    }

    这种实现方式较为简单,就是通过标准的输出流输出到指定的目标位置,默认情况下应该是输出到控制台,当然也可以输出到文件,相信很少有人用这种方式。

    Log4jImpl

    Log4jImpl 是 Apache 的一个开源项目,它可以通过配置文件灵活的配置日志的输出格式和目的地,也是比较主流的实现方式。同时分为 1.x 和 2.x 版本,但是从 2015年8月5日开始,官方建议使用 2.x  传送门:https://logging.apache.org/log4j/1.2/

    On August 5, 2015 the Logging Services Project Management Committee announced that Log4j 1.x had reached end of life. For complete text of the announcement please see the Apache Blog. Users of Log4j 1 are recommended to upgrade to Apache Log4j 2

    我们先看 1.x 的实现。

    package org.apache.ibatis.logging.log4j;

    import org.apache.ibatis.logging.Log;
    import org.apache.log4j.Level;
    import org.apache.log4j.Logger;

    /**
     * @author Eduardo Macarron
     */

    public class Log4jImpl implements Log {

      private static final String FQCN = Log4jImpl.class.getName();

      private final Logger log;

      public Log4jImpl(String clazz) {
        log = Logger.getLogger(clazz);
      }

      @Override
      public boolean isDebugEnabled() {
        return log.isDebugEnabled();
      }

      @Override
      public boolean isTraceEnabled() {
        return log.isTraceEnabled();
      }

      @Override
      public void error(String s, Throwable e) {
        log.log(FQCN, Level.ERROR, s, e);
      }

      //省略下面部分代码
    }

    由于 Mybatis 本身依赖了 Log4j 所以可以直接使用其 API Logger.getLogger  初始化 Logger 对象。

    NoLoggingImpl

    顾名思义,这种实现方式就是不使用任何日志输出。

    package org.apache.ibatis.logging.nologging;

    import org.apache.ibatis.logging.Log;

    /**
     * @author Clinton Begin
     */

    public class NoLoggingImpl implements Log {

      public NoLoggingImpl(String clazz) {
        // Do Nothing
      }

      @Override
      public boolean isDebugEnabled() {
        return false;
      }

      @Override
      public boolean isTraceEnabled() {
        return false;
      }

      @Override
      public void error(String s, Throwable e) {
        // Do Nothing
      }
      //省略下面部分代码
    }

    可以看到,方法里面都是空实现。没有任何日志输出操作。

    Jdk14LoggingImpl

    这种实现方式也是使用了 JDK 原生的 API 还有另外一种叫法 JUL 它使用了 java.util.logging 包下面的 Logger 对象输出日志。

    package org.apache.ibatis.logging.jdk14;

    import java.util.logging.Level;
    import java.util.logging.Logger;

    import org.apache.ibatis.logging.Log;

    /**
     * @author Clinton Begin
     */

    public class Jdk14LoggingImpl implements Log {

      private final Logger log;

      public Jdk14LoggingImpl(String clazz) {
        log = Logger.getLogger(clazz);
      }

      @Override
      public boolean isDebugEnabled() {
        return log.isLoggable(Level.FINE);
      }

      @Override
      public boolean isTraceEnabled() {
        return log.isLoggable(Level.FINER);
      }

      @Override
      public void error(String s, Throwable e) {
        log.log(Level.SEVERE, s, e);
      }

      @Override
      public void error(String s) {
        log.log(Level.SEVERE, s);
      }
      //省略下面部分代码

    }

    可以看到 Jdk14LoggingImpl 只是对 JUL 做了一层封装,使用 JUL 的最大好处估计也就是因为他是 JDK 内置的 API 我们不用引入任何第三方依赖库,就能直接使用。

    虽然它也提供了配置文件的方式来配置日志,但是目前应该只有很少一部人使用它吧,大多数人还是愿意使用 Log4j Slf4j 等日志,究其历史原因,JUL 是 jdk1.4 中引入的记录日志的。

    但是在此之前就有记录日志的 API 当然这可能只是一部分原因,还有就是配置方式可能没有其他日志那么灵活。传送门:https://stackoverflow.com/questions/11359187/why-not-use-java-util-logging 有讨论 JUl 和其他日志的对比,其中有 Log4j Slf4j Logback 项目创始人的回答。

    JakartaCommonsLoggingImpl

    这种实现方式是对 commons-logging 的封装,commons-logging 也叫 JCL 它是一种日志的门面,所谓门面就是它提供了统一的接口,底层适配了各个日志接口的实现,然后根据规则调用各个日志实现,返回 Log 对象。

    package org.apache.ibatis.logging.commons;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;

    /**
     * @author Clinton Begin
     */

    public class JakartaCommonsLoggingImpl implements org.apache.ibatis.logging.Log {

      private final Log log;

      public JakartaCommonsLoggingImpl(String clazz) {
        log = LogFactory.getLog(clazz);
      }

      @Override
      public boolean isDebugEnabled() {
        return log.isDebugEnabled();
      }

      @Override
      public boolean isTraceEnabled() {
        return log.isTraceEnabled();
      }

      @Override
      public void error(String s, Throwable e) {
        log.error(s, e);
      }

      //省略下面部分代码

    }

    commons-logging 的适配规则是:

    1. org.apache.commons.logging.impl.Log4JLogger(Log4j)

    2. org.apache.commons.logging.impl.Jdk14Logger(Jul,Jdk1.4之后)

    3. org.apache.commons.logging.impl.Jdk13LumberjackLogger(Jul,Jdk1.4之前)

    4. org.apache.commons.logging.impl.SimpleLog(System.err.println)

    我们可以看到虽然 commons-logging 是一种日志门面,但是当所有规则都不能匹配的情况下,它会自己实现一个简单的日志 SimpleLog 但是从匹配规则来看,如果项目依赖中没有 Log4j 那么应该匹配 Jul 这个肯定是存在的,毕竟它是在 JDK 自带的包中。

    Slf4jImpl

    Slf4j 也是一种日志门面,它提供了和很多日志关联的适配器。

    4d4098a1ce9617ef72520851d507a576.png
    concrete-bindings

    通过这幅图我们可以看到,它可以和很多第三方依赖配合完成日志的输出。

    package org.apache.ibatis.logging.slf4j;

    import org.apache.ibatis.logging.Log;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.slf4j.Marker;
    import org.slf4j.spi.LocationAwareLogger;

    /**
     * @author Clinton Begin
     * @author Eduardo Macarron
     */

    public class Slf4jImpl implements Log {

      private Log log;

      public Slf4jImpl(String clazz) {
        Logger logger = LoggerFactory.getLogger(clazz);

        if (logger instanceof LocationAwareLogger) {
          try {
            // check for slf4j >= 1.6 method signature
            logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
            log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
            return;
          } catch (SecurityException e) {
            // fail-back to Slf4jLoggerImpl
          } catch (NoSuchMethodException e) {
            // fail-back to Slf4jLoggerImpl
          }
        }

        // Logger is not LocationAwareLogger or slf4j version 
        log = new Slf4jLoggerImpl(logger);
      }//省略下面部分代码
    }

    Slf4jImpl 的构造方法里面有一个判断条件,源码里面的注释说的也清楚,也就是说如果得到的 Logger 对象是

    LocationAwareLogger 接口的实例,则说明当前 Slf4j 的版本是大于等于 1.6 的,下面的代码会通过反射检查是否包含指定的方法,检查通过则实例化 Slf4jLocationAwareLoggerImpl 对象

    package org.apache.ibatis.logging.slf4j;

    import org.apache.ibatis.logging.Log;
    import org.apache.ibatis.logging.LogFactory;
    import org.slf4j.Marker;
    import org.slf4j.MarkerFactory;
    import org.slf4j.spi.LocationAwareLogger;

    /**
     * @author Eduardo Macarron
     */

    class Slf4jLocationAwareLoggerImpl implements Log {

      private static final Marker MARKER = MarkerFactory.getMarker(LogFactory.MARKER);

      private static final String FQCN = Slf4jImpl.class.getName();

      private final LocationAwareLogger logger;

      Slf4jLocationAwareLoggerImpl(LocationAwareLogger logger) {
        this.logger = logger;
      }

      @Override
      public boolean isDebugEnabled() {
        return logger.isDebugEnabled();
      }

      @Override
      public boolean isTraceEnabled() {
        return logger.isTraceEnabled();
      }

      @Override
      public void error(String s, Throwable e) {
        logger.log(MARKER, FQCN, LocationAwareLogger.ERROR_INT, s, null, e);
      }
      //省略下面部分代码
    }

    如果失败则说明当前 Slf4j 的版本小于 1.6 则实例化 Slf4jLoggerImpl 对象。

    package org.apache.ibatis.logging.slf4j;

    import org.apache.ibatis.logging.Log;
    import org.slf4j.Logger;

    /**
     * @author Eduardo Macarron
     */

    class Slf4jLoggerImpl implements Log {

      private final Logger log;

      public Slf4jLoggerImpl(Logger logger) {
        log = logger;
      }

      @Override
      public boolean isDebugEnabled() {
        return log.isDebugEnabled();
      }

      @Override
      public boolean isTraceEnabled() {
        return log.isTraceEnabled();
      }

      @Override
      public void error(String s, Throwable e) {
        log.error(s, e);
      }
      //省略下面部分代码
    }

    想验证也很简单,Slf4j 初始化时会查找 StaticLoggerBinder 类,如果发现多个,控制台就会报错,告诉你找到多个绑定。

    package org.slf4j.impl;

    import org.apache.log4j.Level;
    import org.slf4j.ILoggerFactory;
    import org.slf4j.LoggerFactory;
    import org.slf4j.helpers.Util;
    import org.slf4j.spi.LoggerFactoryBinder;

    public class StaticLoggerBinder implements LoggerFactoryBinder {

        private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();

        public static final StaticLoggerBinder getSingleton() {
            return SINGLETON;
        }

        public static String REQUESTED_API_VERSION = "1.6.99"// !final

        private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();

        private final ILoggerFactory loggerFactory;

        private StaticLoggerBinder() {
            loggerFactory = new Log4jLoggerFactory();
            try {
                @SuppressWarnings("unused")
                Level level = Level.TRACE;
            } catch (NoSuchFieldError nsfe) {
                Util.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
            }
        }
        //忽略下面部分代码
    }

    Class path contains multiple SLF4J bindings 这个报错大家可能都不陌生吧。如果你引入的是 slf4j-log4j 在 StaticLoggerBinder 的构造方法中会初始化 Log4jLoggerFactory 对象,该对象的 getLogger 方法中会创建 Logger 对象。

    public Logger getLogger(String name) {
        Logger slf4jLogger = loggerMap.get(name);
        if (slf4jLogger != null) {
            return slf4jLogger;
        } else {
            org.apache.log4j.Logger log4jLogger;
            if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
                log4jLogger = LogManager.getRootLogger();
            else
                log4jLogger = LogManager.getLogger(name);

            Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        }
    }

    打开 Log4jLoggerAdapter 你会发现它实现了 LocationAwareLogger 接口。

    package org.slf4j.impl;

    import static org.slf4j.event.EventConstants.NA_SUBST;

    import java.io.Serializable;

    import org.apache.log4j.Level;
    import org.apache.log4j.spi.LocationInfo;
    import org.apache.log4j.spi.ThrowableInformation;
    import org.slf4j.Logger;
    import org.slf4j.Marker;
    import org.slf4j.event.LoggingEvent;
    import org.slf4j.helpers.FormattingTuple;
    import org.slf4j.helpers.MarkerIgnoringBase;
    import org.slf4j.helpers.MessageFormatter;
    import org.slf4j.spi.LocationAwareLogger;
    public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements LocationAwareLoggerSerializable {
        //省略代码
    }

    Log4j2Impl

    前面我们说过官方建议使用 Log4j2 下面我们来看看 Mybatis 是怎么适配的。

    package org.apache.ibatis.logging.log4j2;

    import org.apache.ibatis.logging.Log;
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.spi.AbstractLogger;

    /**
     * @author Eduardo Macarron
     */

    public class Log4j2Impl implements Log {

      private final Log log;

      public Log4j2Impl(String clazz) {
        Logger logger = LogManager.getLogger(clazz);

        if (logger instanceof AbstractLogger) {
          log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
        } else {
          log = new Log4j2LoggerImpl(logger);
        }
      }
      //省略下面部分代码
    }

    这个类和 Slf4j 一样构造方法里面都有条件判断,初始化不同的 Logger 实例。在 LogManager 的静态代码块里面会初始化 LoggerContextFactory 对象,如果你要使用 Log4j2 需要添加以下依赖。

    <dependency>
        <groupId>org.apache.logging.log4jgroupId>
        <artifactId>log4j-apiartifactId>
        <version>xxxversion>
    dependency>
    <dependency>
        <groupId>org.apache.logging.log4jgroupId>
        <artifactId>log4j-coreartifactId>
        <version>xxxversion>
    dependency>

    那为什么要判断获取到的 Logger 对象呢,如果你只引入了 log4j-api 而没有引入 log4j-core 依赖,那还能记录日志吗?答案是当然可以,我们来看一下静态代码块。

    e53d4d36a98934e984f2bb35558f797f.png
    LogManager-static-1
    d67b5ee82760538e9e70d95604e602c9.png
    LogManager-static-2

    由于静态代码块代码太长,粘贴代码的话可能看的不太清楚,所以这里用图片代替。

    正如我代码里面的注释所描述,就算只引入了 log4j-api 也是可以打印日志的,只不过这个时候只能打印 error 级别的日志。如果使用默认的 SimpleLoggerContextFactory 的话接下来我们获取到的 LoggerContext 对象就是 SimpleLoggerContext 当调用 getLogger 的时候返回的就是 SimpleLogger

    那么重点来了,在 Log4j2Impl 里面的判断是 logger instanceof AbstractLogger 那么去查看 SimpleLogger 类就会看到它其实是继承了 AbstractLogger 对象。

    我们根据构造方法再来看一下

    if (logger instanceof AbstractLogger) {
        log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
    else {
        log = new Log4j2LoggerImpl(logger);
    }

    创建 Log4j2AbstractLoggerImpl 对象。

    package org.apache.ibatis.logging.log4j2;

    import org.apache.ibatis.logging.Log;
    import org.apache.ibatis.logging.LogFactory;
    import org.apache.logging.log4j.Level;
    import org.apache.logging.log4j.Marker;
    import org.apache.logging.log4j.MarkerManager;
    import org.apache.logging.log4j.message.SimpleMessage;
    import org.apache.logging.log4j.spi.AbstractLogger;
    import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;

    /**
     * @author Eduardo Macarron
     */

    public class Log4j2AbstractLoggerImpl implements Log {

      private static final Marker MARKER = MarkerManager.getMarker(LogFactory.MARKER);

      private static final String FQCN = Log4j2Impl.class.getName();

      private final ExtendedLoggerWrapper log;

      public Log4j2AbstractLoggerImpl(AbstractLogger abstractLogger) {
        log = new ExtendedLoggerWrapper(abstractLogger, abstractLogger.getName(), abstractLogger.getMessageFactory());
      }
      //省略下面部分代码
    }

    ExtendedLoggerWrapper 其实是对 AbstractLogger 又做了一次包装。底层还是调用 SimpleLogger 对象的方法。

    那我们再来看一下,如果添加了 log4j-core 以后会有什么不同,前面代码里面说了,如果加了这个依赖,就会找到 log4j-provider.properties 这个文件,我们看下文件内容。

    LoggerContextFactory = org.apache.logging.log4j.core.impl.Log4jContextFactory
    Log4jAPIVersion = 2.1.0
    FactoryPriority= 10

    可以看到文件里面提供了 LoggerContextFactory 的实现,那么就会初始化 Log4jContextFactory 从而得到 LoggerContext 最后返回的是 Logger 对象。

    我们根据构造方法再来看一下

    if (logger instanceof AbstractLogger) {
        log = new Log4j2AbstractLoggerImpl((AbstractLogger) logger);
    else {
        log = new Log4j2LoggerImpl(logger);
    }

    创建 Log4j2LoggerImpl 对象,进行包装。

    package org.apache.ibatis.logging.log4j2;

    import org.apache.ibatis.logging.Log;
    import org.apache.ibatis.logging.LogFactory;
    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.Marker;
    import org.apache.logging.log4j.MarkerManager;

    /**
     * @author Eduardo Macarron
     */

    public class Log4j2LoggerImpl implements Log {

      private static final Marker MARKER = MarkerManager.getMarker(LogFactory.MARKER);

      private final Logger log;

      public Log4j2LoggerImpl(Logger logger) {
        log = logger;
      }
      //省略下面部分代码
    }

    看到这里,Mybatis 对日志的封装适配就结束了。接下来我们来看看 Mybatis 是怎么让这些日志生效的。

    分析

    Mybatis 通过 LogFactory 获取 Log 对象,具体怎么使用,日志的优先级是什么都在这个类里面。

    package org.apache.ibatis.logging;

    import java.lang.reflect.Constructor;

    /**
     * @author Clinton Begin
     * @author Eduardo Macarron
     */

    public final class LogFactory {

      /**
       * Marker to be used by logging implementations that support markers
       */

      public static final String MARKER = "MYBATIS";

      private static Constructor extends Log> logConstructor;

      static {
        tryImplementation(new Runnable() {
          @Override
          public void run() {
            useSlf4jLogging();
          }
        });
        tryImplementation(new Runnable() {
          @Override
          public void run() {
            useCommonsLogging();
          }
        });
        tryImplementation(new Runnable() {
          @Override
          public void run() {
            useLog4J2Logging();
          }
        });
        tryImplementation(new Runnable() {
          @Override
          public void run() {
            useLog4JLogging();
          }
        });
        tryImplementation(new Runnable() {
          @Override
          public void run() {
            useJdkLogging();
          }
        });
        tryImplementation(new Runnable() {
          @Override
          public void run() {
            useNoLogging();
          }
        });
      }
      //省略下面部分代码
    }

    从静态代码块可以看出,日志的优先顺序。

    1. useSlf4jLogging

    2. useCommonsLogging

    3. useLog4J2Logging

    4. useLog4JLogging

    5. useJdkLogging

    6. useNoLogging

    tryImplementation 方法里面做了什么呢,我们来看一看。

    private static void tryImplementation(Runnable runnable) {
        if (logConstructor == null) {
            try {
                runnable.run();
            } catch (Throwable t) {
                // ignore
            }
        }
    }

    其实就是判断当前 logConstructor 属性是否为空,如果为空则执行线程的 run 方法,为什么是 run 而不是 start 呢,先卖个关子,我们稍后再说。

    public static synchronized void useSlf4jLogging() {
        setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
    }

    每个 userXXX 方法又调用了一个 setImplementation 方法,参数就是每个日志类的类对象。

    private static void setImplementation(Class extends Log> implClass) {
        try {
            Constructor extends Log> candidate = implClass.getConstructor(String.class);
            Log log = candidate.newInstance(LogFactory.class.getName());
            if (log.isDebugEnabled()) {
                log.debug("Logging initialized using '" + implClass + "' adapter.");
            }
            logConstructor = candidate;
        } catch (Throwable t) {
            throw new LogException("Error setting Log implementation.  Cause: " + t, t);
        }
    }

    setImplementation 方法就很简单了,就是根据参数类对象,获取指定的构造方法,然后创建实例,如果成功则赋值给全局属性 logConstructor 否则抛出异常。

    回头看 tryImplementation 方法里面有捕捉异常的代码,就是处理这个地方抛出的异常,为什么要这样呢,很简单,Mybatis 依次加载日志对象,如果失败则顺序加载,如果成功,则赋值全局属性,那么下面的代码就不会执行了。

    看到这里有的小伙伴可能会对 useXXX 方法和 tryImplementation 方法有疑问,一是 userXXX 方法加了 synchronized 线程同步关键字,但是从代码里面看好像没有线程同步的问题。二是 tryImplementation 方法的参数是 Runnable 它是一个接口,用来创建线程。但是代码里面却没有启动任何线程。

    首先第一个问题,我们可以看到 useXXX 方法是用 public 关键字修饰的,也就意味着它可以在任何地方,被任何代码所调用,之所以加了线程同步关键字,应该也是为了更加严谨吧,毕竟写代码的人不知道这个代码究竟会被人怎么使用。

    第二个问题,如果从我们自己的角度来编写的话,会怎么写这段代码,有顺序的进行判断,并忽略可能会发生的异常。如果之前没看过这个代码,我来写的话,可能就是直接调用多个 userXXX 方法,在每个方法里面处理异常。

    那作者当时为什么这样写呢,菜鸡和大神的想法肯定是不一样的。所以我就去 Mybatis 的 Github 仓库里面问了这个问题,为什么要这样处理。大神给了回复。传送门:https://github.com/mybatis/mybatis-3/pull/2052

    In some ways,tryImplementationparameter is an action function without parameters or results.The JDK only provides an action function without parameters or results, that is Runadble. In short,Just for convenience!

    用我蹩脚的英文水平翻译了一下大致意思就是:tryImplementation 方法的参数是一个动作函数它不需要参数和结果,为了优雅的执行 userXXX 需要使用这样一个函数式的参数,而 JDK 没有提供别的方式,只有使用 Runnable 可以达到这样的效果,所以就这样用了,只是为了更方便而已。

    那我在想如果是 Jdk1.8 其实我们就可以使用 lambada 来完成了。就不需要 Runnable 接口了。

    到这里 Mybatis 对日志的处理和适配就分析完了,看了人家的源码才知道写代码的艺术。

    一入源码深似海,对吗?


    1a62464fcdef9569e64c1d987a531bf5.png

    968bf7ca4e54ab748720bf38c2dca863.gif
    展开全文
  • 美好的周末说过去一下子就过去了呢~今天又迎来了更加美好的周一哇,哈哈哈。好啦废话不多说,还是来看看今天的教程方案吧...不知道也没关系,小编今天来教大家怎么制作圆形文字logo,分分钟制作出属于自己的文字log...

    美好的周末说过去一下子就过去了呢~今天又迎来了更加美好的周一哇,哈哈哈。好啦废话不多说,还是来看看今天的教程方案吧!圆形文字logo是什么意思呢?不知道大家有没有看到过那种圆形的标志,比如小编大学时候,学校的校徽就是圆形的图案,然后文字也是绕着圆圈的,圆形环绕效果。不知道小编这样子说大家知不知道是什么意思呢?不知道也没关系,小编今天来教大家怎么制作圆形文字logo,分分钟制作出属于自己的文字logo图案。

    首先,我们需要先通过上方的链接下载我们所要用到的软件,软件比较大,下载需要时间,大家耐心等待哦。在等待软件下载完成的过程中,来看看小编的制作效果吧!小编制作的logo当然与咱们狸窝家园有关啦,哈哈。

    20ab082f41ae068920566e960b9c6d8a.png

    软件下载完成之后双击安装包进行静默安装即可。安装完成打开软件,首先我们需要来创建一个项目,点击左上方的“文件”然后点击“新建”,打开新建页面,然后输入项目名称,设置项目宽高,以及选择背景内容。

    801f4959e29ed624945e564abf429b77.png

    点击“确定”按钮创建项目,在左侧的工具栏中,点击矩形工具并鼠标右键选择“椭圆工具”,然后在左上方,将“形状”改为“路径”,接着按住键盘的shift键,同时在画布中移动鼠标,画一个正圆路径,如下图所示。

    349a9051fa8b8d597eae3cdae9d1a409.png

    点击左侧工具栏的文字工具,并设置文字的字体,字号以及颜色等。用鼠标点击圆形路径的边,注意一定要点中才得,不然文字不会围绕路径的哦。这里我就说个技巧,当我们鼠标出现一个S形虚线的时候点击就可以了。

    7426a2d74eef16e10ae0eb8ad19c9e82.png

    输入文字内容之后点击右上方的“√”保存文字。如果想要旋转文字的话,我们可以按住键盘的Ctrl键,同时鼠标移到出现的正方形的角旁边移动,就可以旋转文字哦。同时我们可以将字符面板打开,来设置文字的间距,如下图。

    f52cee932c83c642338ff6390c41b63b.png

    这里小编给大家介绍一下如何添加圆形图片来完善我们的logo。点击左上方的“文件”按钮选择“置入”或者是直接拖拽的方式将我们想要的logo图案添加进来。图片添加进来之后点击右上方的“√”或者双击图片确认置入。

    b5aadfe74fc85d91538e5450c83f49d8.png

    在左侧的工具栏中选择椭圆选框工具。然后在logo图案中,按住键盘shift键,同时拖动鼠标画一个正圆选框,接着可以通过键盘上的上下左右按键来轻移圆形选框,使圆形选框在图案的正中间并且不能大于图片,如下图所示。

    14d585a5413a88ffe64cdf9a4d06cde1.png

    接着,Ctrl+J复制图层,将我们圆形选框中的内容复制到新图层中,然后将原图片素材的图层可见性关闭,点击“眼睛”即可关闭。然后选中刚刚复制的图层,Ctrl+T进入变形工具,对图案进行放大缩小以及调整显示位置等。

    c5fa17e76198c01bdda69d42042f8b2c.png

    可以来为文字添加一点样式效果,点击文字图层,然后点击右下方的“fx”,选中“混合”选项。在图层样式页面中,将“斜面和浮雕”效果还有“投影”选中,之后再设置斜面和浮雕还有投影的具体数值,更改角度或颜色等。

    69a377f0f7a7c9f8b5d00f01304cf913.png

    这里小编就不详细介绍那些数值啦,大家可以根据自己的实际情况来进行设置,一边对照着画布中的效果一边更改数值就可以咯。接着就可以来保存图片了,通过点击文件选择存储或存储为,或者是快捷键Ctrl+S进行保存。

    fad68fd28e73f142adf3f2828f2b3664.png

    可以选择图片的保存格式,基本上所有的常见格式都有的哦。好啦,我们今天的制作圆形文字logo的教程方案就到这里啦。怎么样呢?是不是很简单,只需要画出一个圆形路径,就可以制作出属于自己的圆形文字logo啦,快来试试吧!

    笑话段子:

    我爸的朋友李大爷,被女儿接去洛杉矶住,大爷痴迷太极,每天早晚都会去广场练太极拳太极剑。有一天李大爷正要去广场锻炼呢,一个小伙抢了一个阿姨的包之后跑到李大爷附近,李大爷从布包里就抽出来一把剑!你想象一下一个白头发白胡子的老头穿着太极服举着一把剑,那叫一个仙风道骨,小伙估计是被镇住了,放下抢的包,换个方向飞快地跑了……

    相关文章:

    资源推荐:

    (领导说了,分享本文一次小编工资涨0.5元)

    为了感谢你帮小编分享,推荐你一个学习小视频制作的方法 >>

    展开全文
  • ### 设计一个可以在外部调用方法,方法中有回调函数,且回调函数能获取到需要值 现在有一个编辑器插件(自已封装),编辑器上有一个保存按钮,当点击此按钮,会执行一个方法,这个方法中有回调函数,回调...
  • VS操作字符串总是自己引发中断...以下是程序代码,每当运行到有关logfile字符串有关操作就会自己中断,求助各位! int ergodicFlashData,textCount; //logfile=_T(""); CString logtemp; for (ergodicFlashData...
  • 发现配置很繁琐 决定自己设计一个 肯定有不少不足之处 分为以下几个步骤 1.建立日志数据表 都用一个表来存放,那么字段就要多设置一个 用来区分不同日志类型 具体怎么设置 也很简单 字段很简单 us
  • 从而形成它自己的生态系统。 <p>redux 的核心很简洁。这篇文章将专注于解读 redux 核心的设计思路,以 <a href="https://github.com/Lucifier129/Isomorphism-react-todomvc">Isomorphism-react-todomvc</a> ...
  • 发现配置很繁琐 决定自己设计一个 肯定有不少不足之处 分为以下几个步骤 1.建立日志数据表 都用一个表来存放,那么字段就要多设置一个 用来区分不同日志类型 具体怎么设置 也很简单 字段很简单 usi
  • 设计了一个更适合我们自己业务场景架构 整体架构图 <p><img alt="整体架构图" src="https://img-blog.csdnimg.cn/img_convert/a491d06afa28509a6f232b156b5b2072.png" /></p> 目录结构 <pre><code> . ├── ...
  • 更新自己的技术储备非常有帮助。而高效率的找到好的开源项目,定位到热点技术话题的项目,对我们探索学习非常关键。 <h3>git:logs 它提供了 Trending 流行功能,可以看到基于日期的新晋流行和热点...
  • Shiro @RequiresPermissions是如何运转

    万次阅读 多人点赞 2019-01-29 17:04:46
    废话不多说,框架是公司大佬设计的: 在查看日志方法上面加了RequiresPermissions。那么只有当用户拥有这个sys:log:content字符串时才能访问此方法。 那怎么知道用户拥有这个字符串呢????? 自己必须定义一个...
  • console.log("上报数据参数:", JSON.stringify(data)); http.put(httpReq.url_Join(rank_host, rank_score) , data, callback); } /** * 从 Matchvs Rank System 获取用户当前排行数据 * @param {object} ...
  • 废话不多说,框架是公司大佬设计的: 在查看日志方法上面加了RequiresPermissions。那么只有当用户拥有这个sys:log:content字符串时才能访问此方法。 那怎么知道用户拥有这个字符串呢????? 自己必须定义一...
  • 使用开源日志记录框架例如log4net,或者自己写一份单独实现利用IoC动态切入到你程序中?没有必要!看看用LINQ to SQL如何实现这个功能吧! 注意:这是我第一次分析设计,我只想通过这个系列来讨论学习
  • 如果是你设计一个框架,你日志系统会怎么设计?是自己实现还是依赖日志门面 slf4j commons-logging jul 或者是直接依赖实现 log4j log4j2 logback stdout 或者是干脆不输出日志。其实还是挺令人头疼一件事,接...
  • 对于测试帖,可以使用 Sandbox 机制:带有 Sandbox 标签的帖子将视为测试帖,不会显示在首页或是某领域内,只会展现在发帖者自己的帖子列表中。 人性化的回帖交互 实时呈现:回帖提交后其他浏览者可以不刷新...
  • 在ASP.Net课程一开始,不是直接教学员怎么拖ASP.Net控件进行快速开发,而是通过ashx模式开发原始动态网站,让学员明白“请求—处理—响应模型”、“Http协议、Http无状态”、“c#代码渲染生成浏览器端...
  • 每道题自己都竭尽全力去找尽可能多解法,且每个解法都实现出来。此前一直对树这个数据结构理解不是很彻底很到位,以及围绕树相关算法也感觉很飘忽很神秘,所以本周就萌生了全方位总结树这...
  • 但却是完全不同两个设计理念,怎么就能差那么远呢? ps:如果想说md5是基于文件内容话(实际上现在实现是这样),这问题也不难解决,不赘述 当任何操作都是基于...
  • 业务应用开发总结

    2020-01-01 16:34:24
    往往是快速排期、快速设计、快速开发,快速上线的一个状态,这就比较考验程序员的如何来适应这个快速的节奏和建立自己的开发流程和如何抛开工作之外的自我提升。 那怎么来应对这样一个快速的节奏呢? 理解团队技术...
  • excel使用

    2012-11-25 17:06:01
    在B2格输入“=ABS(LOG10(6+A2^3))”,B2格内马上得出了计算结果。这时,再选中B2格,让光标指向B2矩形右下角“■”,当光标变成"+"时按住光标沿B列拖动到适当位置即完成函数值计算。图7绘制曲线:点击...
  • asp.net知识库

    2015-06-18 08:45:45
    突破屏蔽限制,自己的网站使劲弹新IE窗口 对页面SCROLLING的CSS不能生效原因 .Net 中IE使用WinForm控件的使用心得。 动态加载用户控件的组件!(终结MasterPages技术) 在ASP.NET 1.1下实现模板化站点的新思路 在ASP...
  •  于是我通过自己的网站和邮件发出约稿邀请,得到了很多朋友的积极响应,最后汇集了本书的五位作者,而我希望这只是一个合作的开始,期待以后会有更多的作者将自己的经验总结分享出来。  这本书以诊断案例和性能...
  • <div><p>最近关于 Serverless 的讨论越来越多。看似与前端关系不大的 ...这些触发器也与平台自己的后端服务相关,比如阿里云的对象存储触发器,是基于阿里云的 OSS 产品的存取等事件触发的;...

空空如也

空空如也

1 2 3 4
收藏数 72
精华内容 28
关键字:

怎么设计自己的log