1.electron应用版本更新添加releaseNotes(更新日志)的源码N种方法
2.使用 Changesets 管理版本及更新日志
3.常见log日志的使用方法详细解析
4.element-plus源码学习日志-03
5.easylogging源码学习笔记(6)
6.Odoo框架源码研读二:ORM框架与日志
electron应用版本更新添加releaseNotes(更新日志)的N种方法
前言
目前electron应用一般是使用electron-builder进行打包,使用electron-updater进行版本更新,升级客户端检测到新版本后一般会弹窗提示用户有新版本+展示更新日志,日志这就需要我们打包的源码时候将版本更新日志(releaseNotes)添加到latest.yml或latest-mac.yml文件中,然后客户端检测到新版本后就能够获取到该更新日志并展示给用户。升级通过分析electron-builder源码,日志接口源码总结出几种添加releaseNotes的源码方法。
version:?升级1.0.0files:-?url:?electron-start_setup_1.0.0.exesha:?+yJuqcWDdhWGLvuLiJFjFKM+uQfihiQ8FHE7RoyyFjOiFZeGugE7UPlceDHfm9qyQOYmUvuEzjq/u3zw==size:?path:?electron-start_setup_1.0.0.exesha:?+yJuqcWDdhWGLvuLiJFjFKM+uQfihiQ8FHE7RoyyFjOiFZeGugE7UPlceDHfm9qyQOYmUvuEzjq/u3zw==releaseNotes:?"修复断网时离开会议失败的问题\r\n修复会议中受开关麦影响听不到的问题\r\n新增动态转发功能\r\n修复Bug,优化UI"releaseDate:?日志'--T::.Z'适用范围项目使用Electron-builder打包
provider为generic
//package.json{ "build":?{ "publish":?[{ "provider":?"generic","url":?"xxxxxx"}]}}方法一:在package.json文件build节点下添加releaseNotes信息示例:
//package.json{ "build":?{ "releaseInfo":{ "releaseNotes":"修复断网时离开会议失败的问题\r\n修复会议中受开关麦影响听不到的问题\r\n新增动态转发功能\r\n修复Bug,优化UI"}}}方法二:在package.json文件build节点下添加releaseNotesFiles信息(推荐使用)示例:
//package.json{ "build":?源码{ "releaseInfo":{ "releaseNotesFile":"release-1.0.0.md"}}}releaseNotesFile字段指定更新日志文件为release-1.0.0.md,其内容如下:
修复断网时离开会议失败的升级问题修复会议中受开关麦影响听不到的问题新增动态转发功能修复Bug,优化UI使用这种方法添加releaseNotes,日志可自定义更新日志文件名称,源码如每发布一个版本就添加一个更新日志文件(release-1.0.0.md,升级release-1.0.1.md...),日志便于以后查看每一个版本更新日志。
方法三:在打包输出目录下创建文件release-notes.md如指定了输出目录为build,则在build目录下创建名称为release-notes.md的文件。
可取的文件名为:
release-notes.md
release-notes-(mac|windows|linux).md
.....
方法四:直接修改打包生成的latest.yml或latest-mac.yml,添加releaseNotes字段(不建议使用)客户端实现使用electron-updater检查更新获取更新日志,弹窗提示用户有新版本
const?{ ?autoUpdater?}?=?require('electron-updater')//检测到新版本时触发autoUpdater.on('update-available',?function?(info)?{ //获取更新日志var?releaseNotes=info.releaseNotes//弹窗提示用户const?dialogOpts?=?{ type:?'info',buttons:?['立即下载','稍后'],title:?'版本更新',textWidth:?,message:?'发现新版本'+info.version+"("+(info.files[0].size//).toFixed(2)+"MB)"+"\r\n\r\n"+releaseNotes,cancelId:?1}dialog.showMessageBox(dialogOpts).then((returnValue)?=>?{ if?(returnValue.response?===0)?{ ?autoUpdater.downloadUpdate();}})});实现效果如下:
macOS系统Windows系统相关源码分析electron-builder打包时获取releaseNotes对应的代码文件为packages/app-builder-lib/src/publish/updateInfoBuilder.ts,具体代码如下:
async?function?getReleaseInfo(packager:?PlatformPackager<any>)?{ const?releaseInfo:?ReleaseInfo?=?{ ?...(packager.platformSpecificBuildOptions.releaseInfo?||?packager.config.releaseInfo)?}if?(releaseInfo.releaseNotes?==?null)?{ const?releaseNotesFile?=?await?packager.getResource(releaseInfo.releaseNotesFile,`release-notes-${ packager.platform.buildConfigurationKey}.md`,`release-notes-${ packager.platform.name}.md`,`release-notes-${ packager.platform.nodeName}.md`,"release-notes.md")const?releaseNotes?=?releaseNotesFile?==?null?null?:?await?readFile(releaseNotesFile,?"utf-8")//?to?avoid?undefined?in?the?file,?check?for?nullif?(releaseNotes?!=?null)?{ releaseInfo.releaseNotes?=?releaseNotes}}delete?releaseInfo.releaseNotesFilereturn?releaseInfo}优先从releaseInfo.releaseNotes字段中取值(方法一)
如果releaseInfo.releaseNotes未定义,则从releaseInfo.releaseNotesFile取值(方法二)
如果releaseInfo.releaseNotesFile未定义,则从资源目录下的指定文件(如release-notes.md)中取值(方法三)
参考资源/post/
使用 Changesets 管理版本及更新日志
在探索 Slate.js 框架中,我发现了changesets这个工具在管理版本和更新日志方面的高效应用。它能解决我们使用standard-version时的一些问题,其操作流程清晰,避免了黑盒操作,非常适合我的需求。 changesets本质上是一个用于生成变更日志的工具,遵循语义化版本2.0.0规则。具体操作包括:首先,根据代码更改生成changeset文件;然后,发布时合并这些文件,更新版本号,并生成详尽的变更记录。 使用changesets的资源吧源码步骤如下:安装相关依赖库
初始化并可能需要手动配置,如使用@changesets/changelog-github插件来定制生成的changelog样式
生成changeset,包括选择版本类型、输入更新摘要并保存文件
提交更改并推送至版本控制系统
最后,使用配置好的GITHUB_TOKEN生成changelog
如果项目结构复杂,如我遇到的情况,可能需要在根目录的package.json中配置workspace,以便正确处理包间的版本管理。同时,changelog文件也需要相应移动。 另外,确保配置了GITHUB_TOKEN环境变量,以便访问GitHub信息。遇到错误时,例如未提交的更改或token未生效,按照提示进行修正即可。 虽然我目前的场景并不涉及多包,但changesets的设计初衷就是支持多包管理,通过其源代码可见其强大的多包操作能力,能明确指出代码更改的包,避免了复杂的操作过程。常见log日志的使用方法详细解析
日志在程序开发中起着至关重要的作用,它能帮助我们调试错误并记录关键信息。常见的日志框架包括Java自带的原生日志、log4j以及Slf4j等。
日志级别是控制输出信息的关键,通常分为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE和ALL,级别越高,信息越详细。Java.util.Logger,如在源代码中使用`Logger`,源码左右移可以通过设置级别来调整输出,例如,`logger.setLevel(Level.ALL)`会显示所有级别的信息,而`logger.setLevel(Level.WARNING)`则只会显示严重和警告级别的信息。
log4j则通常在XML配置文件中配置,比如导入依赖和配置``元素。通过这种方式,可以精细地控制每个日志条目的格式和输出位置。log4j2的配置文件如`log4j2.xml`,提供了丰富的自定义选项。
Slf4j作为当前主流的日志框架,支持参数化输出,只需在类头引入`import org.slf4j.Logger`和`LoggerFactory.getLogger`,并在代码中调用相关方法。在引入依赖时,可能出现`SLF4J: Failed to load class`的问题,解决方法通常是检查类路径和slf4j的绑定。Slf4j的输出信息中包含类路径信息,便于追踪问题来源。
总结来说,log日志的使用方法因框架而异,但核心在于控制输出级别和配置细节,以满足不同开发阶段的需求。通过灵活运用这些工具,可以提高程序的可维护性和可读性。
element-plus源码学习日志-
每日学习进阶,承上启下
昨日探讨了input组件的使用及编码准则,今日深入剖析element-plus源码,探索新知识。
文件定位至element-plus\packages\dialog\src\index.vue
先看模板代码片段,引入了teleport组件,这是新增的内置组件。
没有使用teleport时,元素作为app组件的子节点;而使用teleport后,元素变为app组件的同级节点,统一挂载于body下,源码猫下载to属性可指定具体id的DOM节点。
前端展示层级对最终显示结果影响重大。在Vue 2时代,使用Vue.extend创建新实例,挂载于app同级节点,解决全局弹层的层级问题。新自定义组件简化了开发流程,优化代码。
引入了Vue 3自定义指令,与之前版本有所调整,需进一步学习。
注意到Vue 3支持fragments,组件不再受限于单一节点,引入新问题,需深入研究官方文档,理解其用法。
JS代码段回顾了之前讨论过的基础知识,简要审视,复习要点。
今日总结:学习了Vue的新内置组件teleport,具备将包含的节点挂载至指定DOM节点的功能。并了解了新版本自定义指令的调整。
下一步规划:基于Jest为组件编写单元测试,学习Jest的基本用法、报告生成等操作,深入框架测试领域。
easylogging源码学习笔记(6)
`LOG` 是默认日志、CLOG自定义日志、LOG_IF条件日志
特殊日志
LOG_EVERY_N、LOG_AFTER_N、LOG_N_TIMES
for (int i = 1; i <= ; ++i) {
LOG_EVERY_N(2, INFO) << "Logged every second iter";
}// 5 logs written; 2, 4, 6, 7,
for (int i = 1; i <= ; ++i) {
LOG_AFTER_N(2, INFO) << "Log after 2 hits; " << i;
}// 8 logs written; 3, 4, 5, 6, 7, 8, 9,
for (int i = 1; i <= ; ++i) {
LOG_N_TIMES(3, INFO) << "Log only 3 times; " << i;
}// 3 logs writter; 1, 2, 3
条件日志和特殊日志可以搭配使用
* `VLOG_IF(condition, verbose-level)`
* `CVLOG_IF(condition, verbose-level, loggerID)`
* `VLOG_EVERY_N(n, verbose-level)`
* `CVLOG_EVERY_N(n, verbose-level, loggerID)`
* `VLOG_AFTER_N(n, verbose-level)`
* `CVLOG_AFTER_N(n, verbose-level, loggerID)`
* `VLOG_N_TIMES(n, verbose-level)`
* `CVLOG_N_TIMES(n, verbose-level, loggerID)`
日志详细等级判定
if (VLOG_IS_ON(2)) {
// Verbosity level 2 is on for this file
}
性能追踪
* `TIMED_FUNC(obj-name)`
* `TIMED_SCOPE(obj-name, block-name)`
* `TIMED_BLOCK(obj-name, block-name)`
这些宏实际上都是关于el::base::type::PerformanceTrackerPtr,一个指向el::base::PerformanceTracker的指针
#if defined(ELPP_FEATURE_ALL) || defined(ELPP_FEATURE_PERFORMANCE_TRACKING)
PerformanceTracker::PerformanceTracker(const std::string& blockName,
base::TimestampUnit timestampUnit,
const std::string& loggerId,
bool scopedLog, Level level) :
m_blockName(blockName), m_timestampUnit(timestampUnit), m_loggerId(loggerId), m_scopedLog(scopedLog),
m_level(level), m_hasChecked(false), m_lastCheckpointId(std::string()), m_enabled(false) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
// We store it locally so that if user happen to change configuration by the end of scope
// or before calling checkpoint, we still depend on state of configuration at time of construction
el::Logger* loggerPtr = ELPP->registeredLoggers()->get(loggerId, false);
m_enabled = loggerPtr != nullptr && loggerPtr->m_typedConfigurations->performanceTracking(m_level);
if (m_enabled) {
base::utils::DateTime::gettimeofday(&m_startTime);
}
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
}
在构造函数中获取一个时间,
PerformanceTracker::~PerformanceTracker(void) {
#if !defined(ELPP_DISABLE_PERFORMANCE_TRACKING) && ELPP_LOGGING_ENABLED
if (m_enabled) {
base::threading::ScopedLock scopedLock(lock());
if (m_scopedLog) {
base::utils::DateTime::gettimeofday(&m_endTime);
base::type::string_t formattedTime = getFormattedTimeTaken();
PerformanceTrackingData data(PerformanceTrackingData::DataType::Complete);
data.init(this);
data.m_formattedTimeTaken = formattedTime;
PerformanceTrackingCallback* callback = nullptr;
for (const std::pair& h
: ELPP->m_performanceTrackingCallbacks) {
callback = h.second.get();
if (callback != nullptr && callback->enabled()) {
callback->handle(&data);
}
}
}
}
#endif // !defined(ELPP_DISABLE_PERFORMANCE_TRACKING)
}
在析构函数中获取一个时间,处理时间data,使用PerformanceTrackingCallback类型指针callback,并在callback->handle(&data)中处理输出。梦回剑道源码
由于定义了ELPP_FEATURE_PERFORMANCE_TRACKING,因此在初始化(INITIALIZE_EASYLOGGINGPP)中实际上是安装了一个base::DefaultPerformanceTrackingCallback。
在PerformanceTracker类的handle函数中,callback是一个PerformanceTrackingCallback类型指针,由于安装的是DefaultPerformanceTrackingCallback对象,因此是一个基类指针指向了派生类对象。处理输出的逻辑在DefaultPerformanceTrackingCallback类的handle函数中。
DefaultPerformanceTrackingCallback类的handle函数首先会将数据成员m_data的指针赋值给函数参数,并创建一个base::type::stringstream_t类型的对象ss用于构建输出内容。根据m_data的dataType,输出不同的信息。在输出时,会使用el::base::Writer类构造并输出内容。
Odoo框架源码研读二:ORM框架与日志
上一期我们带来了关于Odoo框架源码第一期内容(传动门➡《Odoo框架源码研读一:前后端交互》),让大家对Odoo整体结构和前后端交互有了更深刻的了解。
而Odoo在实际开发的大多数场景都是基于它的ORM框架进行的,所以本期我们将带来Odoo框架源码的第二期内容——ORM和日志。
一、ORM
Odoo是通过Controller控制器,来控制前后台的交互。上一期我们详细的介绍了如何让请求顺利到达Controller控制器。
那么当请求到达Controller后,又如何来实现后端的业务逻辑呢?这就不得不提到Odoo一个非常强大的类——Environment。
通过_loca属性,Environment可以直接管理线程中的environments上下文状态,并且包装了ORM相关四个属性:
而且Environment还提供了Model name和Model之间的映射,通过self.env[ModelName] 就可以直接调用Model的API ,非常强大。
现在,再回头看Controller中的方法,可以看到,通过request.env[model]可以直接获取对应的Model对象,然后直接调用Model的方法。
Odoo中一切都是基于Model编程。即使是前端的menu、action、view,都是也都有对应 Model,和业务数据无异。
创建一个Model ,然后通过简单的视图配置,那么这个Model对应的CRUD基础功能就已经实现,这些都归功于Odoo对于Model的抽象。
Odoo中的model分为三类:AbstractModel、Model、TransientModel;
继承关系如下图 ⬇
从源码中可以看出AbstractModel 是Model 和TransientModel的父类。而这个三者的区别也主要在_auto、_register、_abstract、_transient这四个属性上。
由此可以AbstractModel是抽象类,不会在数据库创建表,Model和TransientModel 不是抽象类,会在数据库建表,但TransientModel建的是临时表,数据会被系统定期清除,这个可以在系统中设置清除频率。
由于这种特性的不同,三个Model的用途也不相同。
TransientModel由于存临时表的特性,多用来做wizard向导视图,存储临时缓存数据;
Model多用于做业务的主要Model,AbstractModel多用来抽象做父类,由于不创建表的特性,有时也会用来做向导视图。
新模块中的Model,根据功能需要去继承这三个类,由于这三个父类中丰富的API方法,新建 Model在创建完字段后,功能就已经基本完善,如果有定制化的逻辑,只需要重写父类的方法就可以了。
Model中的Field不是Python的基础类型,而是继承Odoo封装的Field类。
因此,在Model字段赋值的时候,和基础类型字段不同,会调用Field中的API方法,这是容易踩坑的地方。
二、日志
Odoo的日志是在Python的logging基础模块之上,做了定制化的封装和配置。这部份代码主要在odoo/netsvc.py文件中。
Odoo定义了自己的Filter对象、Formatter对象、以及Handler对象。
1)项目启动的时候,配置管理器configmanager初始化,这个时候会去初始化默认的系统配置,包括日志模块。
2)随后,配置管理器会去加载配置文件odoo.conf中的自定义配置覆盖原先的默认配置。
3)最后,处理完配置加载,Odoo会调用日志初始化代码,根据最终的日志配置去设定相关的logger对象。
上图即为Odoo日志的默认配置。
初始化过程:
通过logging.logging.getLogger(_name_)调用前包层级的logger 对象,logging.logging.getLogger() 则返回root logger对象。
本期关于Odoo的ORM和日志就聊到这里,下一期我们会继续聊一下Odoo的异常和流程引擎,感兴趣的小伙伴记得关注我~
CesiumJS 更新日志 1. 与 1. - 新构建工具 esbuild 体验及 Model API 更替完成
本文将对CesiumJS的1.与1.版本进行概述,并详细介绍构建工具esbuild的使用与新Model API的更替。对于1.版本,主要更新内容包括两项过期API消息。至于1.版本,尽管尚未发布,但在源码仓库中已完成Model API的替换,相关更新内容将持续更新。
在新构建工具esbuild的引入中,CesiumJS项目经历了重大改进,包括优化代码构建过程,减小发行版库文件体积,提升加载速度以及解决Linux系统中Chrome浏览器的长期问题。esbuild的使用不仅带来更小体积的库文件,还加速了构建过程,显著缩短了网络加载时间。
esbuild与Rollup的对比显示,CesiumJS在构建过程中存在未进行轻量化与最小化处理的问题。因此,选择esbuild作为构建工具,以替代Rollup,来实现ESModule到库文件的转换,同时解决WebWorker的遗留问题。目前,由于Firefox仍未在WebWorker中支持ESModule,开发人员暂时使用Rollup和RequireJS解决此问题。一旦Firefox更新支持,CesiumJS将完全切换至esbuild。
构建过程中的重头戏包括旧构建指令的移除与新指令的用法。官方对构建脚本进行了重新评估和设计,包括构建、build-ts、build-docs、release等关键指令的引入或变更。这些更新旨在优化构建过程,提高效率并确保兼容性。
使用esbuild进行构建后,CesiumJS的性能显著提升。例如,与使用gulp时的构建速度相比,使用build指令的加速效果十分明显,从加载时间到库文件大小,都实现了优化。此外,基于HTTP2的CDN传输体积也能进一步提升加载速度。
未来CesiumJS可能考虑转向TypeScript,并在Firefox支持ESM后,彻底移除RequireJS和Rollup,以进一步加速构建过程和减小发布版本的库代码。对于开发者而言,了解如何最优地引入CesiumJS并利用CDN加速,以及避免应用打包器对CesiumJS的额外打包,将是提高项目性能的关键。
EasyLogger源码学习笔记(5)
在EasyLogger源码的学习中,我们了解到日志对象使用了互斥锁以确保同一时刻只有一个线程能进行操作,保证了日志管理的安全性与高效性。
对于异步输出,EasyLogger通过信号量实现了优化。当需要等待执行时,某个线程会被阻塞,以减少CPU的占用。这一特性允许用户单独设置异步输出的日志等级,提高系统的灵活性与可控性。
在文件输出时,使用了信号量集合,其中仅包含一个信号量。这一设计确保了同时只有一个线程能向文件中写入日志,避免了多线程并发写入导致的文件混乱。
日志输出的多样选择体现了EasyLogger的灵活性,无论是输出到文件还是串口,都可以根据需要配置是否采用异步输出,以适应不同的应用场景与性能需求。
此外,sem_post函数用于解锁由semby指定的信号量,执行对特定信号量的解锁操作。而semop函数则用于执行一组预先定义的信号量操作,适用于对多个信号量进行原子性操作。
在信号量集合仅包含一个信号量的情况下,使用sem_post函数进行操作可能直接替代使用semop函数。这一设计简化了信号量管理,提高了代码的可读性和效率。
EasyLogger源码学习笔记(2)
在EasyLogger源码学习中,关注函数elog_set_filter_tag_lvl(const char *tag, uint8_t level)。该函数的注释指出,仅当过滤等级level不为ELOG_FILTER_LVL_ALL时,才在0-ELOG_FILTER_TAG_LVL_MAX_NUM范围内添加新标签的过滤级别。
深入分析代码,发现其主要逻辑在于寻找未被使用的过滤级别,并将新标签与其关联。然而,代码未对在0-ELOG_FILTER_TAG_LVL_MAX_NUM范围内找不到未使用过滤级别的特殊情况进行处理。
这一问题的存在,意味着在系统资源紧张或标签使用率极高的情况下,该函数可能无法正常执行其预设功能,导致新标签的过滤等级无法被正确设置。为了确保功能的健全性和稳定性,开发者需对这一潜在缺陷进行修正。
在解决该问题时,建议增加逻辑判断,检查0-ELOG_FILTER_TAG_LVL_MAX_NUM范围内的过滤级别是否已全部被使用。如果已满,可以考虑扩展过滤级别范围或采用其他策略来容纳更多标签,以避免功能限制。
通过这一改进,EasyLogger的灵活性和兼容性将得到显著提升,更好地支持复杂应用环境中的日志管理需求。最终,这将有助于提升系统整体性能和用户体验,实现更高效、更稳定的信息记录与分析。