C++项目实战——基于多设计模式下的同步异步日志系统-⑪-日志器管理类与全局建造者类设计(单例模式)

news2024/12/24 21:06:41

文章目录

  • 专栏导读
  • 日志器建造者类完善
  • 单例日志器管理类设计思想
  • 单例日志器管理类设计
  • 全局建造者类设计
  • 日志器类、建造者类整理
  • 日志器管理类测试

专栏导读

🌸作者简介:花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。

🌸专栏简介:本文收录于 C++项目——基于多设计模式下的同步与异步日志系统

🌸相关专栏推荐:C语言初阶系列C语言进阶系列C++系列数据结构与算法Linux

在这里插入图片描述

日志器建造者类完善

实现了异步日志器设计之后,将异步日志器添加到日志器建造者类当中。

// 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
//  1.设置日志器类型
//  2.将不同类型的日志器的创建放到同一个日志器建造者类中完成
enum class LoggerType
{
    LOGGER_SYNC,
    LOGGER_ASYNC
};
class LoggerBuilder
{
public:
    LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
                      _limit_level(LogLevel::value::DEBUG),
                      _looper_type(AsyncType::ASYNC_SAFE)
    {}
    void buildLoggerType(LoggerType type) { _logger_type = type; }
    void buildEnableUnSafeAsync() { _looper_type = AsyncType::ASYNC_UNSAFE; }
    void buildLoggerName(const std::string &name) { _logger_name = name; }
    void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
    void buildFormatter(const std::string &pattern)
    {
        _formatter = std::make_shared<Formatter>(pattern);
    }
    template <typename SinkType, typename... Args>
    void buildSink(Args &&...args) // 由用户自己决定落地方式
    {
        LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
        _sinks.push_back(psink);
    }
    virtual Logger::ptr build() = 0;
protected:
    AsyncType _looper_type;
    LoggerType _logger_type;
    std::string _logger_name;
    std::atomic<LogLevel::value> _limit_level;
    Formatter::ptr _formatter;
    std::vector<LogSink::ptr> _sinks;
};
/*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
class LocalLoggerBuilder : public LoggerBuilder
{
public:
    Logger::ptr build() override
    {
        assert(_logger_name.empty() == false);
        if (_formatter.get() == nullptr)
        {
            _formatter = std::make_shared<Formatter>();
        }
        if (_sinks.empty())
        {
            buildSink<StdOutSink>();
        }
        if (_logger_type == LoggerType::LOGGER_ASYNC)
        {
            return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);
        }
        return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
    }
};

单例日志器管理类设计思想

通过局部日志器建造者创建的日志器受到作用域的限制。但是日志的输出,我们希望能够在任意位置。

因此为了突破日志器作用域的限制,我们创建一个日志器管理类,且该类是一个单例类,这样我们就可以在任意位置通过单例管理器单例获取到指定的日志器进行输出了。

基于单例日志器管理类的设计思想,我们对于日志器建造者类进行继承,继承出一个全局日志器建造者类,实现一个日志器在创建完毕后,直接将其添加到单例的日志器管理器当中,以便于能够在任意位置通过日志器名称能够获取到指定的日志器进行输出。

日志器管理器的作用

  • 对所有创建的日志器进行管理
  • 可以在程序的任意位置进,获取相同的单例对象,获取其中的日志器进行日志输出

单例日志器管理类设计

管理的成员:

  • 默认日志器
  • 所管理的日志器数组(使用哈希表,日志器名称为key,日志器对象为value);
  • 互斥锁

提供的操作:

  • 添加日志器管理
  • 判断是否管理了指定名称的日志器
  • 获取指定名称的日志器
  • 获取默认日志器
class LoggerManager
{
public:
    static LoggerManager& getInstance()
    {
        // c++11之后,静态局部变量,编译器在编译的层面实现了线程安全
        // 当静态局部变量在没有构造完成之前,其他线程进入就会阻塞
        static LoggerManager eton;
        return eton;
    }
    void addLogger(Logger::ptr &logger)
    {
        if(hasLogger(logger->name())) return;
        std::unique_lock<std::mutex> lock(_mutex);
        _loggers.insert(std::make_pair(logger->name(), logger));
    }
    bool hasLogger(const std::string name)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _loggers.find(name);
        if(it == _loggers.end())
        {
            return false;
        }
        return true;
    }
    Logger::ptr getLogger(const std::string name)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _loggers.find(name);
        if(it == _loggers.end())
        {
            return Logger::ptr();
        }
        return it->second;
    }
    Logger::ptr rootLogger()
    {
        return _root_logger;
    }
private:
    // 构造函数私有化
    LoggerManager()
    {
        std::unique_ptr<LOG::LoggerBuilder> builder(new LOG::LocalLoggerBuilder());
        builder->buildLoggerName("root");
        _root_logger = builder->build();
        _loggers.insert(std::make_pair("root", _root_logger));
    }
private:
    std::mutex _mutex;
    Logger::ptr _root_logger; // 默认日志器
    std::unordered_map<std::string, Logger::ptr> _loggers // 日志器数组;
};

全局建造者类设计

为了降低用户的使用复杂度,我们提供一个全局日志器建造者类。全局建造者类的设计思想非常简单,即在局部的基础上增加了一个功能:

  • 将日志器添加到单例对象中
class GlobalLoggerBuilder : public LoggerBuilder
{
public:
    Logger::ptr build() override
    {
        assert(_logger_name.empty() == false);
        if (_formatter.get() == nullptr)
        {
            _formatter = std::make_shared<Formatter>();
        }

        if (_sinks.empty())
        {
            buildSink<StdOutSink>();
        }

        Logger::ptr logger;
        if (_logger_type == LoggerType::LOGGER_ASYNC)
        {
            logger = std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);
        }
        else
        {
            logger = std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
        }
        LoggerManager::getInstance().addLogger(logger);
        return logger;
    }
};

日志器类、建造者类整理

#ifndef __M_LOGGER_H__
#define __M_LOGGER_H__
#include "util.hpp"
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "looper.hpp"
#include <cstdarg>
#include <atomic>
#include <thread>
#include <mutex>
#include <unordered_map>

namespace LOG
{
    class Logger
    {
    public:
        using ptr = std::shared_ptr<Logger>;
        Logger(const std::string &logger_name,
               LogLevel::value level,
               Formatter::ptr &formatter,
               std::vector<LogSink::ptr> &sinks) : 
               _logger_name(logger_name),
               _limit_level(level),
               _formatter(formatter),
               _sinks(sinks.begin(), sinks.end())
        {}

        const std::string& name(){ return _logger_name; }
        void debug(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            // 判断当前的日志是否达到了输出等级
            if (LogLevel::value::DEBUG < _limit_level)
            {
                return;
            }

            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::DEBUG, file, line, res);
            free(res);
        }
        void info(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::INFO < _limit_level)
            {
                return;
            }

            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::INFO, file, line, res);
            free(res);
        }
        void warn(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::WARN < _limit_level)
            {
                return;
            }
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::WARN, file, line, res);
            free(res);
        }
        void error(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::ERROR < _limit_level)
            {
                return;
            }
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::ERROR, file, line, res);
            free(res);
        }
        void fatal(const std::string &file, size_t line, const std::string &fmt, ...)
        {
            // 通过传入的参数构造出一个日志消息对象, 进行日志格式化,最终落地
            if (LogLevel::value::FATAL < _limit_level)
            {
                return;
            }
            // 对fmt格式化字符串和不定参数进行字符串组织, 得到的日志消息字符串
            va_list ap;
            va_start(ap, fmt);
            char *res;
            int ret = vasprintf(&res, fmt.c_str(), ap);
            if (ret == 1)
            {
                std::cout << "vasprintf failed\n";
                return;
            }
            va_end(ap);
            serialize(LogLevel::value::FATAL, file, line, res);
            free(res);
        }

    protected:
        void serialize(LogLevel::value level, const std::string &file, size_t line, char *str)
        {
            // 构造LogMsg对象
            LogMsg msg(level, line, file, _logger_name, str);
            // 通过格式化工具对LogMsg进行格式化, 得到格式化后的日志字符串
            std::stringstream ss;
            _formatter->format(ss, msg);
            // 对日志进行落地
            log(ss.str().c_str(), ss.str().size());
        }
        virtual void log(const char *data, size_t len) = 0;

    protected:
        std::mutex _mutex;
        std::string _logger_name;                  // 日志器名称
        std::atomic<LogLevel::value> _limit_level; // 限制输出等级
        Formatter::ptr _formatter;
        std::vector<LogSink::ptr> _sinks;
    };

    class SyncLogger : public Logger
    {
    public:
        SyncLogger(const std::string &logger_name,
                   LogLevel::value level,
                   LOG::Formatter::ptr &formatter,
                   std::vector<LogSink::ptr> &sinks) 
                   : Logger(logger_name, level, formatter, sinks)
        {}

    protected:
        void log(const char *data, size_t len)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            if (_sinks.empty())
                return;
            for (auto &sink : _sinks)
            {
                sink->log(data, len);
            }
        }
    };

    class AsyncLogger : public Logger
    {
    public:
        AsyncLogger(const std::string &logger_name,
                    LogLevel::value level,
                    LOG::Formatter::ptr &formatter,
                    std::vector<LogSink::ptr> &sinks,
                    AsyncType looper_type)
            : Logger(logger_name, level, formatter, sinks),
              _looper(std::make_shared<AsyncLooper>(std::bind(&AsyncLogger::realLog, this, std::placeholders::_1), looper_type))
        {}
        // 将数据写入缓冲区 
        void log(const char *data, size_t len)
        {
            _looper->push(data, len);
        }
        // 设计一个实际落地函数
        void realLog(Buffer &buf)
        {
            if (_sinks.empty())
                return;
            for (auto &sink : _sinks)
            {
                sink->log(buf.begin(), buf.readAbleSize());
            }
        }

    private:
        AsyncLooper::ptr _looper; // 异步工作器
    };

    // 1.抽象一个日志器建造者类(完成日志器所需零部件的构建 & 日志器的构建)
    //  1.设置日志器类型
    //  2.将不同类型的日志器的创建放到同一个日志器建造者类中完成
    enum class LoggerType
    {
        LOGGER_SYNC,
        LOGGER_ASYNC
    };

    class LoggerBuilder
    {
    public:
        LoggerBuilder() : _logger_type(LoggerType::LOGGER_SYNC),
                          _limit_level(LogLevel::value::DEBUG),
                          _looper_type(AsyncType::ASYNC_SAFE)
        {}
        void buildLoggerType(LoggerType type) { _logger_type = type; }
        void buildEnableUnSafeAsync() { _looper_type = AsyncType::ASYNC_UNSAFE; }
        void buildLoggerName(const std::string &name) { _logger_name = name; }
        void buildLoggerLevel(LogLevel::value level) { _limit_level = level; }
        void buildFormatter(const std::string &pattern)
        {
            _formatter = std::make_shared<Formatter>(pattern);
        }
        template <typename SinkType, typename... Args>
        void buildSink(Args &&...args) // 由用户自己决定落地方式
        {
            LogSink::ptr psink = SinkFactory::create<SinkType>(std::forward<Args>(args)...);
            _sinks.push_back(psink);
        }
        virtual Logger::ptr build() = 0;
    protected:
        AsyncType _looper_type;
        LoggerType _logger_type;
        std::string _logger_name;
        std::atomic<LogLevel::value> _limit_level;
        Formatter::ptr _formatter;
        std::vector<LogSink::ptr> _sinks;
    };

    /*2.派生出具体的建造者类---局部日志器的建造者 & 全局日志器的建造者*/
    class LocalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            assert(_logger_name.empty() == false);
            if (_formatter.get() == nullptr)
            {
                _formatter = std::make_shared<Formatter>();
            }
            if (_sinks.empty())
            {
                buildSink<StdOutSink>();
            }
            if (_logger_type == LoggerType::LOGGER_ASYNC)
            {
                return std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);
            }
            return std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
        }
    };

    class LoggerManager
    {
    public:
        static LoggerManager& getInstance()
        {
            // c++11之后,静态局部变量,编译器在编译的层面实现了线程安全
            // 当静态局部变量在没有构造完成之前,其他线程进入就会阻塞
            static LoggerManager eton;
            return eton;
        }
        void addLogger(Logger::ptr &logger)
        {
            if(hasLogger(logger->name())) return;
            std::unique_lock<std::mutex> lock(_mutex);
            _loggers.insert(std::make_pair(logger->name(), logger));
        }
        bool hasLogger(const std::string name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _loggers.find(name);
            if(it == _loggers.end())
            {
                return false;
            }
            return true;
        }
        Logger::ptr getLogger(const std::string name)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _loggers.find(name);
            if(it == _loggers.end())
            {
                return Logger::ptr();
            }
            return it->second;
        }
        Logger::ptr rootLogger()
        {
            return _root_logger;
        }
    private:
        // 构造函数私有化
        LoggerManager()
        {
            std::unique_ptr<LOG::LoggerBuilder> builder(new LOG::LocalLoggerBuilder());
            builder->buildLoggerName("root");
            _root_logger = builder->build();
            _loggers.insert(std::make_pair("root", _root_logger));
        }
    private:
        std::mutex _mutex;
        Logger::ptr _root_logger; // 默认日志器
        std::unordered_map<std::string, Logger::ptr> _loggers // 日志器数组;
    };

    class GlobalLoggerBuilder : public LoggerBuilder
    {
    public:
        Logger::ptr build() override
        {
            assert(_logger_name.empty() == false);
            if (_formatter.get() == nullptr)
            {
                _formatter = std::make_shared<Formatter>();
            }

            if (_sinks.empty())
            {
                buildSink<StdOutSink>();
            }

            Logger::ptr logger;
            if (_logger_type == LoggerType::LOGGER_ASYNC)
            {
                logger = std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _sinks, _looper_type);
            }
            else
            {
                logger = std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _sinks);
            }
            LoggerManager::getInstance().addLogger(logger);
            return logger;
        }
    };
}

#endif

日志器管理类测试

#include "logger.hpp"

void log_test()
{
    LOG::Logger::ptr logger = LOG::LoggerManager::getInstance().getLogger("async_logger");
    logger->debug(__FILE__, __LINE__, "%s", "测试日志");
    logger->info(__FILE__, __LINE__, "%s", "测试日志");
    logger->warn(__FILE__, __LINE__, "%s", "测试日志");
    logger->error(__FILE__, __LINE__, "%s", "测试日志");
    logger->fatal(__FILE__, __LINE__, "%s", "测试日志");

    size_t count = 0;
    while(count < 300000)
    {   
        logger->fatal(__FILE__, __LINE__, "测试日志-%d", count++);
    }
}
int main()
{   
    std::unique_ptr<LOG::LoggerBuilder> builder(new LOG::GlobalLoggerBuilder());
    builder->buildLoggerName("async_logger");
    builder->buildLoggerLevel(LOG::LogLevel::value::WARN);
    builder->buildFormatter("[%c]%m%n");
    builder->buildLoggerType(LOG::LoggerType::LOGGER_ASYNC);
    builder->buildEnableUnSafeAsync();
    builder->buildSink<LOG::FileSink>("./logfile/async.log");
    builder->buildSink<LOG::StdOutSink>();
    builder->build();
    log_test();
    return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1098166.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

达梦数据库适配ServiceStack框架

注&#xff1a;达梦的驱动版本请使用2023第四季度及以后版本驱动才可以 ServiceStack介绍 ServiceStack官网&#xff1a; https://github.com/ServiceStack/ServiceStack ServiceStack是一个开源的十分流行的WebService框架&#xff0c;引用其官网的介绍&#xff1a;“Servic…

创建React Native的第一个hello world工程

创建React Native的第一个hello world工程 需要安装好node、npm环境 如果之前没有安装过react-native-cli脚手架的&#xff0c;可以按照下述步骤直接安装。如果已经安装过的&#xff0c;但是在使用这个脚手架初始化工程的时候遇到下述报错的话 cli.init(root, projectname);…

FPGA中的LUT查找表工作原理。

在RAM中填入1110,后续的不同AB组合Y输出对应的值&#xff0c;实现上面逻辑表达式的功能。

windows编译ollvm笔记

准备工作 1.找到Android SDK目录配置好cmake环境变量 E:\AndroidSDK\cmake\3.18.1&#xff08;E:\AndroidSDK为 Android SDK目录地址&#xff09;。 下载llvm-mingw编译环境(gcc编译器的windows版本&#xff0c;即可以在windows平台上使用gcc编译器)&#xff0c;下载地址&…

Linux安装rpm包在线安装mysql5.7

以前安装过mysql 前言&#xff1a;检查以前是否装有mysql rpm -qa|grep -i mysql安装了会显示&#xff1a;   bt-mysql57-5.7.31-1.el7.x86_64 停止mysql服务和删除之前安装的mysql rpm -e bt-mysql57-5.7.31-1.el7.x86_64查找并删除mysql相关目录 find / -name mysql/va…

QT开发工业自动化控制软件的几个常用模块

最近两年一直从事工业自动化制造企业的软件开发&#xff0c;发现跟以前开发网络软件还是有较大的区别&#xff0c;重点就是在一些细的方面&#xff0c;比如架构、模块、通讯之类的。下面举几个例子&#xff1a; 1、数字键盘&#xff08;替代普通键盘的小数字键盘&#xff09; …

Jenkins 内存占用

查看内存占用 # ps aux | grep 9090 root 130854 0.0 0.0 8900 708 pts/1 S 16:23 0:00 grep --colorauto 9090 root 4010748 0.2 30.7 5826500 2502884 ? Ssl Oct13 8:55 /usr/bin/java -Djava.awt.headlesstrue -jar /usr/share/java/jenkins…

数据在内存中的存储(2)

文章目录 3. 浮点型在内存中的存储3.1 一个例子3.2 浮点数存储规则 3. 浮点型在内存中的存储 常见的浮点数&#xff1a; 3.14159 1E10 ------ 1.0 * 10^10 浮点数家族包括&#xff1a; float、double、long double 类型 浮点数表示的范围&#xff1a;float.h中定义 3.1 一个例…

C++基本语法【恩培学习笔记(一)】

文章目录 1、C程序结构1.1 C程序的基本组成部分1.2 预处理指令1.3 注释1.4 main() 主函数1.5 命名空间 namespace 2、 C的变量和常量2.1 变量2.2 变量的声明2.3 变量的类型 3、C 数组和容器3.1 数组&#xff08;array&#xff09;3.2 容器&#xff08;vector&#xff09; 4、C …

创建scala项目并增加新的object试运行

一、创建scala项目 依赖配置&#xff1a; scala&#xff0c;jdk&#xff0c;maven 没有maven也可以创建 1.1 直接创建 1.1.1 创建 选择新project 路径、依赖配置、代码调试 1.1.2 项目结构 Scala项目中几个文件&#xff1a; .idea&#xff1a;这个文件夹是用来存储项目的…

Android酒店客房预订系统 后台管理+前端app 包含视频教程

【项目功能介绍】 功能列表: 本系统包含后台管理和前端app双端系统, 本系统包含三个角色: 管理员,员工,app用户。 后台管理员的功能包含: 登录, 退出, ,酒店管理,添加酒店,修改酒店,禁用启用酒店; 酒店客房管理,添加客房,修改客房,启用禁用客房; 订单管理,确定订单,拒绝订单,用…

Android视音频知识

Android视音频知识 视音频完整解码播放流程分析。 视音频完整录制编码流程分析。 为什么要编码&#xff0c;如何编码(编码原理) ?。 为什么要编码&#xff1f; 因为视频文件实在太大了&#xff0c;一部电影 200多个GB&#xff0c;编码&#xff1a;1G 视频是连续的图像序列&a…

【Linux】chmod 命令使用

chmod&#xff08;英文全拼&#xff1a;change mode&#xff09;命令是控制用户对文件的权限的命令。 chmod命令 -Linux手册页 著者 作者&#xff1a;David MacKenzie和Jim Meyering。 语法 chmod [选项] [模式] 文件或目录 Linux/Unix 的文件调用权限分为三级 : 文件所有者…

Spring-AOP-加强

目录 简略介绍 AOP是如何实现的 实现时机 实现原理 简略介绍 AOP(Aspect-Oriented Programming)&#xff0c;即面向切面编程&#xff0c;用人话说就是把公共的逻辑抽出来&#xff0c;让开发者可以更专注于业务逻辑开发和IOC一样&#xff0c;AOP也指的是一种思想AOP思想是OO…

自动化测试框架中如何记录日志更加已读 ?一文介绍使用loguru来管理日志的心得。

只要做代码开发&#xff0c;记录日志必不可少的 &#xff0c;对于像我这样的测试开发同学也是 &#xff0c;你在编写自动化时如何记录日志 &#xff1f;怎么要日志记录更容易已读 &#xff1f;如何备份日志文件 &#xff1f; 这都是我们在编写代码时要考虑的问题 &#xff0c;如…

JNI 的数据类型以及和Java层之间的数据转换

JNI的数据类型和类型签名 数据类型 JNI的数据类型包含两种&#xff1a;基本类型和引用类型。 基本类型主要有jboolean、jchar、jint等&#xff0c;它们和Java中的数据类型的对应关系如下表所示。 JNI中的引用类型主要有类、对象和数组&#xff0c;它们和Java中的引用类型的对…

ICC2:如何抓取“no net“的shape和via

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 pr过程中(尤其是eco)会产生一些no net的shape或via&#xff0c;它们会造成drc和lvs问题&#xff0c;但是常规的办法无法把他们抓出来&#xff0c;下面分享可以获取no net的方法…

数字化时代下,汽车行业如何突破现有营销困境?

之前三年的“口罩”时期&#xff0c;给全球和中国汽车市场带来不小影响&#xff0c;汽车销售市场整体下滑&#xff0c;传统营销模式很难适应现阶段汽车营销需求&#xff0c;那么在当下&#xff0c;汽车行业应该如何突破现有营销困境呢&#xff1f;接下来就由媒介盒子跟大家聊聊…

如何同步 Github 和 Gitee的仓库代码

一、从github导入仓库&#xff0c;手动同步 在 Gitee 的项目主页&#xff0c;导入的仓库会会有一个同步的按钮&#xff0c;你只用点一下&#xff0c;即可与 Github 同步更新&#xff0c;但是注意这里的同步功能默认是强制同步。有点麻烦的是&#xff0c;我们需要在推送到 Githu…

推理成本增加10倍?对文心大模型4.0的一些猜想

夕小瑶科技说 原创 作者 | 卖萌酱 大家好&#xff0c;我是卖萌酱。 相信不少小伙伴这几天都听到了消息&#xff0c;在期待下周即将发布的文心大模型4.0。我们的几个读者群里也发生了相关的讨论&#xff1a; 讨论的核心主要围绕以下两个话题展开&#xff1a; 文心4.0能不能打过…