精华内容
下载资源
问答
  • xlog
    千次阅读
    2022-01-16 20:47:32

    对移动开发者来说,最头疼的莫过于线上出现问题,本地无法复现又没有任何日志的场景。但是考虑到应用性能和安全性,无法打印和保存过多的日志。颇有一种书到用时方恨少的感觉。

    一、xlog介绍

    xlog 是腾讯 Mars 终端基础组件中的通用日志模块,它有下面几个优点:

    • 使用mmap的方案进行日志写入,mmap 是使用逻辑内存对磁盘文件进行映射,中间只是进行映射没有任何拷贝操作,避免了写文件的数据拷贝。操作内存就相当于在操作文件,避免了内核空间和用户空间的频繁切换。提升了效率同时也保证了日志的完整性。下图是官方提供的写入效率对比图
      在这里插入图片描述
    • 日志支持加密,提高日志信息的安全性
    • 底层使用c++实现,支持Android和iOS平台
    • 支持设置单个日志文件的保存天数和大小,基本满足日常的开发需求

    二、xlog 使用

    参考官方提供的 demo

    2.1 引入xlog

     configurations {
     	//定义一个新的依赖方式
        cmake_depends
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        implementation 'com.android.support:appcompat-v7:26.1.0'
        testImplementation 'junit:junit:4.12'
        implementation "com.tencent.mars:mars-xlog:${VERSION_NAME}${VERSION_NAME_SUFFIX}"
        cmake_depends "com.tencent.mars:mars-xlog:${VERSION_NAME}${VERSION_NAME_SUFFIX}"
    }
    
    //获取xlog中的.so文件,用于ndk编译使用,如果项目中不包括ndk代码,可以不需要
    task resolveDependencies {
        project.configurations.each { configuration ->
            if ("cmake_depends".equalsIgnoreCase(configuration.name)) {
                def lib = configuration.resolve()[0]
                copy {
                    from zipTree(lib)
                    into "${project.rootDir}/${project.name}/src/main/jniLibs/"
                    include "jni/**/*.so"
                }
            }
        }
    }
    
    //build 依赖 resolveDependencies task
    build.dependsOn resolveDependencies
    

    导入xlog头文件
    在这里插入图片描述
    cmake 中 查找并链接 xlog 动态库

    cmake_minimum_required(VERSION 3.4.1)
    
    include_directories(export_include)
    
    add_library(native-lib SHARED native-lib.cpp)
    
    find_library(log-lib log)
    
    set(XLOG_PATH ../jniLibs/jni/${ANDROID_ABI}/)
    
    find_library(XLOG_LIB marsxlog PATHS ${XLOG_PATH} NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
    
    target_link_libraries(native-lib ${log-lib} ${XLOG_LIB})
    

    2.2 使用xlog

    初始化xlog

        static {
            System.loadLibrary("c++_shared");
            System.loadLibrary("marsxlog");
            System.loadLibrary("native-lib");
        }
    
    public void initLog(boolean isDebug) {
        String logPath = getExternalFilesDir(null).getPath() + "/logsample/xlog";
        Xlog xlog = new Xlog();
    //    xlog.setMaxFileSize(0, 1024*1024);
    //    xlog.setMaxAliveTime(0, 1);
        Log.setLogImp(xlog);
        Log.setConsoleLogOpen(true);
        Log.appenderOpen(isDebug ? Xlog.LEVEL_DEBUG : Xlog.LEVEL_INFO, Xlog.AppednerModeAsync, "",
            logPath, "LOGSAMPLE", 0);
      }
    

    主要是appenderOpen 方法:

    appenderOpen(int level, int mode, String cacheDir, String logDir, String nameprefix, int cacheDays)

    level:日志等级,如果使用xlog,日志等级就比较好控制,不需要再传递给openvpn debug开关
    mode: 写入的模式,支持同步和异步写入,同步写入可能会导致卡顿,release版本一定要异步写入
    cacheDir:设置缓存目录
    logDir:设置写入的文件目录,
    nameprefix:设置日志文件名的前缀,
    cacheDays:在多少天以后 从缓存目录移到日志目录 一般情况下填0即可

    xlog 使用

    直接使用xlog相关的api进行打印日志,日志会自动写入文件中。

            com.tencent.mars.xlog.Log.d(TAG, "test");
            com.tencent.mars.xlog.Log.e(TAG, "test");
    
    #include "xlogger/android_xlog.h"
    
    	LOGI("test", "111111111111");
    	LOGD("test", "111111111111");
    
    • 生成的日志文件
      在这里插入图片描述
      .xlog 文件是无法直接查看的,需要使用xlog对应的python脚本进行处理。
    python decode_mars_nocrypt_log_file.py 日志文件
    

    请添加图片描述
    转换后的日志内容:
    在这里插入图片描述

    xlog加解密

    • 生成加密秘钥,执行 python gen_key.py 命令
      在这里插入图片描述在这里插入图片描述
      其中 private key 是私钥,appender_open's parameter 是公钥。

    • 代码中设置公钥,初始化方式和上面有些差异

        public void initLog(boolean isDebug) {
            String logPath = getExternalFilesDir(null).getPath() + "/logsample/xlog";
    
    
            Xlog xlog = new Xlog();
            xlog.setMaxAliveTime(0, 24 * 60 * 60);
    
            Xlog.open(false, isDebug ? Xlog.LEVEL_DEBUG : Xlog.LEVEL_INFO, Xlog.AppednerModeAsync, "",
                logPath, "LOGSAMPLE", LOG_PUB_KEY);
    
            xlog.setMaxFileSize(0, 1024 * 120);
            xlog.setConsoleLogOpen(0, true);
            Log.setLogImp(xlog);
        }
    

    Log.appenderOpen() 方法没有 pubKey 参数,需要使用 Xlog.open() 方法初始化。

    • 修改解密脚本 decode_mars_crypt_log_file.py,替换刚才生成的私钥和公钥。
      在这里插入图片描述

    其他使用细节

    1. 由于使用异步模式,所以日志并不会实时同步到文件中,当执行日志上报操作时,需要调用 appenderFlush 方法将内存中的日志写入文件中,避免日志不完整。
    2. 单条日志大小限制16kb,超过16kb会被截取,目前没有其他解决方法,需要自行判断日志大小进行拆分写入。
    更多相关内容
  • xlog日志解密.zip

    2021-10-14 16:36:08
    xlog日志解密+鼠标右键配置文件
  • XLog A powerful log util for android 主要功能  *标记Log代码位置,(在android studio中可以点击快速定位)  *支持Kotlin  *支持Log打印监听  *支持JSON格式打印,自动格式化JSON 使用方法 1.在build.gradle ...
  • XLog . init( LogLevel . ALL ); 记录中 XLog . d( " hello xlog " ); 记录中 记录简单消息。 XLog . d(message); 用throwable记录一条消息,通常在引发异常时使用。 XLog . e(message, throwable); 还支持格式...
  • MM_20210429.xlog

    2021-05-05 07:47:03
    MM_20210429.xlog
  • xlog功能实时收集指定日志文件内容,发送到sources类型为netcat的flume端(理论上支持所有以socket形式监听的日志收集服务端)可以满足单个日志文件的实时收集,可以配置nginx每个项目一个日志文件,每个日志文件一个...
  • 腾讯XLog的文件解密,已封装成EXE文件,把EXE文件和xlog文件放到一个文件夹内双击解密即可,注意:这个是默认加密模式的,还有android用的jni 的So库
  • Android 微信高性能日志存储库Xlog的使用 Android Mars XLog的编译 我们大概了解了XLog,但是微信开源的XLog日志库,格式是写死的,如下图所示 那我们如果需要实现自定义的格式存储日志,该怎么办呢 ? 通过源码,...
  • XLog一个简易的日志打印框架(支持打印策略自定义,默认提供2种策略:logcat打印和磁盘打印)
  • 微信终端跨平台组件Mars系列高性能日志模块Xlog库文件
  • xLog-开源

    2021-07-18 11:10:27
    使用 PHP、XSLT、XUL 的面向博客的 CMS。
  • Android代码-XLog

    2019-08-06 17:30:58
    XLOG简介 自动保存LOG 过期删除LOG,控制LOG存储大小 自定义Crash操作 Gradle构建: compile 'com.sum.xlog:xlog:1.1.4' 初始化: XLogConfiguration.Builder builder = new XLogConfiguration.Builder(My...
  • imsdk_20210624.xlog

    2021-06-25 00:43:45
    imsdk_20210624.xlog
  • PUSH_20210501.xlog

    2021-05-07 15:14:44
    PUSH_20210501.xlog
  • PUSH_20210310.xlog

    2021-03-15 15:00:41
    PUSH_20210310.xlog
  • 微信高性能日志存储库XLog的自定义编译后的so库,使其支持自定义格式存储。 详见https://blog.csdn.net/EthanCo/article/details/104378841
  • xlog_v7so.zip

    2019-12-16 17:22:28
    详细 https://blog.csdn.net/j7a2son/article/details/103472219,这里下载的是v7a下的so库,和需要的Java代码。
  • Xlog加密使用指引配套资源:exe.zip和LogUtil.zip.
  • PUSH_20210426.xlog

    2021-05-07 14:12:34
    PUSH_20210426.xlog
  • MarsSample_20210520.xlog

    2021-05-20 15:45:33
    MarsSample_20210520.xlog
  • merchant_pushservice_20210711.xlog
  • 微信终端跨平台组件 mars 系列(一) - 高性能日志模块xlog 微信终端跨平台组件 mars 系列(一) - 高性能日志模块xlog
  • :warning: 检查 ,它是xlog的后继者。 HTTP处理程序记录器 xlog是感知HTTP应用程序的记录器。 与大多数记录器不同, xlog永远不会阻止您的应用程序,因为它的输出滞后。 日志命令通过缓冲通道连接到其输出,如果...
  • 实现客户端海量数据的高性能上报,上报过程不会block任何操作,实现高性能上报,基于腾讯Xlog的数据压缩和加密算法,实现数据的高压缩比压缩和加密存储。全新设计上报调度模块,策略调度完全由内部控制。 特点: 跨...
  • XLog 详解及源码分析

    2021-06-13 00:54:23
    一、前言这里的 XLog 不是微信 Mars 里面的 xLog,而是elvishew的xLog。感兴趣的同学可以看看作者 elvishwe 的官文史上最强的 Android 日志库 XLog。这里先过一下它的特点以及与其余日志库的比较。文章主要分析 xLog...

    一、前言

    这里的 XLog 不是微信 Mars 里面的 xLog,而是elvishew的xLog。感兴趣的同学可以看看作者 elvishwe 的官文史上最强的 Android 日志库 XLog。这里先过一下它的特点以及与其余日志库的比较。文章主要分析 xLog 中的所有特性的实现,以及作为一个日志工具,它实际的需求是什么。

    特点1.全局配置(TAG,各种格式化器...)或者基于单条日志的配置

    2.支持打印任意对象以及可自己设置的对象格式化器

    3.支持打印数组

    4.支持打印无限长的日志(没有 4K 字符的限制)

    5.XML 和 JSON 格式化输出

    6.线程信息(线程名等,可自己设置)

    7.调用栈信息(可配置的调用栈深度,调用栈信息包括类名、方法名文件名和行号)

    8.支持日志阻拦器

    9.保存日志文件(文件名和自动备份策略可灵活配置)

    10.在 Android Studio 中的日志样式美观

    11.简单易用,扩展性高

    与其余日志库的区别1.柔美的源代码,良好的文档

    2.扩展性高,可轻松扩展和强化功能

    3.轻量级,零依赖

    二、源码分析

    1.官文架构

    c1c1517efc74abf1dd2a371d54758daf.pngimage.png

    2.全局配置及其子组件详情// 日志输出样式配置LogConfiguration config = new LogConfiguration.Builder() .tag("MY_TAG") // 指定 TAG,默认为 "X-LOG" .t() // 允许打印线程信息,默认禁止 .st(2) // 允许打印深度为2的调用栈信息,默认禁止 .b() // 允许打印日志边框,默认禁止 .jsonFormatter(new MyJsonFormatter()) // 指定 JSON 格式化器,默认为 DefaultJsonFormatter .xmlFormatter(new MyXmlFormatter()) // 指定 XML 格式化器,默认为 DefaultXmlFormatter .throwableFormatter(new MyThrowableFormatter()) // 指定可抛出异常格式化器,默认为 DefaultThrowableFormatter .threadFormatter(new MyThreadFormatter()) // 指定线程信息格式化器,默认为 DefaultThreadFormatter .stackTraceFormatter(new MyStackTraceFormatter()) // 指定调用栈信息格式化器,默认为 DefaultStackTraceFormatter .borderFormatter(new MyBoardFormatter()) // 指定边框格式化器,默认为 DefaultBorderFormatter .addObjectFormatter(AnyClass.class, // 为指定类增加格式化器 new AnyClassObjectFormatter()) // 默认使用 Object.toString() .build();// 打印器Printer androidPrinter = new AndroidPrinter(); // 通过 android.util.Log 打印日志的打印器Printer SystemPrinter = new SystemPrinter(); // 通过 System.out.println 打印日志的打印器Printer filePrinter = new FilePrinter // 打印日志到文件的打印器 .Builder("/sdcard/xlog/") // 指定保存日志文件的路径 .fileNameGenerator(new DateFileNameGenerator()) // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log") .backupStrategy(new MyBackupStrategy()) // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024) .logFlattener(new MyLogFlattener()) // 指定日志平铺器,默认为 DefaultLogFlattener .build();

    全局配置主要是为了根据业务需求进行相关的配置。xLog 的配置可以分成 2 个大类别:日志的输出样式以及日志输出的打印器配置。

    LogConfiguration

    LogConfiguration 的构造用是 Builder 设计模式。对于属性配置类,一般因为会有比较多的配置项,并且一般都会设定其默认配置值,所以大多都会选择采用 Builder 设计模式。

    6726ea2fea03ab5351190e974e044e0a.pngLogConfiguration.jpg

    上图是一个在 Builder 设计模式下的严格定义,但一般情况下,假如只要要 builder 出一个 “产品”,那么完全不需要再笼统出一个 builder 接口,而是直接使用具体类型的 builder 就可。否则就会出现过度设计的问题。

    Formatter

    Formatter 主要是为少量常见的对象提供格式化的输出。XLog 中抽你了一个泛型接口 Formatter,其中的 format() 方法定义了输入一个数据/对象,对应将其格式化成一个 String 用于输出,中间的解决过程由各个子类自己完成。/** * A formatter is used for format the data that is not a string, or that is a string but not well * formatted, we should format the data to a well formatted string so printers can print them. * * @param the type of the data */public interface Formatter { /** * Format the data to a readable and loggable string. * * @param data the data to format * @return the formatted string data */ String format(T data);}

    如下是框架内定义的各类 Formatter:Object,Json,Border,Throwable,Xml,StackTrace,Thread 共 7 个接口,每个接口下又都提供了默认的具类 DefaultXXXFormatter。我们可以通过实现这 7 个接口,来定义自己的具类 Formatter,从而定义自己的输出格式,并通过LogConfiguration 相应的 xxxFormatter() 方法来控制 formatter。

    dc9078e2013cfbbd0ae0e45e1b48ee89.pngFormatter.jpg

    Printer

    Printer 的主要功能是控制日志的输出渠道,可以是 Android 的日志系统,控制台,也可以是文件。XLog 中笼统出了 Printer 接口,接口中的 println() 方法控制实际的输出渠道。** * A printer is used for printing the log to somewhere, like android shell, terminal * or file system. *

    * There are 4 main implementation of Printer. *
    {@link AndroidPrinter}, print log to android shell terminal. *
    {@link ConsolePrinter}, print log to console via System.out. *
    {@link FilePrinter}, print log to file system. *
    {@link RemotePrinter}, print log to remote server, this is empty implementation yet. */public interface Printer { /** * Print log in new line. * * @param logLevel the level of log * @param tag the tag of log * @param msg the msg of log */ void println(int logLevel, String tag, String msg);}

    如下是框架定义的各类 Printer,一共 5 个。其中 AndroidPrinter,FilePrinter,ConsolePrinter,RemotePrinter 可以看成单一可实际输出的渠道。而 PrinterSet 是包含了这些 Printer 的组合,其内部实现就是通过循环迭代每一个 printer 的 println() 方法,从而实现同时向多个渠道打印日志的功能。

    b75619de36d3a79a9e5341a1fc996d71.pngPrinter.jpg

    AndroidPrinter 调用了 Android 的日志系统 Log,并且通过分解 Log 的长度,按最大 4K 字节进行划分,从而突破 Android 日志系统 Log 对于日志 4K 的限制。

    FilePrinter 通过输出流将日志写入到文件,客户需要指定文件的保存路径、文件名的产生方式、备份策略以及清理策略。当然,对于文件的写入,是通过在子线程中进行的。如下分别是清理策略以及备份策略的定义。清理策略是当日志的存放超过肯定时长后进行清理或者者不清理。备份策略是当日志文件达到肯定大小后便将其备份,并产生一个新的文件以继续写入。

    aa47616674c1ed3b3abec27c2d972453.pngCleanStrategy.jpg

    f00766704081cc28f3786f0595071a62.pngBackupStrategy.jpg

    ConsolePrinter 通过 System.out 进行日志的输出

    RemotePrinter 将日志写入到远程服务器。框架内的实现是空的,所以这个其实是需要我们自己去实现。

    除了以上 4 个框架内定义好的 printer,客户还可以通过实现 Printer 接口实现自己的 printer。

    Flatter

    Flatter 的主要作用是在 FilePrinter 中将日志的各个部分(如time,日志 level,TAG,消息体)按肯定规则的衔接起来,组成一个新的字符串。需要注意的是框架现在提供的是 Flattener2,而原来的 Flattener 已经被标记为过时。/** * The flattener used to flatten log elements(log time milliseconds, level, tag and message) to * a single CharSequence. * * @since 1.6.0 */public interface Flattener2 { /** * Flatten the log. * * @param timeMillis the time milliseconds of log * @param logLevel the level of log * @param tag the tag of log * @param message the message of log * @return the formatted final log Charsequence */ CharSequence flatten(long timeMillis, int logLevel, String tag, String message);}

    框架里为我们定义了 2 个默认的 Flatter,DefaultFlattener 和 PatternFlattener,其类图如下。

    a405d9a326ab33be362b5f1de6f85011.pngFlattener2.jpg

    DefaultFlattener 默认的 Flattener 只是简单的将各部分进行拼接,中间用 “|” 连接。@Override public CharSequence flatten(long timeMillis, int logLevel, String tag, String message) { return Long.toString(timeMillis) + '|' + LogLevel.getShortLevelName(logLevel) + '|' + tag + '|' + message; }

    PatternFlattener 要略微复杂少量,其使用正则表达式规则对各部分进行适配再提取内容,其支持的参数如下。序号ParameterRepresents1{d}默认的日期格式 "yyyy-MM-dd HH:mm:ss.SSS"

    2{d format}指定的日期格式

    3{l}日志 level 的缩写. e.g: V/D/I

    4{L}日志 level 的全名. e.g: VERBOSE/DEBUG/INFO

    5{t}日志TAG

    6{m}日志消息体

    我们将需要支持的参数拼接到一个字串当中,而后由 PatternFlattener 将其进行分解并构造出对应的 **Filter,在其 flatten() 方法中,会通过遍历的方式讯问每个 filter 能否需要进行相应的替换。@Override public CharSequence flatten(long timeMillis, int logLevel, String tag, String message) { String flattenedLog = pattern; for (ParameterFiller parameterFiller : parameterFillers) { flattenedLog = parameterFiller.fill(flattenedLog, timeMillis, logLevel, tag, message); } return flattenedLog; }

    当然,除此之外,我们还可以定义自己的 Flatter,如作者所说,可以将其用于对 Log 的各个部分有选择的进行加密等功能。

    Interceptor

    interceptor 与 OkHttp 中 interceptor 有点相似,也同样采用了职责链的设计模式,其简要的类图如下。

    fae14081edbc099f35e5086417606969.pngInterceptor.jpg

    可以通过在构造 LogConfiguration 的时候,通过其 Builder 的 addInterceptor() 方法来增加 interceptor。对于每个日志都会通过遍历 Interceptor 进行解决,解决的顺序按照增加的先后顺序进行。而当某个 interceptor 的 intercept() 方法返回 null 则终止后面所有的 interceptor 解决,并且这条日志也将不会再输出。

    以上便是对 XLog 框架中所定义的子组件的简要分析,共包括:LogConfiguration,Formatter,Printer,Flatter,Interceptor。通过对整体框架的认识以及各个子组件的分析,从而使得我们可以熟知整个框架的基本功能。

    3.初始化

    XLog#init()

    经过全局配置后,便会调用 XLog#init() 方法进行初始化。//初始化XLog.init(LogLevel.ALL, // 指定日志级别,低于该级别的日志将不会被打印 config, // 指定日志配置,假如不指定,会默认使用 new LogConfiguration.Builder().build() androidPrinter, // 增加任意多的打印器。假如没有增加任何打印器,会默认使用 AndroidPrinter systemPrinter, filePrinter);

    init() 方法有多个重载的,我们仅看相关的就可。/** * Initialize log system, should be called only once. * * @param logConfiguration the log configuration * @param printers the printers, each log would be printed by all of the printers * @since 1.3.0 */ public static void init(LogConfiguration logConfiguration, Printer... printers) { if (sIsInitialized) { Platform.get().warn("XLog is already initialized, do not initialize again"); } sIsInitialized = true; if (logConfiguration == null) { throw new IllegalArgumentException("Please specify a LogConfiguration"); } // 记录下全局配置 sLogConfiguration = logConfiguration; // 将所有的 printer 汇合成一个 PrinterSet 集合 sPrinter = new PrinterSet(printers); // 初始化 Logger sLogger = new Logger(sLogConfiguration, sPrinter); }

    从上面的代码来看,其主要就是记录下了状态,及其 3 个静态变量 sLogConfiguration,sPrinter以及 sLogger,而 sLogConfiguration和sPrinter又拿来初始化了 sLogger,其关系如下类图所示。

    79d9208420fc64b585b0cb99d410ce6b.pngXLog.jpg

    Logger 类是日志中的核心类,其真正持有了 LogConfiguration 和 PrinterSet,并通过调度 LogConfiguration 和 PrinterSet 来进行日志的输出。

    4.日志的输出

    XLog#d(String, Throwable)

    这里以 XLog.d(String, Throwable) 这个方法来分析一下日志的打印,其余的过程上是相似的/** * Log a message and a throwable with level {@link LogLevel#DEBUG}. * * @param msg the message to log * @param tr the throwable to be log */ public static void d(String msg, Throwable tr) { assertInitialization(); sLogger.d(msg, tr); }

    再进一步看 Logger#d()/** * Log a message and a throwable with level {@link LogLevel#DEBUG}. * * @param msg the message to log * @param tr the throwable to be log */ public void d(String msg, Throwable tr) { println(LogLevel.DEBUG, msg, tr); }/** * Print a log in a new line. * * @param logLevel the log level of the printing log * @param msg the message you would like to log * @param tr a throwable object to log */ private void println(int logLevel, String msg, Throwable tr) { // 控制 debug level if (logLevel < logConfiguration.logLevel) { return; } // 将 Throwable 进行格式化,而后调用 printlnInternal()方法进行日志的输出。 printlnInternal(logLevel, ((msg == null || msg.length() == 0) ? "" : (msg + SystemCompat.lineSeparator)) + logConfiguration.throwableFormatter.format(tr)); }

    上面代码最终就是走到了 printlnInternal() 方法,这是一个私有方法,而不论前面是调用哪一个方法进行日志的输出,最终都要走到这个方法里面来。/** * Print a log in a new line internally. * * @param logLevel the log level of the printing log * @param msg the message you would like to log */ private void printlnInternal(int logLevel, String msg) { // 获取 TAG String tag = logConfiguration.tag; // 获取线程名称 String thread = logConfiguration.withThread ? logConfiguration.threadFormatter.format(Thread.currentThread()) : null; // 获取 stack trace,通过 new 一个 Throwable() 即可以拿到当前的 stack trace了。而后再通过设置的 stackTraceOrigin 和 stackTraceDepth 进行日志的切割。 String stackTrace = logConfiguration.withStackTrace ? logConfiguration.stackTraceFormatter.format( StackTraceUtil.getCroppedRealStackTrack(new Throwable().getStackTrace(), logConfiguration.stackTraceOrigin, logConfiguration.stackTraceDepth)) : null; // 遍历 interceptor,假如其中有一个 interceptor 返回了 null ,则丢弃这条日志 if (logConfiguration.interceptors != null) { LogItem log = new LogItem(logLevel, tag, thread, stackTrace, msg); for (Interceptor interceptor : logConfiguration.interceptors) { log = interceptor.intercept(log); if (log == null) { // Log is eaten, don't print this log. return; } // Check if the log still healthy. if (log.tag == null || log.msg == null) { throw new IllegalStateException("Interceptor " + interceptor + " should not remove the tag or message of a log," + " if you don't want to print this log," + " just return a null when intercept."); } } // Use fields after interception. logLevel = log.level; tag = log.tag; thread = log.threadInfo; stackTrace = log.stackTraceInfo; msg = log.msg; } // 通过 PrinterSet 进行日志的输出,在这里同时也解决了日志能否需要格式化成边框形式。 printer.println(logLevel, tag, logConfiguration.withBorder ? logConfiguration.borderFormatter.format(new String[]{thread, stackTrace, msg}) : ((thread != null ? (thread + SystemCompat.lineSeparator) : "") + (stackTrace != null ? (stackTrace + SystemCompat.lineSeparator) : "") + msg)); }

    代码相比照较简单,主要的步骤也都写在注释里面,就不再逐个形容了。至此,XLog 的主要框架也基本分析完了。同时,也感谢作者无私的开源精神,向我们分享了一个如此简单但很优秀的框架。

    三、后记

    感谢你能读到并读完此文章。希望我的分享能够帮助到你,假如分析的过程中存在错误或者者疑问都欢迎留言探讨。

    展开全文
  • ios-Xlog的使用.zip

    2019-07-11 18:52:59
    Xlog的使用封装。
  • XLog

    千次阅读 2017-09-29 16:20:24
    XLog English 简单、美观、强大、可扩展的 Android 和 Java 日志库,可同时在多个通道打印日志,如 Logcat、Console 和文件。如果你愿意,甚至可以打印到远程服务器(或其他任何地方)。 XLog 能干什么...

    XLog

    English

    简单、美观、强大、可扩展的 Android 和 Java 日志库,可同时在多个通道打印日志,如 Logcat、Console 和文件。如果你愿意,甚至可以打印到远程服务器(或其他任何地方)。

    XLog 能干什么:

    • 全局配置(TAG,各种格式化器...)或基于单条日志的配置
    • 支持打印任意对象以及可自定义的对象格式化器
    • 支持打印数组
    • 支持打印无限长的日志(没有 4K 字符的限制)
    • XML 和 JSON 格式化输出
    • 线程信息(线程名等,可自定义)
    • 调用栈信息(可配置的调用栈深度,调用栈信息包括类名、方法名文件名和行号)
    • 支持日志拦截器
    • 保存日志文件(文件名和自动备份策略可灵活配置)
    • 在 Android Studio 中的日志样式美观
    • 简单易用,扩展性高

    与其他日志库的不同:

    • 优美的源代码,良好的文档
    • 扩展性高,可轻松扩展和强化功能
    • 轻量级,零依赖

    依赖

    compile 'com.elvishew:xlog:1.3.0'

    预览

    • 带线程信息、调用栈信息和边框的日志

    * 格式化后的网络 API 请求

    * 格式化后的网络 API 响应

    * 日志文件

    架构

    用法

    初始化

    简单方式

    XLog.init(LogLevel.ALL);

    或者如果你想要在正式版中禁止打日志

    XLog.init(BuildConfig.DEBUG ? LogLevel.ALL : LogLevel.NONE);
    

    高级方式

    LogConfiguration config = new LogConfiguration.Builder()
        .logLevel(BuildConfig.DEBUG ? LogLevel.ALL             // 指定日志级别,低于该级别的日志将不会被打印,默认为 LogLevel.ALL
            : LogLevel.NONE)
        .tag("MY_TAG")                                         // 指定 TAG,默认为 "X-LOG"
        .t()                                                   // 允许打印线程信息,默认禁止
        .st(2)                                                 // 允许打印深度为2的调用栈信息,默认禁止
        .b()                                                   // 允许打印日志边框,默认禁止
        .jsonFormatter(new MyJsonFormatter())                  // 指定 JSON 格式化器,默认为 DefaultJsonFormatter
        .xmlFormatter(new MyXmlFormatter())                    // 指定 XML 格式化器,默认为 DefaultXmlFormatter
        .throwableFormatter(new MyThrowableFormatter())        // 指定可抛出异常格式化器,默认为 DefaultThrowableFormatter
        .threadFormatter(new MyThreadFormatter())              // 指定线程信息格式化器,默认为 DefaultThreadFormatter
        .stackTraceFormatter(new MyStackTraceFormatter())      // 指定调用栈信息格式化器,默认为 DefaultStackTraceFormatter
        .borderFormatter(new MyBoardFormatter())               // 指定边框格式化器,默认为 DefaultBorderFormatter
        .addObjectFormatter(AnyClass.class,                    // 为指定类添加格式化器
            new AnyClassObjectFormatter())                     // 默认使用 Object.toString()
        .addInterceptor(new BlacklistTagsFilterInterceptor(    // 添加黑名单 TAG 过滤器
            "blacklist1", "blacklist2", "blacklist3"))
        .addInterceptor(new MyInterceptor())                   // 添加一个日志拦截器
        .build();
    
    Printer androidPrinter = new AndroidPrinter();             // 通过 android.util.Log 打印日志的打印器
    Printer consolePrinter = new ConsolePrinter();             // 通过 System.out 打印日志到控制台的打印器
    Printer filePrinter = new FilePrinter                      // 打印日志到文件的打印器
        .Builder("/sdcard/xlog/")                              // 指定保存日志文件的路径
        .fileNameGenerator(new DateFileNameGenerator())        // 指定日志文件名生成器,默认为 ChangelessFileNameGenerator("log")
        .backupStrategy(new NeverBackupStrategy()              // 指定日志文件备份策略,默认为 FileSizeBackupStrategy(1024 * 1024)
        .logFlattener(new MyFlattener())                       // 指定日志平铺器,默认为 DefaultFlattener
        .build();
    
    XLog.init(                                                 // 初始化 XLog
        config,                                                // 指定日志配置,如果不指定,会默认使用 new LogConfiguration.Builder().build()
        androidPrinter,                                        // 添加任意多的打印器。如果没有添加任何打印器,会默认使用 AndroidPrinter(Android)/ConsolePrinter(java)
        consolePrinter,
        filePrinter);

    对于 android,做初始化的最佳地方是 Application.onCreate()

    全局用法

    XLog.d("Simple message")
    XLog.d("My name is %s", "Elvis");
    XLog.d("An exception caught", exception);
    XLog.d(object);
    XLog.d(array);
    XLog.json(unformattedJsonString);
    XLog.xml(unformattedXmlString);
    ... // 其他全局使用

    局部用法

    创建一个 Logger

    Logger partial = XLog.tag("PARTIAL-LOG")
        ... // 其他配置
        .build();

    然后对该 Logger 进行局部范围的使用,所有打印日志的相关方法都跟 XLog 类里的一模一样。

    partial.d("Simple message 1");
    partial.d("Simple message 2");
    ... // 其他局部使用

    基于单条日志的用法

    进行基于单条日志的配置,然后就可以直接打印日志了,所有打印日志的相关方法都跟 XLog 类里的一模一样。

    XLog.t()    // 允许打印线程信息
        .st(3)  // 允许打印深度为3的调用栈信息
        .b()    // 允许打印日志边框
        ...     // 其他配置
        .d("Simple message 1");
    
    XLog.tag("TEMP-TAG")
        .st(0)  // 允许打印不限深度的调用栈信息
        ...     // 其他配置
        .d("Simple message 2");
    
    XLog.nt()   // 禁止打印线程信息
        .nst()  // 禁止打印调用栈信息
        .d("Simple message 3");
    
    XLog.b().d("Simple message 4");

    比较

    让我们设想有一个 JSON 字符串和一个 XML 字符串:

    String jsonString = "{\"name\": \"Elvis\", \"age\": 18}";
    String xmlString = "<team><member name="Elvis"/><member name="Leon"/></team>";

    Android Log

    Log.d(TAG, "Message");
    Log.d(TAG, String.format("Message with argument: age=%s", 18));
    Log.d(TAG, jsonString);
    Log.d(TAG, xmlString);
    Log.d(TAG, "Message with stack trace info", new Throwable());

    XLog

    XLog.init(LogLevel.ALL);
    XLog.d("Message");
    XLog.d("Message with argument: age=%s", 18);
    XLog.json(jsonString);
    XLog.xml(xmlString);
    XLog.st(5).d("Message with stack trace info");

    带边框的 XLog

    XLog.init(LogLevel.ALL, new LogConfiguration.Builder().b().build());
    XLog.d("Message");
    XLog.d("Message with argument: age=%s", 18);
    XLog.json(jsonString);
    XLog.xml(xmlString);
    XLog.st(5).d("Message with stack trace info");

    类似的库

    兼容性

    为了兼容 Android Log,XLog 支持 Android Log 里的所有方法。 请看 XLog 里的 Log 类。

    Log.v(String, String);
    Log.v(String, String, Throwable);
    Log.d(String, String);
    Log.d(String, String, Throwable);
    Log.i(String, String);
    Log.i(String, String, Throwable);
    Log.w(String, String);
    Log.w(String, String, Throwable);
    Log.wtf(String, String);
    Log.wtf(String, String, Throwable);
    Log.e(String, String);
    Log.e(String, String, Throwable);
    Log.println(int, String, String);
    Log.isLoggable(String, int);
    Log.getStackTraceString(Throwable);

    迁移

    如果你有一个大工程正在用 Android Log,并且很难将所有对 Android Log 的使用都换成 XLog,那么你可以使用兼容 API,简单地将所有 'android.util.Log' 替换成 'com.elvishew.xlog.XLog.Log'. (为了更好的性能,尽量不要使用兼容 API)

    Linux/Cygwin:

    grep -rl "android.util.Log" <your-source-directory> | xargs sed -i "s/android.util.Log/com.elvishew.xlog.XLog.Log/g"

    Mac

    grep -rl "android.util.Log" <your-source-directory> | xargs sed -i "" "s/android.util.Log/com.elvishew.xlog.XLog.Log/g"

    Android Studio

    在 'Project' 窗口里,切换到 'Project Files' 标签,然后右键点击你的源码目录。 在出现的菜单里,点击 'Replace in Path...' 选项。 在弹出的对话框里,'Text to find' 区域填上 'android.util.Log','Replace with' 区域填个 'com.elvishew.xlog.XLog.Log',然后点击 'Find'。

    待开发

    •  打印任意对象: XLog.d(Object) (v1.1.0 开始支持)
    •  支持日志拦截器(类似于 okhttp 的 Interceptor,这里用来拦截日志)(v1.3.0 开始支持)
    •  添加 tags 过滤器 (白名单过滤和黑名单过滤)(v1.3.0 开始支持)
    •  添加 PatternLogFlattener(主要用在向文件打印日志时),如: 使用模式 "{d yyyy-MM-dd hh:mm:ss.SSS} {l}/{t}: {m}",平铺后的日志将会是 "2016-10-30 13:00:00,000 W/my_tag: Simple message" (v1.3.0 开始支持)
    •  打印日志到文件时,采用异步方式(v1.3.0 开始支持)
    •  Logger 粒度的日志级别控制,取代当前的全局控制(v1.3.0 开始支持)
    •  为 Bundle 和 Intent 对象添加默认的格式化器(v1.4.0 开始支持)
    •  导出日志文件为 .zip(v1.4.0 开始支持)

    版本

    最新版本:1.3.0 Change log

    问题

    如果你在使用过程中遇到任何问题或者有任何建议,请创建一个 Issue。

    注意:麻烦使用英文提问和回复,方便其他国家的用户看懂并从中受益,谢谢。

    感谢

    感谢 Orhan Obut 的 logger,它让我知道一个日志库能做哪些事情。

    感谢 Serge Zaitsev 的 log,让我想到要兼容 Android Log

    许可

    Copyright 2016 Elvis Hew
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
       http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
    

    关于我

    展开全文
  • xLog:简单、美观、强大、可扩展的 Android 和 Java 日志库,可同时在多个通道打印日志,如 Logcat、Console 和文件。
  • XLOG 2.0 预备知识 《PostgreSQL重启恢复—XLOG 1.0》 概述 在《PostgreSQL重启恢复—XLOG》中,我们查询的XLOG的组织结构、XLOG写入的基本流程,但是忽略了一些有意思的细节。现在我们来调试一遍全流程,在调试的...

    XLOG 2.0

    预备知识

    PostgreSQL重启恢复—XLOG 1.0

    概述

    在《PostgreSQL重启恢复—XLOG》中,我们查询的XLOG的组织结构、XLOG写入的基本流程,但是忽略了一些有意思的细节。现在我们来调试一遍全流程,在调试的过程中印证《PostgreSQL重启恢复—XLOG》中的描述,同时更加深入的分析PostgreSQL XLOG的写入。此外,本章我们还需要解决一个问题,就是PostgreSQL如何应对partial write。所以,在调试之前,我们需要先阐述什么是partial write,PostgreSQL如何解决partial write。然后再在调试中加以印证。

    partial write

    如果一个页面在落盘的过程中,数据库发生了崩溃,那么这个页面就可能出现一部分落盘,而一部分没有落盘的情况,也就是部分写(partial write)。对于一个部分写的页面,我们是没办法用XLOG来恢复的。这是什么原因呢?

    在上一篇文章中, 我们说过,PostgreSQL采用的是物理逻辑日志。物理逻辑是不具备幂等性的,也就是说我们用这样的日志执行一次redo,页面中就会多一条元组,执行多次就存在多条相同的元组,所以对于物理逻辑日志的redo,我们需要确保两点:

    • 对于已经落盘的元组,不执行redo。
    • 对于没有落盘的元组,只执行一次redo。

    为了达到这个目标,我们引入了一个叫做LSN的概念,LSN(Log Sequence Number),日志序列号。LSN有两个特性:

    • 每一条XLOG都有一个LSN。
    • LSN是一个单调递增的整数,日志本身也是顺序写的,这和LSN的特性高度契合。

    在heap_insert中,我们可以看到这样3个步骤:

    1. 将一条元组写入数据页
    2. 将XLOG写入log buffer,得到LSN
    3. 将LSN记录到页面头

    重要的是第三步,每一个页面都记录了一个LSN,称为Page LSN。Page LSN表示,所有LSN小于等于Page LSN的XLOG对应的操作都已经落盘。那么在重启恢复时,所有LSN小于Page LSN的XLOG都不会在该页面中做redo操作。但是这一切都必须有一个前提,那就是页面必须完成落盘。如果发生partial write,那么就会出现块头正确落盘,而块数据没有正确落盘。从而无法保证Page LSN之前的所有操作都正确落盘,当然也就无法恢复了。

    为了解决这个问题,PostgreSQL提供备份区块的方式。这种方式的思路是,对于checkpoint 之后,页面的第一次修改,会在 XLOG中记录页面的全部数据。这样我们就可以借助WAL的特性,来实现页面的原子写。

    WAL的一个特性是,事务的相关日志全部落盘,事务才可以提交。对于PostgreSQL来说,只有正常提交事务才需要redo。备份区块方案确保了一旦事务正常提交,那么一定可以从redo log中获取到一个完整的页面数据。

    上面的描述,涉及的知识点有点多,其中PostgreSQL的checkpoint我们还没有讲到,所以可能不太好理解。这里我们只需要记住:在特定的情况下,XLOG中会存放一整个数据页,在接下来的内容里,我们会阐述数据页是如何写入XLOG的。

    流程调试

    接下来,我们就正式进入我们的流程调试。我们首先新建一张表:

    drop table if exists test;
    create table test(a int);
    

    然后在heap_insert上打上断点,接着执行一条insert语句:

    insert into test values(1);
    

    元组插入

    在这里插入图片描述

    现在便执行到了heap_insert函数。图中显示的几个函数,这就是用于插入元组的函数:

    • heap_prepare_insert

      对插入的元组进行一些预处理:填充元组头、执行toast操作等。

    • RelationGetBufferForTuple

      获取一个用于执行插入操作的数据页

    • RelationPutHeapTuple

      实际写入数据

    写XLOG

    执行完元组插入操作后,就需要产生一条与插入相关的XLOG,然后将该XLOG写入log buffer。

    在这里插入图片描述

    现在即将开始与XLOG相关的事宜。

    细节补充—对于insert new block的XLOG处理

    这里补充一个细节,回顾下我们的用例。我们创建了一张新表,然后立即向表中写入了一条数据。由于是新表,所以在写入之前,还没有为表分配数据页。而在插入执行的过程中,会首先分配一个新的数据页,然后向这个新的数据页中写入元组。由于数据页是新分配的,所以就可能出现数据页在落盘之前,数据库就崩溃了。重启后发现根本没有这个数据页。这该怎么恢复?联系前面partial write中介绍的备份区块技术,针对新分配块的insert操作,是不是也应该将整个块数据都写入XLOG呢?其实大可不必,毕竟将一个块写入XLOG是很占空间的事情。我们需要做的只是在XLOG中给这个页面一个标识,表示这个页面可能需要重新分配并初始化,代码如下:

    在这里插入图片描述
    XLOG_HEAP_INIT_PAGE就表示数据页是一个新分配的页,在重启恢复时需要重新初始化。会被存放到info中,而info最终会被存放到XLogRecord的xl_info成员中。我们在《XLOG 1.0》中并没有展开讲解xl_info。这里我们就看到了xl_info的一个作用。xl_info的高4位是用来表示该XLOG对应的是什么操作,具体有如下操作:

    /*
     * WAL record definitions for heapam.c's WAL operations
     *
     * XLOG allows to store some information in high 4 bits of log
     * record xl_info field.  We use 3 for opcode and one for init bit.
     */
    #define XLOG_HEAP_INSERT		0x00
    #define XLOG_HEAP_DELETE		0x10
    #define XLOG_HEAP_UPDATE		0x20
    /* 0x030 is free, was XLOG_HEAP_MOVE */
    #define XLOG_HEAP_HOT_UPDATE	0x40
    #define XLOG_HEAP_CONFIRM		0x50
    #define XLOG_HEAP_LOCK			0x60
    #define XLOG_HEAP_INPLACE		0x70
    
    #define XLOG_HEAP_OPMASK		0x70
    /*
     * When we insert 1st item on new page in INSERT, UPDATE, HOT_UPDATE,
     * or MULTI_INSERT, we can (and we do) restore entire page in redo
     */
    #define XLOG_HEAP_INIT_PAGE		0x80
    

    这些操作可以用“|”运算符连接起来。对于上面的用例,当前就应该有两个操作XLOG_HEAP_INSERT和XLOG_HEAP_INIT_PAGE。
    在这里插入图片描述有了XLOG_HEAP_INIT_PAGE标识后,我们在重启恢复时,如果发现这个标识就需要将页面重新初始化。核心代码如下:
    在这里插入图片描述

    初始化完毕后,正常redo插入操作即可

    注册数据

    接下来,我们应该注册我们需要写入XLOG的各项数据。

    构建并注册xl_heap_insert

    首先是构建和注册xl_heap_insert,xl_heap_insert用于标识元组在页面中的逻辑位置(ItemIdData的下标)。
    在这里插入图片描述

    构建并注册元组

    接下来,会构建并注册一条写入XLOG的元组,其中包括两个部分:xl_heap_header(简化版元组头)+实际元组数据。由于XLOG是page-oriented log,所以在注册元组之前需要注册数据页。
    在这里插入图片描述

    构建XLOG并写入log buffer

    完成注册流程后,就需要构建XLOG,然后写入log buffer中。

    在这里插入图片描述

    现在我们进入XLogInsert函数
    在这里插入图片描述

    XLogRecordAssemble构建XLOG

    在这里插入图片描述

    在XLOG 1.0中,我们讲过在XLogRecordAssemble中,我们需要构建XLOG头部,然后将XLOG的4个部分链成链表。在调试流程中,我们来看看具体是怎么做的。首先rdt_datas_last变量表示链头,我们将其初始化为hdr_rdt:
    在这里插入图片描述

    然后,将hdr_scratch存放到链表头,如此,就将XLOG头部插入了链表头,注意此时并没有向hdr_scratch中写入数据,也就是说此时还没有完成XLOG头的构建。

    XLOG头部 +xl_heap_header + 实际元组数据 + xl_heap_insert

    蓝色表示尚未完成构建但已加入链表、红色表示尚加入链表、绿色表示已加入链表)

    然后,PostgreSQL在XLOG头部buffer中为XLogRecord预留了空间,但是并未向XLogRecord中写入任何值。(XLogRecord在函数最后才进行赋值)

    接下来,PostgreSQL并没有构建XLOG头部,而是遍历registered_buffers数组,然后将元组数据加入链表:
    在这里插入图片描述
    在这里插入图片描述

    首先,我们需要注意needs_backup和needs_data这两个变量,needs_backup表示是否需要备份区块,当前的用例并不需要备份区块。而needs_data表示是否有数据需要写入XLOG,显然这里是需要的。于是将regbuf中的data链表加入XLogRecData链表。回顾下《XLOG 1.0》注册阶段,注册时已经将元组头和实际元组数据注册到了regbuf的data链表中,所以上述操作就将xl_heap_header+ 实际元组数据加入了XLogRecData链表。

    XLOG头部 +xl_heap_header + 实际元组数据 + xl_heap_insert

    接下来,就开始构建XLOG头部了,但是在真正构建之前,PostgreSQL做了一个优化,判断当前的regbuf与前一个regbuf是否属于同一张表,为什么需要这个判断?
    在这里插入图片描述
    我们来回顾下XLOG头部的组成:XLogRecord + XLogRecordBlockHeader + RelFileNode+BlockNumber+mainrdata_len,其中RelFileNode表示数据写到了哪张表。那么如果两条数据都写入同一张表,那么后面的数据的XLOG头中其实就不需要RelFileNode。

    遗留问题1

    samerel什么时候会为ture?或者说registered_buffers什么时候会存在多个regbuf?目前还不清楚!但是值得注意的是,对于批量插入,比如:

    insert into test values(1),(2);
    insert into test (select * from test1);
    

    是通过多次调用heap_insert来实现的,而一次heap_insert只一条元组,所以XLOG的组织和insert单条元组没有区别。

    接下来开始正式构建XLOG头。在line493是已经在XLOG头部buffer中为XLogRecord预留了空间,现在要做的事是将XLOG头部的XLogRecordBlockHeader + RelFileNode+BlockNumber写入XLOG头部。
    在这里插入图片描述

    循环结束后将mainrdata_len写入XLOG头部,同时将mainrdata的实际数据,也就是之前注册的xl_heap_insert加入链表
    在这里插入图片描述

    此时链表中已经存放了XLOG的大部分数据:

    XLOG头部 +xl_heap_header + 实际元组数据 + xl_heap_insert

    XLOG头部还剩下XLogRecord还未构建。

    接下来是计算XLOG头部的长度,以及整个xlog的长度。

    在这里插入图片描述
    最后构建XLogRecord。

    在这里插入图片描述

    至此,XLOG的4个部分已经构建完成,且全部加入链表。

    XLOG头部 +xl_heap_header + 实际元组数据 + xl_heap_insert

    在《XLOG 1.0》中我们将过,这4个部分的长度分别为:46+5+5+3。我们现在来验证一下:
    在这里插入图片描述

    可以到hdr_rdt链表确实有4个元组,长度分别为46+5+5+3,总长度为59。

    至此,XLOG构建完毕,下一步就是要把XLOG写入log buffer。

    XLogInsertRecord

    现在我们来到XLogInsertRecord函数,向log buffer中写入通过XLogRecordAssemble组装好的XLOG。

    在这里插入图片描述

    在XLogInsertRecord函数中,会首先调用ReserveXLogInsertLocation为即将写入的XLOG分配空间,然后调用CopyXLogRecordToWAL将XLOG的写入log buffer。在《XLOG 1.0》中我们并没有详细讲解ReserveXLogInsertLocation,现在来看看ReserveXLogInsertLocation中一些比较精妙的地方。

    ReserveXLogInsertLocation

    ReserveXLogInsertLocation有1个传入参数和三个传出参数

    在这里插入图片描述

    传入参数size为XLOG的长度,此处为59。对于三个传出参数:

    • StartPos

      日志的插入偏移。

    • EndPos

      日志的结束偏移,EndPos = StartPos + size。这个位置非常重要,因为他会作为最终的page lsn存放到数据页面头部。所以请注意,page lsn用的是EndPos而不是StartPos。因为page lsn表示所有大于page lsn的XLOG都需要在该页面中进行redo。

    • PrevPtr

      前一条日志的偏移,上面的EndPos就会作为下一条日志的PrevPtr。

    在PostgreSQL中XLOG是顺序写入的,PostgreSQL使用了一个全局的XLogCtlInsert结构体对象来记录日志的写入位置。其中CurrBytePos成员表示日志的当前写入位置,用CurrBytePos+size就可以得到日志的结束位置。然后将这个结束位置作为下一条日志的PrevBytePos存放到XLogCtlInsert结构体中。由于XLogCtlInsert是一个全局对象,所以在获取和修改其中成员时,需要加锁,这里直接使用自旋锁。具体代码如下:

    在这里插入图片描述

    经过这段代码,我们得到了三个位置:startbytepos、endbytepos、prevbytepos。这三个位置,实际上是三个逻辑位置。这是一个很精妙的设计,我们现在来慢慢讲解。

    从上述代码中,不难看出CurrBytePos每次都递增一个XLOG日志的大小size。这种方式给程序员提供了一个很好的抽象,仿佛xlog buffer中只会存放一条一条的XLOG。但实际上并不是这样,log buffer是按照段页式进行管理。log buffer被分为多个段(segment)每个段又被分为多个页面(page)。其结构如下:

    在这里插入图片描述

    摘自《PostgreSQL指南:内幕探索》

    一个WAL段文件的默认大小为16MB,并且其内部被划分成大小为8KB的多个页面。第一个页面包含了由XLogLongPageHeaderData定义的首部数据,其他页面包含了由XLogPageHeaderData定义的首部数据。

    而startbytepos、endbytepos、prevbytepos实际上是除开所有管理信息后的偏移。所以这三个偏移不能直接使用,需要转换为物理偏移,代码如下:

    在这里插入图片描述

    我们来看看XLogBytePosToRecPtr的实现:

    /*
     * Converts a "usable byte position" to XLogRecPtr. A usable byte position
     * is the position starting from the beginning of WAL, excluding all WAL
     * page headers.
     */
    static XLogRecPtr
    XLogBytePosToRecPtr(uint64 bytepos)
    {
    	uint64		fullsegs;
    	uint64		fullpages;
    	uint64		bytesleft;
    	uint32		seg_offset;
    	XLogRecPtr	result;
    
        /*
         * fullsegs:XLOG有多个段,所以需要先确定bytepos应该对应第几个段
         * bytesleft:确定了bytepos属于第几个段后,就需要确定段内偏移,bytesleft只是一个逻辑偏移
         *
         * UsableBytesInSegment是一个段里面的有效载荷,
         * 更通俗的说就是一个段里面除去管理信息剩下的空间大小
         */
    	fullsegs = bytepos / UsableBytesInSegment;		
    	bytesleft = bytepos % UsableBytesInSegment;
    
        /*
         * 现在需要将bytesleft转换成一个物理偏移seg_offset
         * 转换的方式是确定bytesleft是段内的第几个块,以及块内偏移,然后加上块管理信息
         * 段中第一个页面的组织结构和其他页面不太一样(第一个页面放的是XLogLongPageHeaderData,
         * 其他是XLogPageHeaderData),所以第一个页面需要单独处理
         */
    	if (bytesleft < XLOG_BLCKSZ - SizeOfXLogLongPHD)
    	{
    		/* 
    		 * fits on first page of segment 
    		 * 段内的第一个块,直接加上块头SizeOfXLogLongPHD(XLogLongPageHeaderData的大小)
    		 */
    		seg_offset = bytesleft + SizeOfXLogLongPHD;
    	}
    	else
    	{
    		/* account for the first page on segment with long header */
    		seg_offset = XLOG_BLCKSZ; //由于肯定不在第一个页面上,所以起始偏移就是第一个页面的大小
    		bytesleft -= XLOG_BLCKSZ - SizeOfXLogLongPHD;	//去掉第一个页面中可用空间有效载荷大小
            												//XLOG_BLCKSZ - SizeOfXLogLongPHD
    		
            /*
             *	计算剩余的bytesleft属于哪个页面,以及页内偏移
             *  UsableBytesInPage是页面内的有效载荷
             */
    		fullpages = bytesleft / UsableBytesInPage;
    		bytesleft = bytesleft % UsableBytesInPage;
    
            //加上管理信息,得到最终的段内偏移
    		seg_offset += fullpages * XLOG_BLCKSZ + bytesleft + SizeOfXLogShortPHD;
    	}
    
        /*
         *  #define XLogSegNoOffsetToRecPtr(segno, offset, dest) \
    	 *			(dest) = (segno) * XLOG_SEG_SIZE + (offset)
    	 *
    	 * 	根据前面计算出的段号和段内偏移计算出最终的物理偏移
    	 */	
        
    	XLogSegNoOffsetToRecPtr(fullsegs, seg_offset, result);
    
    	return result;
    }
    
    

    XLogBytePosToRecPtr的实现细节详见上述代码的注释。

    这样的实现方式,除了可以提供很好的抽象。更重要的是,可以提高并发性。不难发现XLogBytePosToRecPtr函数的实现其实还挺复杂的,运算操作比较多。但是对于全局XLogCtlInsert对象Insert中的CurrBytePos、PrevBytePos成员的修改却非常简单,临界区非常小。

    在这里插入图片描述

    通过ReserveXLogInsertLocation,我们得到了日志的插入偏移结束偏移前一个日志的偏移。接下来,我们就要使用这几个偏移来完成XLOG的写入。

    CopyXLogRecordToWAL

    在这里插入图片描述

    CopyXLogRecordToWAL有5个传入参数:

    • write_len

      XLOG长度,此处为59

    • isLogSwitch

      尚不清楚

    • rdata

      由XLogRecordAssemble组装的XLOG数据

    • StartPos

      XLOG写入位置

    • EndPos

      XLOG结束位置

    CopyXLogRecordToWAL有两个重要的步骤:

    1. 调用GetXLogBuffer获取XLOG在log buffer中的插入指针currpos。
    2. 将XLOG写入currpos开始的buffer中。

    我们首先来简单看下GetXLogBuffer:

    在这里插入图片描述

    在GetXLogBuffer,首先需要判断插入位置,对应的buffer page是否是当前缓存的page,如果是就直接返回插入的位置指针。GetXLogBuffer函数涉及到具体XLOG如何写入和落盘,比较复杂,在后面会有专门文档来讲解。这里只是简单了解下。

    找到了写入位置后,就开始真正的写入:
    在这里插入图片描述

    这部分在《XLOG1.0》中已经讲到过,唯一需要注意的就是,line1333的while循环,当XLOG长度大于当前page可用空间时,需要先将XLOG的一部分写入当前page,然后再获取下一个page。

    XLogInsertRecord返回

    CopyXLogRecordToWAL返回,即XLOG写入完成,最后将EndPos返回,作为page lsn。

    在这里插入图片描述

    page lsn设置

    XLOG写入完成,并返回EndPos之后,需要将EndPos写入页面头部,作为page lsn。

    在这里插入图片描述

    至此,完成了从元组插入到XLOG写入的整个流程。

    备份区块

    现在,我们已经完成了XLOG的主要流程,最后再来看看备份区块的处理。为了解决partial write,PostgreSQL对于checkpoint 之后,页面的第一次修改,会在 XLOG中记录页面的全部数据。我们现在来调试下这个流程。为了掩饰checkpoint之后的重做日志操作,我们需要先手动checkpoint。用例如下:

    drop table if exists test;
    create table test(a int);
    insert into test values(1);
    CHECKPOINT;	-- 手动checkpoint
    insert into test values(2);
    

    我们需要调试的是line5的insert发生了什么,而这个insert会经历一个比较“诡异”的流程。我们来看看他如何诡异。

    首先,在XLogRecordAssemble中会判断,是否需要备份区块,而我们得到的结果如下:

    在这里插入图片描述

    这里显示不需要备份区块。所以XLogRecordAssemble的执行和前面非备份区块的流程完全一致。然后,我们就到了XLogInsertRecord函数的line1010:

    在这里插入图片描述

    此时,会进行一个判断,然后直接返回InvalidXLogRecPtr。在外层循环中如果EndPos的值为InvalidXLogRecPtr,就会继续循环。

    在这里插入图片描述

    于是我们就第二次进入了XLogRecordAssemble。再次判断是否需要备份区块,这次的答案是需要

    在这里插入图片描述

    这个“诡异”的流程具体是什么情况,牵扯的东西比较多,所以这里先不讲解。只需要知道,经过一个流程之后,发现需要备份区块。由于区块都已经备份了,那么通常情况下就不需要单独备份数据了。

    在这里插入图片描述

    现在,就走进了备份区块的流程。

    在这里插入图片描述

    现在我们提一个问题,然后带着问题来看代码。问题是备份区块真的就会备份一整个数据页么?我们先来看看下面很有意思的注释:

    在这里插入图片描述

    对于REGBUF_STANDARD类型的页面,需要calculate its hole;而对于非标准的页面需要eliminate hole。那么这个hole是什么。回顾下《PostgreSQL 基础模块—表和元组组织方式》中讲解的PostgreSQL页面的组织结构。在一个数据页面中,页面的空闲区是位于一个页面的中间。这个就是这里的说的hole。我们在备份区块的时候,没有必要把这个空闲区也备份起来,只需要记录空闲区的位置,在恢复的时候可以把空闲区恢复出来就好了。这样有效的减少了XLOG的大小。所以备份区块实际上是备份一个数据页中的有效载荷

    在这里插入图片描述

    页面结构图

    下面,我们来看看具体是什么做的,由于我们需要备份区块,那么按照《XLOG 1.0》中所阐述的,在我们的XLOG头部,就需要存放XLogRecordBlockImageHeader,定义如下:

    typedef struct XLogRecordBlockImageHeader
    {
    	uint16		length;			/* number of page image bytes */
    	uint16		hole_offset;	/* number of bytes before "hole" */
    	uint8		bimg_info;		/* flag bits, see below */
    
    	/*
    	 * If BKPIMAGE_HAS_HOLE and BKPIMAGE_IS_COMPRESSED, an
    	 * XLogRecordBlockCompressHeader struct follows.
    	 */
    } XLogRecordBlockImageHeader;
    

    这个结构体非常简单,其中

    • length

      表示备份区块的大小,注意不是hole的大小,而是page size - hole size。

    • hole_offset

      hole的偏移

    • bimg_info

      标志位,在当前的场景下为BKPBLOCK_HAS_IMAGE。

    下面,就是设置XLogRecordBlockImageHeader的hole_offset成员,以及计算hole的大小并存放于hole_length中。

    在这里插入图片描述

    上面的页面结构图展示了hole的概念,所以这里的代码应该不难理解。接下来我们从regbuf获取一个临时的XLogRecData。

    在这里插入图片描述

    在registered_buffer中,提供了两个临时的XLogRecData,其实就是分别用于存放备份区块的两个部分数据的,第一部分是:page head + item data,第二部分是tuples.

    在这里插入图片描述

    接下来是设置bimg_info

    在这里插入图片描述

    最后就是实际数据的备份了

    在这里插入图片描述

    所以,在备份区块的场景下,XLOG的组织结构为:

    XLogRecord+XLogRecordBlockHeader+XLogRecordBlockImageHeader+ RelFileNode+BlockNumber+mainrdata_len 51字节
    +
    page head + data item 视具体数据长度而定,当前用例5字节
    +
    tuples 视具体数据长度而定,当前用例为5字节
    +
    xl_heap_insert 3字节

    我们来验证一下最后的返回值:

    在这里插入图片描述

    遗留问题

    1. 如何获取写入XLOG的buffer page,即GetXLogBuffer的详细流程。
    2. 如何判断是否需要备份区块,即前面说的“诡异”流程究竟是怎么回事。

    我们将在下一个章节中来详细讨论这两个问题。

    XLogRecord+XLogRecordBlockHeader+XLogRecordBlockImageHeader+ RelFileNode+BlockNumber+mainrdata_len 51字节
    +
    page head + data item 视具体数据长度而定,当前用例5字节
    +
    tuples 视具体数据长度而定,当前用例为5字节
    +
    xl_heap_insert 3字节

    我们来验证一下最后的返回值:

    在这里插入图片描述

    遗留问题

    1. 如何获取写入XLOG的buffer page,即GetXLogBuffer的详细流程。
    2. 如何判断是否需要备份区块,即前面说的“诡异”流程究竟是怎么回事。

    我们将在下一个章节中来详细讨论这两个问题。

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,523
精华内容 3,409
关键字:

xlog

友情链接: Stm32Dma.rar