文章目录

  • 为何建议程序员在项目中使用第三方日志库
  • 第三方日志库plog的优势
  • plog主要头文件及使用方法解释
    • plog/Log.h
    • plog/Appenders/ColorConsoleAppender.h
    • plog/Appenders/RollingFileAppender.h
    • plog/Formatters/TxtFormatter.h
  • plog三个核心概念
    • Formatter:格式化程序
      • 格式化程序举例
        • 1、TxtFormatter
        • 2、JsonFormatter
        • 3、CsvFormatter
        • 4、SyslogFormatter
    • Appender:输出器
      • 输出器举例
        • 1、ConsoleAppender
        • 2、RollingFileAppender
        • 3、DailyRollingFileAppender
        • 4、UdpAppender
        • 5、SyslogAppender
        • 6、NullAppender
    • Logger:记录器
      • 使用指定级别记录器过滤日志示例1(支持多个记录器输出,每个记录器单独配置输出方式)
      • 使用指定级别记录器过滤日志示例2(最常用最简单用法,只需初始化一次,其他源文件导入头文件即可用)(★★★★★)
  • 使用方法
    • 简单入门
      • 单个源文件打印日志
        • 日志输出内容解析`2023-02-27 10:19:20.384 DEBUG [5461] [main@11] Hello log!`
      • 多个源文件打印到同一日志文件(只需初始化一次)
      • 多个源文件打印到不同日志文件(multiple loggers)
      • 日志打印到多个输出器(multiple appenders)
      • 输出彩色日志到控制台
  • 其他说明
    • 当代码中包含多个初始化文件时,调用plog怎么知道输出到哪个初始化文件?
    • 当plog::init()函数中的maxFileSize和maxFiles参数任一为0,取消滚动打印功能(它们默认就是0)
    • 延迟流评估是什么?(Lazy stream evaluation)
    • 自动捕获'this'指针功能有什么用?

因为合作方cw提供的库里有个plog,所以查询一下plog的使用方法

为何建议程序员在项目中使用第三方日志库

使用第三方日志库可以带来以下好处:

  • 功能丰富:第三方日志库通常提供丰富的功能,例如多种日志级别、多种输出格式、日志旋转、日志压缩等等。

  • 可扩展性:第三方日志库通常具有良好的可扩展性,可以自定义日志级别、输出格式等。

  • 易于维护:使用第三方日志库可以降低自己维护日志代码的难度,减少错误。

  • 跨平台:第三方日志库通常具有跨平台的特性,可以在多个平台上使用。

第三方日志库plog的优势

  • 高效:plog 的设计重点是高效性能,它能够快速记录大量的日志,并支持多线程环境下的并发写入。

  • 可扩展性:plog 提供了灵活的日志记录方式,可以自定义日志记录格式、输出位置等。

  • 跨平台:plog 支持多种平台,包括 Linux、Windows、macOS 等,可以轻松在不同平台上使用。

  • 易于集成:plog 的代码量小,使用简单,容易集成到项目中。

plog主要头文件及使用方法解释

plog/Log.h

这是 plog 库的核心头文件,其中定义了 plog::Logger 类和一些与日志相关的函数和宏定义。使用这个头文件可以满足大部分的日志需求。

plog/Appenders/ColorConsoleAppender.h

这个头文件定义了一个彩色控制台输出的日志附加器,可以将日志以彩色输出到控制台。如果你需要在控制台上打印带有颜色的日志,可以使用这个头文件。

plog/Appenders/RollingFileAppender.h

这个头文件定义了一个滚动文件输出的日志附加器,可以将日志写入到滚动的日志文件中。如果你需要将日志保存到文件中,并且希望文件不会无限制增长,可以使用这个头文件。

plog/Formatters/TxtFormatter.h

这个头文件定义了一个简单的文本格式化器,可以将日志以文本格式输出。如果你不需要复杂的日志格式,可以使用这个头文件。

你可以根据自己的需要选择引入相应的头文件,例如:

#include <plog/Log.h>  // 使用 plog 的核心功能
#include <plog/Appenders/ColorConsoleAppender.h>  // 如果需要彩色输出
#include <plog/Appenders/RollingFileAppender.h>   // 如果需要输出到滚动文件
#include <plog/Formatters/TxtFormatter.h>          // 如果需要简单文本格式输出

一般情况下,只需要引入 plog/Log.h 头文件即可使用 plog 的基本功能,如果需要额外的功能,再引入相应的头文件即可。

plog三个核心概念

Formatter:格式化程序

用于将日志消息转换为特定格式的字符串,以便记录和输出。plog提供了几种内置格式化程序,如TXT、CSV、FuncMessage和MessageOnly,用户还可以自定义自己的格式化程序。

格式化程序举例

除了下面几种 Formatter,Plog 还提供了一些其他的 Formatter,如 BsonFormatter、XMLFormatter 等。用户可以根据实际需求选择适合的 Formatter 进行使用。

1、TxtFormatter

TxtFormatter 是 Plog 的默认 Formatter。它将日志信息格式化为一个文本字符串,并添加一些元信息(如时间戳、日志级别等)。TxtFormatter 格式化的文本字符串可以被写入文本文件或控制台输出。TxtFormatter 可以通过设置不同的参数(如是否包含时间戳、是否包含线程 ID 等)进行定制化。

2、JsonFormatter

JsonFormatter 将日志信息格式化为 JSON 格式的字符串。它可以让日志信息更方便地与其他系统进行交互。JsonFormatter 的输出结果可以被写入文本文件或控制台输出。JsonFormatter 可以通过设置不同的参数(如是否包含时间戳、是否包含线程 ID 等)进行定制化。

3、CsvFormatter

CsvFormatter 将日志信息格式化为 CSV(Comma-Separated Values)格式的字符串。它可以让日志信息更方便地进行导出和分析。CsvFormatter 的输出结果可以被写入文本文件或控制台输出。CsvFormatter 可以通过设置不同的参数(如是否包含时间戳、是否包含线程 ID 等)进行定制化。

4、SyslogFormatter

SyslogFormatter 将日志信息格式化为 Syslog 协议规定的格式。Syslog 是一种常见的日志协议,可以让日志信息被发送到远程 syslog 服务器。SyslogFormatter 可以通过设置不同的参数(如 Syslog 服务器地址、Syslog 设备标识符等)进行定制化。

Appender:输出器

用于将格式化后的日志消息输出到目标位置,例如控制台、文件、系统日志等。plog提供了几种内置输出器,如RollingFile、Console、ColorConsole、Android、EventLog、DebugOutput和DynamicAppender,用户还可以自定义自己的输出器。

输出器举例

每个Appender都有自己的优点和用途。ConsoleAppender可用于快速测试和调试,RollingFileAppender可用于将日志记录保留在磁盘上,DailyRollingFileAppender可用于按日期存档日志记录,UdpAppender可用于将日志记录发送到远程服务器,而SyslogAppender可用于与类UNIX系统的syslog集成。

可以根据需要使用这些Appender中的任意一个或多个。通常,您可以在一个应用程序中同时使用多个Appender。例如,您可以将日志记录输出到文件和控制台,或将日志记录发送到远程服务器和syslog。

1、ConsoleAppender

将日志输出到控制台,支持彩色输出。

2、RollingFileAppender

将日志记录输出到文件,并自动轮转日志文件。支持按文件大小、文件数和日期自动轮转。

3、DailyRollingFileAppender

将日志记录输出到文件,并根据日期轮转日志文件。

4、UdpAppender

将日志记录输出到UDP socket。

5、SyslogAppender

将日志记录输出到syslog(适用于类UNIX系统)。

6、NullAppender

不将日志记录输出到任何地方。

Logger:记录器

plog支持多个日志级别,可以使用不同级别的记录器输出不同级别的日志。在plog中,有以下几个预定义的日志级别(按照严重程度递增排列):

  • verbose:最低级别的日志,通常用于输出详细的调试信息。
  • debug:用于调试,输出程序的运行状态信息。
  • info:输出普通信息。
  • warning:输出警告信息。
  • error:输出错误信息。
  • fatal:最高级别的日志,输出致命错误信息,通常会导致程序崩溃。

在使用plog时,可以通过设置记录器的级别来控制输出的日志级别。例如,如果只想输出warning级别及以上的日志,可以将记录器的级别设置为warning,这样所有warning、error和fatal级别的日志都会被输出,而info、debug和verbose级别的日志则会被忽略。

使用指定级别记录器过滤日志示例1(支持多个记录器输出,每个记录器单独配置输出方式)

#include <plog/Log.h>
#include "plog/Initializers/RollingFileInitializer.h"enum // Define log instances. Default is 0 and is omitted from this enum.
{debugLogger = 1,infoLogger = 2
};int main() {// 创建文件输出的记录器plog::RollingFileAppender<plog::TxtFormatter> fileAppender("log.txt", 1024 * 1024, 5);// 创建debug级别的记录器,同时将日志输出到文件plog::init<debugLogger>(plog::debug, &fileAppender);// 创建info级别的记录器,同时将日志输出到文件plog::init<infoLogger>(plog::info, &fileAppender);//注:info级别比debug高// 1. 使用debug级别的记录器输出debug级别的日志PLOG_DEBUG_(debugLogger) << "PLOG_DEBUG message >> debugLogger";// 2. 使用info级别的记录器输出debug级别的日志PLOG_DEBUG_(infoLogger) << "PLOG_DEBUG message >> infoLogger";  //没输出到文件// 3. 使用debug级别的记录器输出info级别的日志PLOG_INFO_(debugLogger) << "PLOG_INFO message >> debugLogger";// 4. 使用info级别的记录器输出info级别的日志PLOG_INFO_(infoLogger) << "PLOG_INFO message >> infoLogger";return 0;
}

编译运行结果:

使用指定级别记录器过滤日志示例2(最常用最简单用法,只需初始化一次,其他源文件导入头文件即可用)(★★★★★)

#include <plog/Log.h>
#include "plog/Initializers/RollingFileInitializer.h"int main()
{plog::init(plog::warning, "logfile.txt");PLOG_VERBOSE << "verbose log";          // 不会输出PLOG_DEBUG << "debug log";              // 不会输出PLOG_INFO << "info log";                // 不会输出PLOG_WARNING << "warning log";          // 会输出PLOG_ERROR << "error log";              // 会输出PLOG_FATAL << "fatal log";              // 会输出return 0;
}

编译运行结果:

使用方法

简单入门

下载源码:https://github.com/SergiusTheBest/plog/releases/tag/1.1.9

单个源文件打印日志

然后在系统上新建文件夹,把上面include拷贝进去,目录结构大致这样:

然后源文件和编译文件长这样(源文件代码我是从上面README.md拷的):

(testPlog.cpp)

#include <plog/Log.h> // Step1: include the headers    //引入头文件
#include "plog/Initializers/RollingFileInitializer.h"int main()
{plog::init(plog::debug, "Hello.txt"); // Step2: initialize the logger //初始化logger// Step3: write log messages using a special macro  //使用特殊宏写入日志消息// There are several log macros, use the macro you liked the most  //有几个日志宏,请使用您最喜欢的宏PLOGD << "Hello log!"; // short macro  //短宏PLOG_DEBUG << "Hello log!"; // long macro   //长宏PLOG(plog::debug) << "Hello log!"; // function-style macro  //函数样式宏// Also you can use LOG_XXX macro but it may clash with other logging libraries  //也可以使用LOG_XXX宏,但它可能与其他日志库冲突(建议不用下面几种,除非有特殊情况)LOGD << "Hello log!"; // short macroLOG_DEBUG << "Hello log!"; // long macroLOG(plog::debug) << "Hello log!"; // function-style macroreturn 0;
}

(build.sh)

g++ -std=c++14 testPlog.cpp -I./include

给build.sh添加可执行权限后,运行它编译,多出个a.out文件:

./build.sh

然后运行a.out,目录下多出个日志文件:


查看Hello.txt内容:

日志输出内容解析2023-02-27 10:19:20.384 DEBUG [5461] [main@11] Hello log!

plog输出的内容“2023-02-27 10:19:20.384 DEBUG [5461] [main@11] Hello log!" 可以解释为:

  • 2023-02-27 10:19:20.384 是记录日志的时间戳,格式为“年-月-日 时:分:秒.毫秒”。
  • DEBUG 是日志的级别,表示这是一条 DEBUG 级别的日志。
  • [5461] 表示当前线程的 ID,用方括号括起来。
  • [main@11] 表示当前函数的名称和行号,用方括号括起来。在这个示例中,函数名是 main,行号是 11。
  • Hello log! 是实际的日志内容,即我们调用 LOG_DEBUG 输出的字符串。

多个源文件打印到同一日志文件(只需初始化一次)

目录结构:

(testPlog.cpp)

#include <plog/Log.h> // Step1: include the headers
#include "plog/Initializers/RollingFileInitializer.h"#include "myFunc.h"int main()
{plog::init(plog::debug, "Hello.txt"); // Step2: initialize the logger// Step3: write log messages using a special macro// There are several log macros, use the macro you liked the mostPLOGD << "Hello log!"; // short macroPLOG_DEBUG << "Hello log!"; // long macroPLOG(plog::debug) << "Hello log!"; // function-style macrofunc();return 0;
}

(myFunc.cpp)

#include "myFunc.h"int func()
{PLOGD << "Hello log!"; // short macroPLOG_DEBUG << "Hello log!"; // long macroPLOG(plog::debug) << "Hello log!"; // function-style macroreturn 0;
}

(myFunc.h)

#ifndef FUNCTION_H_
#define FUNCTION_H_#include <plog/Log.h>int func();#endif // FUNCTION_H_

(build.sh)

g++ -std=c++14 *.cpp -I./include

还是跟上面一样,./build.sh && ./a.out编译运行,打开结果日志文件Hello.txt查看结果:

多个源文件打印到不同日志文件(multiple loggers)

这里没用多个源文件哈,但效果也差不多,初始化时,将枚举值作为参数传入,打印时也将枚举值带上,就能输出到指定文件了

(testPlog.cpp)

//
// MultiInstance - shows how to use multiple logger instances, each instance has its own independent configuration.
//#include <plog/Log.h>
#include <plog/Initializers/RollingFileInitializer.h>enum // Define log instances. Default is 0 and is omitted from this enum.
{SecondLog = 1
};int main()
{plog::init(plog::debug, "MultiInstance-default.txt"); // Initialize the default logger instance.plog::init<SecondLog>(plog::debug, "MultiInstance-second.txt"); // Initialize the 2nd logger instance.// Write some messages to the default log.PLOGD << "Hello default log!";PLOG_DEBUG << "Hello default log!";PLOG(plog::debug) << "Hello default log!";// Write some messages to the 2nd log.PLOGD_(SecondLog) << "Hello second log!";PLOG_DEBUG_(SecondLog) << "Hello second log!";PLOG_(SecondLog, plog::debug) << "Hello second log!";return 0;
}

(build.sh)

g++ -std=c++14 *.cpp -I./include

编译运行:

./build.sh

结果:

日志打印到多个输出器(multiple appenders)

目录结构:

(testPlog.cpp)

#include <plog/Log.h> // Step1: include the headers
#include "plog/Initializers/RollingFileInitializer.h"
#include "plog/Initializers/ConsoleInitializer.h"int main()
{static plog::RollingFileAppender<plog::CsvFormatter> fileAppender("MultiAppender.csv", 8000, 3); // Create the 1st appender.static plog::RollingFileAppender<plog::TxtFormatter> fileAppender2("MyTxtFormatter.txt", 8000, 3); // Create the 2st appender.static plog::ConsoleAppender<plog::TxtFormatter> consoleAppender; // Create the 3nd appender.plog::init(plog::debug, &fileAppender).addAppender(&fileAppender2).addAppender(&consoleAppender); // Initialize the logger with the appenders.PLOGD << "Hello log!"; // short macroPLOG_DEBUG << "Hello log!"; // long macroPLOG(plog::debug) << "Hello log!"; // function-style macroreturn 0;
}

(build.sh)

g++ -std=c++14 *.cpp -I./include

编译运行:

./build.sh

结果:

可以看到,相同的日志被输出到了多个地方,1是打印到了控制台,2是MultiAppender.csv文件,3是MyTxtFormatter.txt

输出彩色日志到控制台

//
// ColorConsole - shows how to use a color console appender.
//#include <plog/Log.h>
#include <plog/Init.h>
#include <plog/Formatters/TxtFormatter.h>
#include <plog/Appenders/ColorConsoleAppender.h>int main()
{static plog::ColorConsoleAppender<plog::TxtFormatter> consoleAppender;plog::init(plog::verbose, &consoleAppender);// Log severity levels are printed in different colors.PLOG_VERBOSE << "This is a VERBOSE message";PLOG_DEBUG << "This is a DEBUG message";PLOG_INFO << "This is an INFO message";PLOG_WARNING << "This is a WARNING message";PLOG_ERROR << "This is an ERROR message";PLOG_FATAL << "This is a FATAL message";return 0;
}

编译运行结果:

其他说明

当代码中包含多个初始化文件时,调用plog怎么知道输出到哪个初始化文件?

当在多个源文件中使用PLOGD等宏时,plog会自动查找最近的plog::init初始化函数并将日志写入该函数指定的日志文件。如果在多个源文件中使用相同的plog::init初始化函数,则所有日志消息都将写入同一个文件。

在plog库中,日志输出器的初始化函数是通过C++静态初始化的方式进行注册的。在程序启动时,这些初始化函数会被按照它们的定义顺序执行,将日志输出器注册到全局日志器中。在编译时,编译器会将所有的初始化函数按照它们的定义顺序加入到可执行文件的初始化段(.init_array),在程序运行时会自动执行这些函数。

当程序执行到某个PLOG宏的时候,plog会从当前函数所在的源文件向上遍历所有调用栈帧,查找是否有已经注册的日志输出器,如果有,则使用最近的一个输出器进行输出。这个查找过程并不会通过文件系统来实现,而是通过静态初始化过程中生成的初始化段来完成。因此,plog不会查找文件系统来确定最近的日志输出器,而是通过静态链接关系来查找。如果没有找到已注册的日志输出器,则会使用默认的输出器。

因此,可以在不同的源文件中通过不同的初始化函数来初始化不同的日志输出器,并在运行时通过PLOG宏来输出日志,plog会根据静态链接关系找到最近的输出器来进行输出。

根据我的测试,当代码中包含多个初始化文件时,而打印时又没指定是哪一个,打印会把所有文件,都输出一次

当plog::init()函数中的maxFileSize和maxFiles参数任一为0,取消滚动打印功能(它们默认就是0)

Logger& init(Severity maxSeverity, const char/wchar_t* fileName, size_t maxFileSize = 0, int maxFiles = 0);
  • maxFileSize - the maximum log file size in bytes(单个日志文件最大字节数)
  • maxFiles - a number of log files to keep(滚动打印维持的最大日志文件数量)

延迟流评估是什么?(Lazy stream evaluation)

延迟流评估(Lazy stream evaluation)是一种日志记录技术,可以将日志消息的格式化延迟到实际需要输出消息时再进行,以提高性能和降低资源占用。

在使用延迟流评估技术时,当用户使用日志记录器记录一条消息时,不会立即将消息转换为字符串格式,而是将消息的内容保存到一个缓存区中,直到真正需要输出消息时,才将缓存区中的内容格式化为字符串,然后输出到目标位置。

使用延迟流评估技术可以有效减少不必要的字符串格式化操作,从而提高程序的性能和响应速度。同时,由于延迟流评估只在需要输出消息时才进行格式化,因此可以避免无效的日志记录对系统性能和资源的影响。

在plog日志库中,延迟流评估是一种可选功能,用户可以根据需要选择是否启用它。默认情况下,plog启用延迟流评估,并提供了相应的支持。

自动捕获’this’指针功能有什么用?

自动捕获’this’指针是一种日志记录技术,用于自动记录当前对象的指针,以便在日志消息中输出有关对象的信息,从而更好地跟踪和调试程序。

在使用自动捕获’this’指针技术时,当用户使用日志记录器记录一条消息时,plog库会自动捕获当前对象的指针,并将其添加到日志消息中,以便用户在查看日志时能够识别与对象相关的消息,并更好地理解程序的行为和状态。

自动捕获’this’指针技术可以帮助用户更好地跟踪和调试程序,尤其是在多线程环境下或对象较多的复杂程序中。同时,由于自动捕获’this’指针是自动完成的,因此可以减少用户在代码中插入日志记录语句的工作量,提高开发效率。

需要注意的是,自动捕获’this’指针只在支持C++11的编译器中受支持,而且只有在MSVC编译器下才能进行自动捕获。在其他编译器或平台下,用户需要手动指定对象指针并将其添加到日志消息中。

参考文章:C/C++ plog日志简单用法

C++plog库,轻量级日志框架(日志库)相关推荐

  1. 2016 年 50 个最佳的轻量级 JavaScript 框架和库一

    2016 年 50 个最佳的轻量级 JavaScript 框架和库 IT程序狮 · 1 天前 回顾今年已发布的 JS 框架和库,我们针对地筛选了一些能够提供直接和具体功能的免费 JavaScript ...

  2. java常用日志框架日志门面及实现 SLF4J 、Jboss-logging 、JCL、Log4j、Logback、Log4j2、JUL,springboot集成 log4j、log4j2

    java常用日志框架日志门面SLF4J .Jboss-logging .JCL.Log4j及实现 Logback.Log4j2.JUL,springboot集成 log4j.log4j2 .logba ...

  3. 日志门面和日志框架(日志实现框架log4j2)

    一.log4j2简介 Apache Log4j 2是对Log4j的升级,是最优秀的java日志框架. 二.log4j2特征 性能提升:Log4j2包含基于LMAX Disruptor库的下一代异步记录 ...

  4. Java日志框架日志门面介绍

    文章目录 一.日志 二.常见日志框架 历史 各大框架介绍 JUL Log4j(1999-2015) Logback(2006-?) Log4j2 Logback与Log4j2对比 三.日志门面 什么是 ...

  5. Java日志框架 -- 日志框架介绍、日志门面技术、JUL日志(JUL架构、JUL入门示例、JUL日志级别、JUL日志的配置文件)

    1. 日志的概念 日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志.具有处理历史数据.诊断问题的追踪以及理解系统的活动等重要作用. 2. Java日志框架 问题: 控制日志输出的内容 ...

  6. 一文详解 C++ 日志框架

    作者: 一去.二三里 个人微信号: iwaleon 微信公众号: 高效程序员 日志框架 日志框架 - 一个经过专门设计的实用程序,用于规范应用程序的日志记录过程. 日志框架可以自己编写(需要一定的能力 ...

  7. requirednew基于xml配置日志不回滚_Mybatis 系列 4:引入日志框架

    为什么要用日志? 我们以前要看一个信息,一般使用的是 System.out.println 来打印的,额,这种方式比较 low,需要在代码里各种写 System.out.println,是会受到鄙视的 ...

  8. Spring boot日志框架

    日志框架 日志的抽象层 日志的实现层 Spring boot底层,是Spring框架 Spring框架,默认使用JCL Spring boot 使用的SLF4J和Logback 日志门面,SLF4J ...

  9. 学java日志框架,看这一篇就够了!!!

    什么是日志框架 日志框架的选择 Logback的使用与配置 什么是日志框架 是一套能实现日志输出的工具包 能够描述系统运行状态的所有时间都可以算作日志 日志框架的能力 定制输出目标 定制输出格式 携带 ...

  10. 可能是全网最全,JAVA日志框架适配、冲突解决方案,可以早点下班了!

    点击上方"Java基基",选择"设为星标" 做积极的人,而不是积极废人! 每天 14:00 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java ...

最新文章

  1. swift3.0提示框新用法
  2. datax 高级_GitHub - xhhx55/DataX
  3. python-之基本语法
  4. Activiti数据库
  5. 牛客网-剑指offer 第一题(二维数组中的查找)
  6. 设置window代理的命令
  7. 装饰者模式 php,php装饰者模式简单应用案例分析
  8. Redis的持久化策略
  9. 批量裁剪或延伸的lisp程序_10 行 Python 代码,批量压缩图片 500 张,简直太强大了...
  10. 大地测量学笔记 : 高斯克吕格投影
  11. 15分钟搭建自己的博客
  12. 增加收入的销售 OKR 案例 – 用这些 OKR 范例来设定你的销售团队目标
  13. 配置 PO SLD步骤
  14. WORD邮件合并打印EXCEL数据制作大量奖证、奖状、准考证、成绩单、明信片、信封等个人报表
  15. 智能驾驶 车牌检测和识别(二)《YOLOv5实现车牌检测(含车牌检测数据集和训练代码)》
  16. node.js运行js_Node.js运行时v8选项列表
  17. 微服务架构-测试理解
  18. 微软Win10 KB5012117更新安装失败 出现了“0x800f0922”的错误
  19. 打印机可以打印不能扫描怎么弄_打印机可以打印不能扫描怎么设置
  20. 基于微信小程序平台实现二手物品交易小程序设计【附项目源码】

热门文章

  1. 25岁转行做软件测试晚不晚?
  2. 我的世界服务器传送时间修改,我的世界改变时间天气和传送的指令介绍
  3. html隐藏换行,css强制换行和超出隐藏实现
  4. CheatSheet for mac 1.2.7 热键快捷键管理工具
  5. 重发老文:DOS游戏编程二十一条
  6. JS 实现驼峰式转下横线,下横线转驼峰式4
  7. 手把手教你JAVA如何连接MYSQL-mysql-connector-j-8.0.32.jar
  8. 考研复试问什么?(一)
  9. 市场经销必备图文工具,推荐几个值得收藏的
  10. 保护您的眼睛 用淡苹果绿的IE背景色