精华内容
下载资源
问答
  • 为python脚本设计自己log模块
    千次阅读
    2018-02-25 13:13:12

    这个方法是来自《python网络实战》(清华大学出版社)中推荐使用的log方法,原理是利用log功能抽象出一个专门的模块负责log记录,剥离之后模块可以提供更多的自定义空间,使用起来非常方便

    import logging
    import getpass
    import sys

    #定义Mylog类,管理log信息
    class MyLog( object ):
    def __init__ ( self ):
    self .user = getpass.getuser()
    self .logger = logging.getLogger( self .user)
    self .logger.setLevel(logging.DEBUG)

    #日志文件名
    self .logFile = sys.argv[ 0 ][ 0 :- 3 ] + '.log'
    self .formatter = logging.Formatter( '%(asctime)-12s %(levelname)- 8s %(name)-10s %(message)-12s \r\n ' )

    #日志显示到屏幕并输出到文档
    self .logHand = logging.FileHandler( self .logFile, encoding = 'utf-8' )
    self .logHand.setFormatter( self .formatter)
    self .logHand.setLevel(logging.DEBUG)

    self .logHandSt = logging.StreamHandler()
    self .logHandSt.setFormatter( self .formatter)
    self .logHandSt.setLevel(logging.DEBUG)

    self .logger.addHandler( self .logHand)
    self .logger.addHandler( self .logHandSt)

    #日志的5个级别对应5个函数
    def debug( self , msg):
    self .logger.debug(msg)

    def info( self , msg):
    self .logger.info(msg)

    def warn( self , msg):
    self .logger.warn(msg)

    def error( self , msg):
    self .logger.error(msg)

    def critical( self , msg):
    self .logger.critical(msg)

    #测试代码 __name__是访问当前函数名的方法,如果作为模块就不是main函数 下面的方法不会执行
    if __name__ == '__main__' :
    mylog = MyLog()
    mylog.debug( u"I'm 测试中文" )
    mylog.info( "I;m info" )
    mylog.warn( "warning" )
    mylog.error( "Error" )
    mylog.critical( "this is a critical" )


    在别的模块中引用时,需要import Mylog,然后调用里面的方法即可

    更多相关内容
  • 预写日志(WAL,Write-Ahead Log)将每次状态更新抽象为一个命令并追加写入一个日志中,这个日志只追加写入,也就是顺序写入,所以 IO 会很快。相比于更新存储的数据结构并且更新落盘这个随机 IO 操作,写入速度更快...

    原文地址:https://martinfowler.com/articles/patterns-of-distributed-systems/wal.html

    Write-Ahead log 预写日志

    预写日志(WAL,Write-Ahead Log)将每次状态更新抽象为一个命令追加写入一个日志中,这个日志只追加写入,也就是顺序写入,所以 IO 会很快。相比于更新存储的数据结构并且更新落盘这个随机 IO 操作,写入速度更快了,并且也提供了一定的持久性,也就是数据不会丢失,可以根据这个日志恢复数据。

    背景介绍

    如果遇到了服务器存储数据失败,例如已经确认客户端的请求,但是存储过程中,重启进程导致真正存储的数据没有落盘,在重启后,也需要保证已经答应客户端的请求数据更新真正落盘成功。

    解决方案

    image

    将每一个更新,抽象为一个指令,并将这些指令存储在一个文件中。每个进程顺序追加写各自独立的一个文件,简化了重启后日志的处理,以及后续的在线更新操作。每个日志记录有一个独立 id,这个 id 可以用来实现分段日志(Segmented Log)或者最低水位线(Low-Water Mark)清理老的日志。日志更新可以使用单一更新队列(Singular Update Queue)这种设计模式。

    日志记录的结构类似于:

    class WALEntry {
      //日志id
      private final Long entryId;
      //日志内容
      private final byte[] data;
      //类型
      private final EntryType entryType;
      //时间
      private long timeStamp;
    }
    

    在每次重新启动时读取日志文件,回放所有日志条目来恢复当前数据状态。

    假设有一内存键值对数据库:

    class KVStore {
      private Map<String, String> kv = new HashMap<>();
    
      public String get(String key) {
          return kv.get(key);
      }
    
      public void put(String key, String value) {
          appendLog(key, value);
          kv.put(key, value);
      }
    
      private Long appendLog(String key, String value) {
          return wal.writeEntry(new SetValueCommand(key, value).serialize());
      }
    }
    

    put 操作被抽象为 SetValueCommand,在更新内存 hashmap 之前将其序列化并存储在日志中。SetValueCommand 可以序列化和反序列化。

    class SetValueCommand {
      final String key;
      final String value;
    
      public SetValueCommand(String key, String value) {
          this.key = key;
          this.value = value;
      }
    
      @Override
      public byte[] serialize() {
          try {
              //序列化
              var baos = new ByteArrayOutputStream();
              var dataInputStream = new DataOutputStream(baos);
              dataInputStream.writeInt(Command.SetValueType);
              dataInputStream.writeUTF(key);
              dataInputStream.writeUTF(value);
              return baos.toByteArray();
    
          } catch (IOException e) {
              throw new RuntimeException(e);
          }
      }
    
      public static SetValueCommand deserialize(InputStream is) {
          try {
              //反序列化
              DataInputStream dataInputStream = new DataInputStream(is);
              return new SetValueCommand(dataInputStream.readUTF(), dataInputStream.readUTF());
          } catch (IOException e) {
              throw new RuntimeException(e);
          }
      }
    }
    

    这可以确保即使进程重启,这个 hashmap 也可以通过在启动时读取日志文件来恢复。

    class KVStore {
      public KVStore(Config config) {
          this.config = config;
          this.wal = WriteAheadLog.openWAL(config);
          this.applyLog();
      }
    
      public void applyLog() {
          List<WALEntry> walEntries = wal.readAll();
          applyEntries(walEntries);
      }
    
      private void applyEntries(List<WALEntry> walEntries) {
          for (WALEntry walEntry : walEntries) {
              Command command = deserialize(walEntry);
              if (command instanceof SetValueCommand) {
                  SetValueCommand setValueCommand = (SetValueCommand)command;
                  kv.put(setValueCommand.key, setValueCommand.value);
              }
          }
      }
    
      public void initialiseFromSnapshot(SnapShot snapShot) {
          kv.putAll(snapShot.deserializeState());
      }
    }
    

    实现考虑

    首先是保证 WAL 日志真的写入了磁盘。所有编程语言提供的文件处理库提供了一种机制,强制操作系统将文件更改flush落盘。在flush时,需要考虑的是一种权衡。对于日志的每一条记录都flush一次,保证了强持久性,但是严重影响了性能并且很快会成为性能瓶颈。如果是异步flush,性能会提高,但是如果在flush前程序崩溃,则有可能造成日志丢失。大部分的实现都采用批处理,减少flush带来的性能影响,同时也尽量少丢数据。

    另外,我们还需要保证日志文件没有损坏。为了处理这个问题,日志条目通常伴随 CRC 记录写入,然后在读取文件时进行验证。

    同时,采用单个日志文件可能变得很难管理(很难清理老日志,重启时读取文件过大)。为了解决这个问题,通常采用之前提到的分段日志(Segmented Log)或者最低水位线(Low-Water Mark)来减少程序启动时读取的文件大小以及清理老的日志。

    最后,要考虑重试带来的重复问题,也就是幂等性。由于 WAL 日志仅附加,在发生客户端通信失败和重试时,日志可能包含重复的条目。当读取日志条目时,可能会需要确保重复项被忽略。但是如果存储类似于 HashMap,其中对同一键的更新是幂等的,则不需要排重,但是可能会存在 ABA 更新问题。一般都需要实现某种机制来标记每个请求的唯一标识符并检测重复请求。

    举例

    各种 MQ 中的类似于 CommitLog 的日志

    MQ 中的消息存储,由于消息队列的特性导致消息存储和日志类似,所以一般用日志直接作为存储。这个消息存储一般就是 WAL 这种设计模式,以 RocketMQ 为例子:

    RocketMQ:
    image

    RocketMQ 存储首先将消息存储在 Commitlog 文件之中,这个文件采用的是 mmap (文件映射内存)技术写入与保存。关于这个技术,请参考另一篇文章JDK核心JAVA源码解析(5) - JAVA File MMAP原理解析

    当消息来时,写入文件的核心方法是MappedFileappendMessagesInner方法:

    public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb) {
        assert messageExt != null;
        assert cb != null;
        //获取当前写入位置
        int currentPos = this.wrotePosition.get();
        //如果当前写入位置小于文件大小则尝试写入
        if (currentPos < this.fileSize) {
            //mappedByteBuffer是公用的,在这里不能修改其position影响读取
            //mappedByteBuffer是文件映射内存抽象出来的文件的内存ByteBuffer
            //对这个buffer的写入,就相当于对文件的写入
            //所以通过slice方法生成一个共享原有相同内存的新byteBuffer,设置position
            //如果writeBuffer不为空,则证明启用了TransientStorePool,使用其中缓存的内存写入
            ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice();
            byteBuffer.position(currentPos);
            AppendMessageResult result;
            //分单条消息还有批量消息的情况
            if (messageExt instanceof MessageExtBrokerInner) {
                result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt);
            } else if (messageExt instanceof MessageExtBatch) {
                result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt);
            } else {
                return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
            }
            //增加写入大小
            this.wrotePosition.addAndGet(result.getWroteBytes());
            //更新最新消息保存时间
            this.storeTimestamp = result.getStoreTimestamp();
            return result;
        }
        log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize);
        return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
    }
    

    RocketMQ 将消息存储在 Commitlog 文件后,异步更新 ConsumeQueue 还有 Index 文件。这个 ConsumeQueue 还有 Index 文件可以理解为存储状态,CommitLog 在这里扮演的就是 WAL 日志的角色:只有写入到 ConsumeQueue 的消息才会被消费者消费,只有 Index 文件中存在的记录才能被读取定位到。如果消息成功写入 CommitLog 但是异步更新还没执行,RocketMQ 进程挂掉了,这样就存在了不一致。所以在 RocketMQ 启动的时候,会通过如下机制保证 Commitlog 与 ConsumeQueue 还有 Index 的最终一致性.

    入口是DefaultMessageStoreload方法:

    public boolean load() {
        boolean result = true;
        try {
            //RocketMQ Broker启动时会创建${ROCKET_HOME}/store/abort文件,并添加JVM shutdownhook删除这个文件
            //通过这个文件是否存判断是否为正常退出
            boolean lastExitOK = !this.isTempFileExist();
            log.info("last shutdown {}", lastExitOK ? "normally" : "abnormally");
    
            //加载延迟队列消息,这里先忽略
            if (null != scheduleMessageService) {
                result = result && this.scheduleMessageService.load();
            }
    
            //加载 Commit Log 文件
            result = result && this.commitLog.load();
    
            //加载 Consume Queue 文件
            result = result && this.loadConsumeQueue();
    
            if (result) {
                //加载存储检查点
                this.storeCheckpoint =
                    new StoreCheckpoint(StorePathConfigHelper.getStoreCheckpoint(this.messageStoreConfig.getStorePathRootDir()));
                //加载 index,如果不是正常退出,销毁所有索引上次刷盘时间小于索引文件最大消息时间戳的文件
                this.indexService.load(lastExitOK);
                //进行 recover 恢复之前状态
                this.recover(lastExitOK);
                log.info("load over, and the max phy offset = {}", this.getMaxPhyOffset());
            }
        } catch (Exception e) {
            log.error("load exception", e);
            result = false;
        }
        if (!result) {
            this.allocateMappedFileService.shutdown();
        }
        return result;
    }
    
    

    进行恢复是DefaultMessageStorerecover方法:

    private void recover(final boolean lastExitOK) {
        long maxPhyOffsetOfConsumeQueue = this.recoverConsumeQueue();
        //根据上次是否正常退出,采用不同的恢复方式
        if (lastExitOK) {
            this.commitLog.recoverNormally(maxPhyOffsetOfConsumeQueue);
        } else {
            this.commitLog.recoverAbnormally(maxPhyOffsetOfConsumeQueue);
        }
    
        this.recoverTopicQueueTable();
    }
    

    当上次正常退出时:

    public void recoverNormally(long maxPhyOffsetOfConsumeQueue) {
        boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
        final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
        if (!mappedFiles.isEmpty()) {
            //只扫描最后三个文件
            int index = mappedFiles.size() - 3;
            if (index < 0)
                index = 0;
            MappedFile mappedFile = mappedFiles.get(index);
            ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
            long processOffset = mappedFile.getFileFromOffset();
            long mappedFileOffset = 0;
            while (true) {
                //检验存储消息是否有效
                DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
                int size = dispatchRequest.getMsgSize();
                //如果有效,添加这个偏移
                if (dispatchRequest.isSuccess() && size > 0) {
                    mappedFileOffset += size;
                }
                //如果有效,但是大小是0,代表到了文件末尾,切换文件
                else if (dispatchRequest.isSuccess() && size == 0) {
                    index++;
                    if (index >= mappedFiles.size()) {
                        // Current branch can not happen
                        log.info("recover last 3 physics file over, last mapped file " + mappedFile.getFileName());
                        break;
                    } else {
                        mappedFile = mappedFiles.get(index);
                        byteBuffer = mappedFile.sliceByteBuffer();
                        processOffset = mappedFile.getFileFromOffset();
                        mappedFileOffset = 0;
                        log.info("recover next physics file, " + mappedFile.getFileName());
                    }
                }
                //只有有无效的消息,就在这里停止,之后会丢弃掉这个消息之后的所有内容
                else if (!dispatchRequest.isSuccess()) {
                    log.info("recover physics file end, " + mappedFile.getFileName());
                    break;
                }
            }
            processOffset += mappedFileOffset;
            this.mappedFileQueue.setFlushedWhere(processOffset);
            this.mappedFileQueue.setCommittedWhere(processOffset);
            //根据有效偏移量,删除这个偏移量以后的所有文件,以及所有文件(正常是只有最后一个有效文件,而不是所有文件)中大于这个偏移量的部分
            this.mappedFileQueue.truncateDirtyFiles(processOffset);
            //根据 commit log 中的有效偏移量,清理 consume queue
            if (maxPhyOffsetOfConsumeQueue >= processOffset) {
                log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
                this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
            }
        } else {
            //所有commit log都删除了,那么偏移量就从0开始
            log.warn("The commitlog files are deleted, and delete the consume queue files");
            this.mappedFileQueue.setFlushedWhere(0);
            this.mappedFileQueue.setCommittedWhere(0);
            this.defaultMessageStore.destroyLogics();
        }
    }
    

    当上次没有正常退出时:

    public void recoverAbnormally(long maxPhyOffsetOfConsumeQueue) {
        boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();
        final List<MappedFile> mappedFiles = this.mappedFileQueue.getMappedFiles();
        if (!mappedFiles.isEmpty()) {
            // 从最后一个文件开始,向前寻找第一个正常的可以恢复消息的文件
            // 从这个文件开始恢复消息,因为里面的消息有成功写入过 consumer queue 以及 index 的,所以从这里恢复一定能保证最终一致性
            // 但是会造成某些已经写入过 consumer queue 的消息再次写入,也就是重复消费。
            int index = mappedFiles.size() - 1;
            MappedFile mappedFile = null;
            for (; index >= 0; index--) {
                mappedFile = mappedFiles.get(index);
                //寻找第一个有正常消息的文件
                if (this.isMappedFileMatchedRecover(mappedFile)) {
                    log.info("recover from this mapped file " + mappedFile.getFileName());
                    break;
                }
            }
            //如果小于0,就恢复所有 commit log,或者代表没有 commit log
            if (index < 0) {
                index = 0;
                mappedFile = mappedFiles.get(index);
            }
            ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();
            long processOffset = mappedFile.getFileFromOffset();
            long mappedFileOffset = 0;
            while (true) {
                //验证消息有效性
                DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover);
                int size = dispatchRequest.getMsgSize();
                //如果消息有效
                if (dispatchRequest.isSuccess()) {
                    if (size > 0) {
                        mappedFileOffset += size;
    
                        if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) {
                            //如果允许消息重复转发,则需要判断当前消息是否消息偏移小于已确认的偏移,只有小于的进行重新分发
                            if (dispatchRequest.getCommitLogOffset() < this.defaultMessageStore.getConfirmOffset()) {
                                //重新分发消息,也就是更新 consume queue 和 index
                                this.defaultMessageStore.doDispatch(dispatchRequest);
                            }
                        } else {
                            //重新分发消息,也就是更新 consume queue 和 index
                            this.defaultMessageStore.doDispatch(dispatchRequest);
                        }
                    }
                    //大小为0代表已经读完,切换下一个文件
                    else if (size == 0) {
                        index++;
                        if (index >= mappedFiles.size()) {
                            // The current branch under normal circumstances should
                            // not happen
                            log.info("recover physics file over, last mapped file " + mappedFile.getFileName());
                            break;
                        } else {
                            mappedFile = mappedFiles.get(index);
                            byteBuffer = mappedFile.sliceByteBuffer();
                            processOffset = mappedFile.getFileFromOffset();
                            mappedFileOffset = 0;
                            log.info("recover next physics file, " + mappedFile.getFileName());
                        }
                    }
                } else {
                    log.info("recover physics file end, " + mappedFile.getFileName() + " pos=" + byteBuffer.position());
                    break;
                }
            }
    
            //更新偏移
            processOffset += mappedFileOffset;
            this.mappedFileQueue.setFlushedWhere(processOffset);
            this.mappedFileQueue.setCommittedWhere(processOffset);
            this.mappedFileQueue.truncateDirtyFiles(processOffset);
    
            //清理
            if (maxPhyOffsetOfConsumeQueue >= processOffset) {
                log.warn("maxPhyOffsetOfConsumeQueue({}) >= processOffset({}), truncate dirty logic files", maxPhyOffsetOfConsumeQueue, processOffset);
                this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);
            }
        }
        // Commitlog case files are deleted
        else {
            log.warn("The commitlog files are deleted, and delete the consume queue files");
            this.mappedFileQueue.setFlushedWhere(0);
            this.mappedFileQueue.setCommittedWhere(0);
            this.defaultMessageStore.destroyLogics();
        }
    }
    

    总结起来就是:

    • 首先,根据 abort 文件是否存在判断上次是否正常退出。
    • 对于正常退出的:
      • 扫描倒数三个文件,记录有效消息的偏移
      • 扫描到某个无效消息结束,或者扫描完整个文件
      • 设置最新偏移,同时根据这个偏移量清理 commit log 和 consume queue
    • 对于没有正常退出的:
      • 从最后一个文件开始,向前寻找第一个正常的可以恢复消息的文件
      • 从这个文件开始恢复并重发消息,因为里面的消息有成功写入过 consumer queue 以及 index 的,所以从这里恢复一定能保证最终一致性。但是会造成某些已经写入过 consumer queue 的消息再次写入,也就是重复消费。
      • 更新偏移,清理

    数据库

    基本上所有的数据库都会有 WAL 类似的设计,例如 MySQL 的 Innodb redo log 等等。
    image

    image

    一致性存储

    例如 ZK 还有 ETCD 这样的一致性中间件。

    展开全文
  • log4cpp源码完整解析

    2016-05-16 21:51:13
    完整分析了log4cpp的整体架构,详细介绍了log4cpp的这个重要组件的实现分析了log4cpp内部所使用的设计模式。介绍了log4cpp中的Category的完整实现细节,介绍了所有的Layout及其子类的具体实现。也详细介绍了比较常用...
  • 使用 Log4j 中的自定义 Appender,将服务运行打印的日志直接推送到 Kafka 中。经由 Logstash 消费 Kafka 生产的数据,进行加工过滤后输出到 ElasticSearch 进行日志数据的存储与全文检索。使用 Kibana 对日志数据...

    ELK 日志系统的常见解决方案:
    通常的产品或项目部署至服务器,服务一般会打印日志便于线上问题跟踪。
    使用 Log4j 中的自定义 Appender,将服务运行打印的日志直接推送到 Kafka 中。经由 Logstash 消费 Kafka 生产的数据,进行加工过滤后输出到 ElasticSearch 进行日志数据的存储与全文检索。使用 Kibana 对日志数据进行可视化操作。

    1. 单点日志系统设计

    在这里插入图片描述

    • 相对于 Filebeat 日志收集后输出到 Kafka 的方案,需要服务器存储日志文件。当随着业务复杂性上升,单日日志量也会较大,存储历史日志将占用服务器内存,且不便管理。
    • 使用自定义 Appender 直接打印日志上报 Kafka,去掉了 Filebeat 日志收集,并解决了日志文件存储空间占用的问题。不过也存在因网络传输等原因造成日志丢失的风险。

    2. 自定义Appender打包说明

    • 应用场景
      应用于 ELK 日志直推 Kafka 设计场景,自定义 Appender 项目开发打Jar包如下
      log-system-util-1.0-SNAPSHOT-jar-with-dependencies.jar
    • 功能设计
      项目打印日志,通过 log4j 将日志信息推送到 Kafka;
      作为日志服务器监测对接应用存活状态,心跳监测或轮询监测;
    • 本地开发自定义 Appender 项目
      实现 Appender 打印日志上报指定 Kafka 服务,项目本地打 jar 包,已供多应用对接此工具
    • 安装 jar 包到 maven 仓库
      mvn install:install-file
      -Dfile=C:\Users\Zxy\Desktop\CODE\log-system-util-1.0-SNAPSHOT-jar-with-dependencies.jar
      -DgroupId=cn.nascent -DartifactId=log-system-util
      -Dversion=1.0 -Dpackaging=jar

    当出现BUILD SUCCESS 时就说明安装成功
    命令说明
    (1)-Dfile jar包所在路径,需要包含jar包名.例如:xx/xx/xx/**.jar
    (2)-DgroupId 指定导入jar时的groupid,可以自定义,cn.nascent就是自定义的
    (3)-DartifactId指定导入jar时的artifactId,可以自定义,log-system-util就是自定义的
    (4)-Dversion 指定导入jar时的版本号,可以自定义。这里的1.0就是自定义的
    (5)-Dpackaging 指定文件类型 ,由于这里是jar包的形式,所以这里得是jar

    • 引入依赖
      pers.niaonao 、 log-system-util 、 1.0.0 就是安装jar包时指定的参数
    <dependency>
          <groupId>pers.niaonao</groupId>
          <artifactId>log-system-util</artifactId>
          <version>1.0.0</version>
    </dependency>
    
    • 修改log4j.properties配置文件
    #输出到kafka
    log4j.logger.pers.niaonao=INFO,KAFKA  
    # appender KAFKA
    log4j.appender.KAFKA=pers.niaonao.kafka.KafkaLog4jAppender
    

    3. 使用示例

    package pers.niaonao;
    
    import org.apache.log4j.Logger;
    
    public class MyAppender {
    
        private static Logger LOGGER = Logger.getLogger(MyAppender.class);
    
        public static void main(String[] args) throws InterruptedException {
    
            int times = 16;
            String mess = "Test My Appender";
            String sendMess = null;
            for (int i = 0; i < times; i++) {
                sendMess = mess + "=======>" + i;
                MyAppender.LOGGER.info(sendMess);
                Thread.sleep(300);
            }
        }
    }
    

    注意
    (1) 不可以将 Kafka 定义到 rootLogger 中,这会造成程序的卡顿。因此需要另外定义一个 rootLogger。
    (2) log4j.logger.后,=之前的的必须是你的包名。当你指定了这个包名之后,那么在这个包下的类产生的日志才会通过下面定义的appender 输送到 kafka 中。
    (3) log4j.appender.KAFKA 这条配置,会在LogManager 类加载时,作为一个 appender 添加到以 log4j.logger 后面跟的包名为名的 logger 中去。之后这个包下的类获取 Logger 时会在类 Hierarchy.class 中的 updateParents 方法中将配置文件中的 appender 添加到在类中获取的 logger(Logger.getLogger(xx.class)) 中。

    参考文章
    ELK日志系统设计方案-Filebeat日志收集推送Kafka
    ELK日志系统设计方案-Log4j日志直推Kafka
    ELK日志系统设计方案-集群扩展
    ELK日志系统部署实现
    Powered By niaonao

    展开全文
  • 什么是redo log和undo log

    千次阅读 2021-12-03 13:52:15
    MySQL日志系统中最重要的日志为重做日志redo log和归档日志bin log,后者为MySQL Server层的日志,前者为InnoDB存储引擎层的日志。 1 重做日志redo log 1.1 什么是redo log redo log用于保证事务的持久性,即ACID...

    MySQL日志系统中最重要的日志为 重做日志redo log 和 归档日志bin log ,后者为MySQL Server层的日志,前者为InnoDB存储引擎层的日志。

    1 重做日志redo log

    1.1 什么是redo log

    redo log用于保证事务的持久性,即ACID中的D。

    持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

    redo log有两种类型,分别为物理重做日志和逻辑重做日志。 在InnoDB中redo log大多数情况下是一个物理日志,记录数据页面的物理变化(实际的数据值)。

    1.2 redo log的功能

    redo log的主要功能是用于数据库崩溃时的数据恢复。

    1.3 redo log的组成

    redo log可以分为以下两部分

    • 存储在内存中的重做日志缓冲区

    • 存储在磁盘上的重做日志文件

    MySQL中的redo log和undo log

    1.4 记录redo log的时机

    • 在 完成数据的修改之后,脏页刷入磁盘之前 写入重做日志缓冲区。即先修改,再写入。脏页:内存中与磁盘上不一致的数据(并不是坏的!)

    • 在以下情况下,redo log由重做日志缓冲区写入磁盘上的重做日志文件。redo log buffer的日志占据redo log buffer总容量的一半是 ,将redo log写入磁盘。一个事务提交时 ,他的redo log都刷入磁盘,这样可以保证数据绝不丢失(最常见的情况)。注意这时内存中的脏页可能尚未全部写入磁盘。后台线程定时刷新 ,有一个后台线程每过一秒就将redo log写入磁盘。MySQL关闭时 ,redo log都被写入磁盘。第一种情况和第四种情况一定会执行redo log的写入,第二种情况和第三种情况的执行要根据参数 innodb_flush_log_at_trx_commit 的设定值,在下文会有详细描述。

    • 索引的创建也需要记录redo log。

    1.5 一个重做全过程的示例

    MySQL中的redo log和undo log

    以更新事务为例。

    1. 将原始数据读入内存,修改数据的内存副本。

    2. 生成redo log并写入重做日志缓冲区,redo log中存储的是修改后的新值。

    3. 事务提交时,将重做日志缓冲区中的内容刷新到重做日志文件。

    4. 随后正常将内存中的脏页刷回磁盘。

    1.6 持久性的保证

    1.6.1 Force Log at Commit机制

    Force Log at Commit机制实现了事务的持久性。 在内存中操作时,日志被写入重做日志缓冲区。但在事务提交之前,必须首先将所有日志写入磁盘上的重做日志文件。

    为了确保每个日志都写入重做日志文件,必须使用一个fsync系统调用,确保OS buffer中的日志被完整地写入磁盘上的log file。

    fsync系统调用:需要你在入参的位置上传递给他一个fd,然后系统调用就会对这个fd指向的文件起作用。fsync会确保一直到写磁盘操作结束才会返回,所以当你的程序使用这个函数并且它成功返回时,就说明数据肯定已经安全的落盘了。所以fsync适合数据库这种程序。

    MySQL中的redo log和undo log

    1.6.2 innodb_flush_log_at_trx_commit参数

    InnoDB提供了一个参数innodb_flush_log_at_trx_commit 控制日志刷新到磁盘的策略。

    • 当 innodb_flush_log_at_trx_commit 值为1时(默认)。 事务每次提交都必须将log buffer中的日志写入os buffer并调用fsync()写入磁盘中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO性能较差。

    • 当 innodb_flush_log_at_trx_commit 值为0时。 事务提交时不将log buffer写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。这实际上相当于在内存中维护了一个用户设计的缓冲区,它减少了和os buffer之间的数据传输,有更好的性能。每秒写入磁盘,系统崩溃会丢失1s的数据。

    • 当 innodb_flush_log_at_trx_commit 值为2时。 每次提交都仅写入os buffer,然后每秒调用fsync()将os buffer中的日志写入到log file on disk中。虽然说我们是每秒调用fsync()将os buffer中的日志写入到log file on disk中,但是平时即使不调用fsync,数据也会2自主地逐渐进入磁盘。所以当发生系统崩溃,相比第二种情况,会丢失较少的数据。但同时,由于每次提交都写入os buffer,所以相比第二种情况,性能会差一些,但还是比第一种好的。

    • 无论是哪种情况

    MySQL中的redo log和undo log

    1.6.3 一个小的性能测试

    几个选项之间的性能差距是极大的,下面做一个简单的测试。

    #创建测试表
    drop table if exists test_flush_log;
    create table test_flush_log(id int,name char(50))engine=innodb;
    
    #创建插入指定行数的记录到测试表中的存储过程
    drop procedure if exists proc;
    delimiter $$
    create procedure proc(i int)
    begin
        declare s int default 1;
        declare c char(50) default repeat('a',50);
        while s<=i do
            start transaction;
            insert into test_flush_log values(null,c);
            commit;
            set s=s+1;
        end while;
    end$$
    delimiter ;

    下面均插入十万条记录。

    Ⅰ 当innodb_flush_log_at_trx_commit值为1时

    test> call proc(100000)
    [2021-07-25 13:22:02] completed in 27 s 350 ms

    需要长达27.35s。

    Ⅱ 当innodb_flush_log_at_trx_commit值为2时

    test> set @@global.innodb_flush_log_at_trx_commit=2;    
    test> truncate test_flush_log;
    
    test> call proc(100000)
    [2021-07-25 13:27:33] completed in 5 s 774 ms

    只需5.774s,性能大大提升。

    Ⅲ 当innodb_flush_log_at_trx_commit值为0时

    test> set @@global.innodb_flush_log_at_trx_commit=0;
    test> truncate test_flush_log;
    
    test> call proc(100000)
    [2021-07-25 13:30:34] completed in 3 s 537 ms

    只需3.537s,性能更高。

    显然,innodb_flush_log_at_trx_commit值为1时性能差得非常明显,改为0和2后性能都有大幅提升,其中0更快但相比2提升不大。

    虽然改为0和2可以大幅提升性能,但会严重影响安全性。 我们可以通过修改存储过程,将事务的创建和提交放到循环外,统一提交,减少了IO频率。

    drop procedure if exists proc;
    delimiter $$
    create procedure proc(i int)
    begin
        declare s int default 1;
        declare c char(50) default repeat('a',50);
        start transaction;
        while s<=i DO
            insert into test_flush_log values(null,c);
            set s=s+1;
        end while;
        commit;
    end$$
    delimiter ;

    1.6.4 迷你事务mini-transaction

    mini-trasaction是InnoDB处理小型事务时使用的一种机制,它可以确保 并发事务操作和数据库异常发生时,数据页中的数据一致性。

    迷你事务必须遵循下面三个协议:

    • FIX规则。写时必须使用独占锁,读时必须使用共享锁。反正就是要锁住。

    • 预写日志。预写日志即WAL,Write-Ahead Log。持久化数据之前,必须先持久化内存中的日志。每个页面都有一个LSN(日志序列号)。在将数据写入磁盘前,要先将内存中序列号小于LSN的日志写入磁盘。WAL提供三种持久化模式最严格的是full-sync,fsync保证在返回之前将记录刷新到磁盘,最大化了数据的安全性。

    MySQL中的redo log和undo log

    • 第二个级别是write-only,保证记录写入操作系统。这允许数据在进程级别的崩溃后幸存。

    MySQL中的redo log和undo log

    • 最不严格的是no-sync,将记录保存在内存缓冲区中,不保证立即写入文件系统。

    MySQL中的redo log和undo log

    • 强制日志再提交。 即Force-log-at-commit,它要求提交事务时必须把所有迷你事务日志刷新到磁盘。

    1.7 写redo log的过程

    MySQL中的redo log和undo log

    如上图,展示了redo log是如何被写入log buffer的。 每个mini-trasaction对应于每个DML操作 ,例如更新语句等。

    • 每个数据修改后被写入迷你事务私有缓冲区。

    • 当更新语句完成,redo log从迷你事务私有缓冲区被写入内存中的公共日志缓冲区。

    • 提交外部事务时,会将重做日志缓冲区刷入重做日志文件。

    1.8 日志块 log block

    redo log以块为单位进行存储,每个块大小为512字节。无论是在内存重做日志缓冲区、操作系统缓冲区还是重做日志文件中,都是以这样的512字节大小地块进行存储的。

    MySQL中的redo log和undo log

    每个日志块头由以下四个部分组成

    • log_block_hdr_no:(4字节)该日志块在redo log buffer中的位置ID。

    • log_block_hdr_data_len:(2字节)该log block中已记录的log大小。写满该log block时为0x200,表示512字节。

    • log_block_first_rec_group:(2字节)该log block中第一个log的开始偏移位置。

    • lock_block_checkpoint_no:(4字节)写入检查点信息的位置。

    1.9 log group

    log group代表redo log的分组,由多个大小相同的redo log file组成。由一个参数 innodb_log_files_group 决定,默认为2。

    [外链图片转存失败,源站可能有防盗img-qAyaSeL3543740G:61311akw89MySQL[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h01w68EG-1627284031849)(G:\markdown\MySQL\image-20210726131134489.png)].png)]

    这个group是逻辑上的概念,但可以通过变量 innodb_log_group_home_dir 来定义组的目录,redo log file都放在这个目录下,默认是在datadir下。

    MySQL中的redo log和undo log

    2 撤销日志undo log

    2.1 关于undo log

    • undo log存在的意义是确保数据库事务的原子性。原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

    • redo log记录了事务的行为,可以很好地保证一致性,对数据进行“重做”操作。但事务有时还需要进行“回滚”操作,这时就需要undo log。当我们对记录做了变更操作的时候就需要产生undo log,其中记录的是老版本的数据,当旧事务需要读取数据时,可以顺着undo链找到满足其可见性的记录。

    • undo log通常以逻辑日志的形式存在。我们可以认为当delete一条记录时,undo log会产生一条对应的insert记录,反之亦然。当update一条记录时,会产生一条相反的update记录。

    • undo log采用段segment的方式来记录,每个undo操作在记录的时候占用一个undo log segment。

    • undo log也会产生redo log,因为undo log也要实现持久性保护。

    2.2 undo log segment

    为了保证事务并发操作时,写各自的undo log时不发生冲突,nnodb用段的方式管理undo log。 rollback segment称为回滚段,每个回滚段中有1024个undo log segment。 MySQL5.5以后的版本支持128个rollback segment,就可以存储128*1024个操作,还可以通过 innodb_undo_logs 参数定义盯梢个rollback segment。

    MySQL中的redo log和undo log

    2.3 purge

    在聚集索引列的操作中,MySQL是这样设计的。对一条delete语句

    delete from t where a = 1

    假如a有聚集索引(主键),那么不会进行真正的删除,而是在主键列等于1的记录处设置delete flag为1,即把记录保存在B+树中。同理,对于update操作,不是直接更新记录,而是把旧记录标识给删除,再创建一条新记录。

    那么,旧版本记录什么时候真正的删除呢?

    InnoDB使用undo日志进行旧版本的删除操作,这个操作称为purge操作。InnoDB开辟了purge线程进行purge操作,并且可以控制purge线程的数量,每个purge线程每10s 进行一次purge操作。

    InnoDB的undo log设计

    一个页上允许多个事务的undo log存在,undo log的存储顺序是随时的。 InnoDB维护了一个history链表,按照事务提交的顺序将undo log进行连接。

    MySQL中的redo log和undo log

    在执行purge过程中,InnoDB存储引擎首先从history list中找到第一个需要被清理的记录,这里为trx1,清理之后InnoDB存储引擎会在trx1所在的Undo page中继续寻找是否存在可以被清理的记录,这里会找到事务trx3,接着找到trx5,但是发现trx5被其他事务所引用而不能清理,故再去history list中去查找,发现最尾端的记录时trx2,接着找到trx2所在的Undo page,依次把trx6、trx4清理,由于Undo page2中所有的记录都被清理了,因此该Undo page可以进行重用。

    InnoDB存储引擎这种先从history list中找undo log,然后再从Undo page中找undo log的设计模式是为了避免大量随机读操作,从而提高purge的效率。

    3 InnoDB的恢复操作

    3.1 数据页刷盘的规则和checkpoint

    内存中(buffer pool)未刷到磁盘的数据称为脏数据(dirty data)。由于数据和日志都以页的形式存在,所以脏页表示脏数据和脏日志。

    在InnoDB中,checkpoint是数据刷盘的唯一规则。checkpoint触发后,会将内存中的脏数据刷到磁盘。

    innodb存储引擎中checkpoint分为两种:

    • sharp checkpoint:在重用redo log文件(例如切换日志文件)的时候,将所有已记录到redo log中对应的脏数据刷到磁盘。

    • fuzzy checkpoint:一次只刷一小部分的日志到磁盘,而非将所有脏日志刷盘。有以下几种情况会触发该检查点:master thread checkpoint。由master线程控制, 每秒或每10秒 刷入一定比例的脏页到磁盘。flush_lru_list checkpoint。从MySQL5.6开始可通过 innodb_page_cleaners 变量指定专门负责脏页刷盘的page cleaner线程的个数,该线程的目的是为了保证lru列表有可用的空闲页。async/sync flush checkpoint。同步刷盘还是异步刷盘。例如还有非常多的脏页没刷到磁盘(非常多是多少,有比例控制),这时候会选择同步刷到磁盘,但这很少出现;如果脏页不是很多,可以选择异步刷到磁盘,如果脏页很少,可以暂时不刷脏页到磁盘dirty page too much checkpoint。脏页太多时强制触发检查点,目的是为了保证缓存有足够的空闲空间。too much的比例有变量 innodb_max_dirty_pages_pct 控制,MySQL 5.6默认的值为75,即当脏页占缓冲池的百分之75后,就强制刷一部分脏页到磁盘。

    由于刷脏页需要一定的时间来完成,所以记录检查点的位置是在每次刷盘结束之后才在redo log中标记的。

    3.2 LSN

    3.2.1 LSN概念

    LSN称为日志的逻辑序列号,在InnoDB中占用8个字节

    我们可以通过LSN了解到下面这些信息:

    • 数据页的版本信息。

    • 写入的日志总量。

    • 检查点的位置。

    在下面两个位置存在LSN:

    fil_page_lsn
    

    显然,如果页中的LSN值小于redo log中的LSN值,说明数据出现了丢失。

    通过 show engine innodb status 可以查看当前InnoDB的运行信息,其中有一栏log中有关于lsn的记录。

    MySQL中的redo log和undo log

    • log sequence number记录了当前的redo log(in buffer)中的LSN。

    • log flushed up to是刷到磁盘重做日志文件中的LSN。

    • pages flushed up to是已经刷到磁盘数据页上的LSN。

    • last checkpoint at是上一次检查点所在位置的LSN。

    3.2.2 LSN处理流程

    (1).首先修改内存中的数据页,并在数据页中记录LSN,暂且称之为data_in_buffer_lsn;

    (2).并且在修改数据页的同时(几乎是同时)向redo log in buffer中写入redo log,并记录下对应的LSN,暂且称之为redo_log_in_buffer_lsn;

    (3).写完buffer中的日志后,当触发了日志刷盘的几种规则时,会向redo log file on disk刷入重做日志,并在该文件中记下对应的LSN,暂且称之为redo_log_on_disk_lsn;

    (4).数据页不可能永远只停留在内存中,在某些情况下,会触发checkpoint来将内存中的脏页(数据脏页和日志脏页)刷到磁盘,所以会在本次checkpoint脏页刷盘结束时,在redo log中记录checkpoint的LSN位置,暂且称之为checkpoint_lsn。

    (5).要记录checkpoint所在位置很快,只需简单的设置一个标志即可,但是刷数据页并不一定很快,例如这一次checkpoint要刷入的数据页非常多。也就是说要刷入所有的数据页需要一定的时间来完成,中途刷入的每个数据页都会记下当前页所在的LSN,暂且称之为data_page_on_disk_lsn。

    MySQL中的redo log和undo log

    上图中,从上到下的横线分别代表:时间轴、buffer中数据页中记录的LSN(data_in_buffer_lsn)、磁盘中数据页中记录的LSN(data_page_on_disk_lsn)、buffer中重做日志记录的LSN(redo_log_in_buffer_lsn)、磁盘中重做日志文件中记录的LSN(redo_log_on_disk_lsn)以及检查点记录的LSN(checkpoint_lsn)。

    假设在最初时(12:0:00)所有的日志页和数据页都完成了刷盘,也记录好了检查点的LSN,这时它们的LSN都是完全一致的。

    假设此时开启了一个事务,并立刻执行了一个update操作,执行完成后,buffer中的数据页和redo log都记录好了更新后的LSN值,假设为110。这时候如果执行 show engine innodb status 查看各LSN的值,即图中①处的位置状态,结果会是:

    log sequence number(110) > log flushed up to(100) = pages flushed up to = last checkpoint at

    之后又执行了一个delete语句,LSN增长到150。等到12:00:01时,触发redo log刷盘的规则(其中有一个规则是innodb_flush_log_at_timeout 控制的默认日志刷盘频率为1秒),这时redo log file on disk中的LSN会更新到和redo log in buffer的LSN一样,所以都等于150,这时 show engine innodb status ,即图中②的位置,结果将会是:

    log sequence number(150) = log flushed up to > pages flushed up to(100) = last checkpoint at

    再之后,执行了一个update语句,缓存中的LSN将增长到300,即图中③的位置。

    假设随后检查点出现,即图中④的位置,正如前面所说,检查点会触发数据页和日志页刷盘,但需要一定的时间来完成,所以在数据页刷盘还未完成时,检查点的LSN还是上一次检查点的LSN,但此时磁盘上数据页和日志页的LSN已经增长了,即:

    log sequence number > log flushed up to 和 pages flushed up to > last checkpoint at

    但是log flushed up to和pages flushed up to的大小无法确定,因为日志刷盘可能快于数据刷盘,也可能等于,还可能是慢于。但是checkpoint机制有保护数据刷盘速度是慢于日志刷盘的:当数据刷盘速度超过日志刷盘时,将会暂时停止数据刷盘,等待日志刷盘进度超过数据刷盘。

    等到数据页和日志页刷盘完毕,即到了位置⑤的时候,所有的LSN都等于300。

    随着时间的推移到了12:00:02,即图中位置⑥,又触发了日志刷盘的规则,但此时buffer中的日志LSN和磁盘中的日志LSN是一致的,所以不执行日志刷盘,即此时 show engine innodb status 时各种lsn都相等。

    随后执行了一个insert语句,假设buffer中的LSN增长到了800,即图中位置⑦。此时各种LSN的大小和位置①时一样。

    随后执行了提交动作,即位置⑧。默认情况下,提交动作会触发日志刷盘,但不会触发数据刷盘,所以 show engine innodb status 的结果是:

    log sequence number = log flushed up to > pages flushed up to = last checkpoint at

    最后随着时间的推移,检查点再次出现,即图中位置⑨。但是这次检查点不会触发日志刷盘,因为日志的LSN在检查点出现之前已经同步了。假设这次数据刷盘速度极快,快到一瞬间内完成而无法捕捉到状态的变化,这时 show engine innodb status 的结果将是各种LSN相等。

    3.3 InnoDB的恢复行为

    启动InnoDB时,一定会进行恢复操作,无论上次是因为什么原因退出。

    checkpoint表示已经完整刷到磁盘上data page上的LSN,因此恢复时仅需要恢复从checkpoint开始的日志部分。例如,当数据库在上一次checkpoint的LSN为10000时宕机,且事务是已经提交过的状态。启动数据库时会检查磁盘中数据页的LSN,如果数据页的LSN小于日志中的LSN,则会从检查点开始恢复。

    还有一种情况,在宕机前正处于checkpoint的刷盘过程,且数据页的刷盘进度超过了日志页的刷盘进度。这时候一宕机,数据页中记录的LSN就会大于日志页中的LSN,在重启的恢复过程中会检查到这一情况,这时超出日志进度的部分将不会重做,因为这本身就表示已经做过的事情,无需再重做。

    另外, 事务日志具有幂等性,所以多次操作得到同一结果的行为在日志中只记录一次。 而二进制日志不具有幂等性,多次操作会全部记录下来,在恢复的时候会多次执行二进制日志中的记录,速度就慢得多。例如,某记录中id初始值为2,通过update将值设置为了3,后来又设置成了2,在事务日志中记录的将是无变化的页,根本无需恢复;而二进制会记录下两次update操作,恢复时也将执行这两次update操作,速度比事务日志恢复更慢。

    原文链接:http://www.cnblogs.com/WangXianSCU/p/15061753.html

    还想了解更多的小伙伴,扫描下方二维码关注公众号“w的编程日记”获取更多免费资料

    展开全文
  • boost log库学习一(设计概述)

    万次阅读 2017-06-22 20:00:12
    boost log设计主要由日志器( Logger )、日志核心( Logging core )、 Sink 前后端( frontend,backend )组成。日志文本以及日志环境由日志器( Logger )负责搜集,日志核心负责处理日志数据(例如全局过滤...
  • 关于Log4j 1.x 升级Log4j 2.x 那些事

    千次阅读 2019-09-20 12:35:37
    log4j 1.x 升级log4j 2.x 踩坑攻略
  • Log4j for C++ 实用指南

    千次阅读 2018-07-01 03:45:55
    课程介绍 日志是一个优秀系统不可或缺的组成部分,利用它我们可以记录系统中所产生的所有行为。 对于很多人来说,日志的...即使像《代码整洁之道》这样的书籍,也建议学习像 Log4j 这样的框架进行日志记录。 本达...
  • log4j的一些设计模式

    千次阅读 2012-08-17 16:11:08
    最近在读log4j的源码,源码不大,但是初次阅读源码,还是会不断地出现疑惑。...下面说说最近发现用到的几个设计模式: 1、Factory 模式  Logger logger= Logger.getLogger(Test.class.getPackage().getName());
  • Log4j2官方文档翻译--欢迎使用Log4j2!

    千次阅读 2018-09-19 17:07:30
    官网原文标题《Welcome to Log4j 2!》 官网原文地址http://logging.apache.org/log4j/2.x/manual/index.html 译者:本文介绍了Log4j的发展历史。...几乎所有大型应用都有它自己log或者tracing A...
  • 本文完成python环境下,类似java的log4j实现,有下面需求 1、 日志输出到控制台,同时可以输出到文件 2、 日志文件可以在任何目录 如果不指定目录,则自动在当前目录下 3、 文件以当天日期为名称,同一天日志...
  • 就拿java来说,在早期的日志都是通过System.out.println()进行记录的,但是这种方式不便于管理,所以apache最先开发了首个日志框架:log4j; 为日志框架奠定了基础; 日志框架出现的历史顺序为 : log4j → JUL → ...
  • 通用日志(Log)模块设计

    千次阅读 2013-12-19 20:53:42
    在程序设计世界里,大大小小的项目都会有自己的日志模块。网络上有很多开源的Log模块,在这里算是以学习为目的重新造了一次轮子,也方便以后项目过程直接可以用上。  按照abused老师的说法,设计一个Log模块需要...
  • Graylog和ELK的简单对比

    千次阅读 2022-04-07 20:11:55
    3、自己开发采集日志的脚本,并用curl/nc发送到Graylog Server,发送格式是自定义的GELF,Flunted和Logstash都有相应的输出GELF消息的插件。自己开发带来很大的自由度。实际上只需要用inotify_wait监控日志的MODIFY...
  • 摘要:log4j远程代码漏洞问题被大范围曝光后已经有一段时间了,今天完整讲清JNDI和RMI以及该漏洞的深层原因。
  • log4j漏洞在于对"${}"的字样处理时可能存在异常,如果是攻击者精心设计的数据,将触发远程代码执行漏洞。 在编写代码简单测试后,我更加理解了这一漏洞... 当前项目用的是slf4j+logback的方案,在pom.xml中分析发现...
  • 文章目录一、为啥要使用第三方Log库,而不用平台自带的Log库二、Log4j系列库的功能介绍与基本概念三、Log4Qt库的基本介绍四、将Log4qt组装成为一个单独模块五、使用配置文件的方式配置Log4Qt六、使用代码的方式配置...
  • 自定义一个自己Log

    千次阅读 2018-04-01 00:18:21
    自定义一个自己Log 目录 自定义一个自己Log 目录 Android Log简介 来看看github中有哪些酷酷的Log开源库 XLog LogUtils logger 这些炫酷的Log的原理解析 logger的缺点 YxfLog简介 功能简介 使用 ...
  • log4rs日志库简析

    千次阅读 2017-10-18 11:31:59
    log4rs是rust实现的高度可配置日志库,该库配置的方式比较灵活,功能相对丰富,可以满足绝大部分的项目需要。本文给出了log4rs的用法示例与配置文件的说明,并简要叙述了log4rs的设计思路与组成......
  • log4j2 实际使用详解

    万次阅读 多人点赞 2017-05-12 19:31:07
    日志框架简单比较(slf4j、log4j、logback、log4j2 ) log4j2基础知识 log4j2实用配置 实战部分 slf4j + log4j2 实际使用 二、日志框架比较(slf4j、log4j、logback、log4j2 ) 日志接口(slf4j) slf4j是对所有日志...
  • Apache Log4j 远程代码注入漏洞

    千次阅读 2021-12-11 22:11:31
    2021年12月9日,Apache Log4j2 Java 日志模块存在远程命令执行漏洞可直接控制目标服务器问题,攻击者攻击难度极低。由于 Apache Log4j2 某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞...
  • Log4j2-Log4j 2介绍及使用

    万次阅读 2017-07-15 11:08:39
    Log4j 2 官网https://logging.apache.org/log4j/2.x/Log4j 2简介Log4j的1.x版本已经被广泛使用于很多应用程序中。然而,它这些年的发展已经放缓。它变得越来越难以维护,因为它需要严格遵循很老的Java版本,并在2015...
  • log4j2 的使用【超详细图文】

    万次阅读 多人点赞 2020-12-06 16:11:03
    Apache Log4j2 是对Log4j 的升级版本,参考了logback 的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有: 异常处理,在logback中,Appender中的异常不会被应用感知到,但是在log4j2中,...
  • Android中使用log4j2

    千次阅读 2021-01-05 17:52:35
    Log4j2 的配置、使用 ...log4j2参考了logback的一些优秀的设计,并且修复了一些问题,因此带来了一些重大的提升,主要有: 异常处理:在logback中,Appender中的异常不会被应用感知到,但是在log4j
  • Log4Shell是什么?从Log4j漏洞说起

    千次阅读 2022-01-04 14:36:57
    在开源Apache日志记录库Log4j中已发现了一个影响使用Java的设备和应用程序的新漏洞。该漏洞被称为Log4Shell,是目前互联网上最重大的安全漏洞,其严重程度为10分(满分10分),其影响有愈演愈烈之势。幸运的是,...
  • Log4j 2.8.2官方简介

    千次阅读 2017-05-04 19:49:33
    Apache Log4j 2 Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fix
  • 秒懂log4j1与log4j2的区别

    千次阅读 2021-03-27 20:41:09
    设计结构上比不上slf4j,在性能上比不上logback。于是apache对垂垂老矣的log4j进行一次重生,不是优化。虽然都叫log4j但是,他们是两个完全不同的东西,为了方便区分他们,给了两个别名:log4j1、log4j2。 slf4j的...
  • Log4j2异步日志核心流程和涉及的组件
  • spring引入log4j2日志框架

    千次阅读 2020-08-04 08:46:48
    log4j2是什么? log4j2可以用干什么?解决什么问题? log4j、slf4j、log4j2、logback之间的关系 相关配置说明 配置文件优先级 代码下载:https://gitee.com/hong99/spring/issues/I1N1DF 代码实现 项目代码...
  • 因此mysql设计了redo log,具体来说就是只记录事务对数据页做了哪些修改,这样就能完美地解决性能问题了(相对而言文件更小并且是顺序IO)。 redo log基本概念 redo log包括两部分:一个是内存中的日志缓冲(redo log ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 559,914
精华内容 223,965
关键字:

怎么设计自己的log