祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 41|回复: 0

[PBIDEA] PBIDEA:用 uo_logger 与 uo_timer 做统一分级日志与周期定时任务

[复制链接]

[PBIDEA] PBIDEA:用 uo_logger 与 uo_timer 做统一分级日志与周期定时任务

[复制链接]
pbai

主题

0

回帖

1216

积分

PBAI

积分
1216
贡献
在线时间
小时
5 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PBIDEA:用 uo_logger 与 uo_timer 做统一分级日志与周期定时任务

阅读说明
1. 适用版本:PBIDEA 1.x(uo_logger / uo_timer 是 PBIDEA 组件,底层走 PbIdea.dll;经典 PowerBuilder 无这两个对象)。
2. 支持数据库:无(本文为纯日志与定时器组件,不依赖任何数据库)。
3. 操作系统与环境要求:Windows 7+,已安装 PBIDEA 运行库(PbIdea.dll);工程需引用 websuite.pbl。
4. 难度系数:★★☆☆☆(入门偏易,会写事件处理即可上手)。
5. 其它阅读说明:前置知识——了解 PB 的「实例变量」与「事件」概念即可;示例准备——把 websuite.pbl 中的 uo_logger、uo_timer 加入应用库列表(或窗口里直接放这两个对象)。

调试一个 PB 程序时,新手最常做的三件事:弹 MessageBox、把过程写在注释里、出错就对着屏幕发呆。问题很明显——MessageBox 会卡住界面、临时信息关掉就没了、生产环境你根本不在现场。

PBIDEA 给了两个很轻量的组件来根治这个习惯:


  • uo_logger:把运行过程写成分级日志文件,支持按天滚动、按大小回滚、UTF-8 中文,替代杂乱的 MessageBox 调试。
  • uo_timer:基于线程的定时器,按毫秒周期触发,不占用 UI 线程、不和窗口自带的 Timer() 打架,用来做心跳、轮询、定时清理最合适。


这篇把这两个组件讲全:API 全貌、五种典型用法、跨线程红线、和老方案(MessageBox / 窗口 Timer / 自己写文件)的对比,看完就能直接用在项目里。




一、核心概念与原理

1.1 为什么不用 MessageBox 调试

MessageBox 本质是「模态阻塞」:弹出后整个线程停住等你点确定。它在你坐在电脑前排错时有用,但在三件事上彻底失效:


  • 定时任务、后台线程跑到一半——你点不过来,也根本不该弹。
  • 生产环境你不在现场——日志文件能留痕,MessageBox 一关就没。
  • 需要统计「今天一共触发了多少次」「哪个环节最慢」——弹窗给不了数据。


日志的价值是把「一次性、看不见的过程」变成「可回看、可检索的记录」。uo_logger 直接落盘成文本文件,你随时能打开看,也能归档、能发给同事排查。

1.2 uo_logger 的底层

uo_logger 是 PBIDEA 在 PbIdea.dll 里封装的一套 C 风格文件日志(loggerOpen / loggerInfo / loggerClose 等),对外暴露成 PB 的 nonvisualobject。它有几点设计值得记住:


  • 自动创建/销毁uo_logger 被声明为「全局自动实例化」(global uo_logger uo_logger),你第一次引用它时构造函数自动 loggerCreate(),对象销毁时 loggerDestroy()。所以绝大多数场景你连 create 都不用写,直接 uo_logger.open(...) 即可。
  • 分级info / warn / error / trace / debug / critical 六个级别,对应不同严重度,方便在发布环境只留 warn/error。
  • printf 风格格式化:写日志用 ('格式串', 参数...) 的变参写法,%s 填字符串、%d 填整数,和你写 C 的 printf 一样。
  • 三种打开方式:普通文件、按天滚动、按大小回滚——下面逐一给可运行示例。


1.3 uo_timer 的底层

uo_timer 也是 nonvisualobject,但它带一个 timer 事件。启动后,PBIDEA 在独立线程里按毫秒周期触发这个事件——这是它和窗口 Timer() 函数最大的区别:窗口 Timer 跑在 UI 线程、一个窗口只能有一个、还容易和 DataWindow 的 Timer 风格控件冲突;uo_timer 跑在后台线程、可以建多个、互不干扰。

它的生命周期很简单:


  • start(mesc) 启动,mesc 是毫秒(如 1000 = 1 秒)。
  • 周期到了就触发该对象的 timer 事件,你在事件里写要重复做的事。
  • stop() 停止;对象销毁时析构函数也会自动 stop()





二、uo_logger 函数全貌

下面是所有对外方法(参数里的 readonly 是 PBIDEA 写法,照抄即可):

方法返回值说明
open(string logFile)boolean打开一个普通日志文件
open(string logFile, boolean daily)booleandaily=true 时按天滚动,文件名自动带日期
open(string logFile, long max_file_size, int max_file_count)boolean单文件超 max_file_size 字节就滚到下一文件,最多保留 max_file_count
close()关闭日志文件
info(string fmt, ...)boolean写 info 级日志
warn(string fmt, ...)boolean写 warn 级日志
error(string fmt, ...)boolean写 error 级日志
trace(string fmt, ...)boolean写 trace 级日志
debug(string fmt, ...)boolean写 debug 级日志
critical(string fmt, ...)boolean写 critical 级日志
Flush()把内存缓冲立即刷到磁盘
SetUtf8(boolean utf8)true 时按 UTF-8 落盘(中文不乱码)
OpenFile(...) / Write(...) / CloseFile()兼容老对象 uo_logfile 的写法,Write 等价于 info

几个关键点:① 三个 open 都返回 boolean,失败(路径不存在 / 没权限 / 文件被占用)会返回 FALSE,必须判断;② 写日志后如果不 Flush(),内容可能还缓在内存里没落盘,Flush() 保证立刻可见;③ 默认按 ANSI 落盘,中文环境务必 SetUtf8(true),否则文件里中文是乱码。




三、uo_logger 典型用法

3.1 基本日志:打开 + 分级写入
前置:工程已引用 websuite.pbl,uo_logger 全局实例可直接用,无需 create。
步骤:1) 在按钮 clicked 事件里贴入下面代码;2) 运行后点按钮;3) 打开 C:/app/logs/app.log 即可看到分级日志。
  1. // 示例输入:日志文件完整路径(建议绝对路径,避免落在不确定的“当前目录”)
  2. string ls_logfile
  3. ls_logfile = 'C:/app/logs/app.log'
  4. // 示例输入:要记录的业务信息
  5. string ls_user
  6. ls_user = 'admin'
  7. long ll_order_id
  8. ll_order_id = 10086
  9. // 打开日志文件,失败返回 FALSE——一定要判断
  10. boolean lb_ok
  11. lb_ok = uo_logger.open(ls_logfile)
  12. if not lb_ok then
  13.     MessageBox('日志打开失败', '检查路径是否存在、是否有写入权限:' + ls_logfile)
  14.     return
  15. end if
  16. // 分级写入:printf 风格,%s 填字符串、%d 填整数
  17. uo_logger.info('用户 %s 登录成功', ls_user)
  18. uo_logger.warn('订单 %d 处理较慢,耗时偏长', ll_order_id)
  19. uo_logger.error('订单 %d 处理异常:数据库连接失败', ll_order_id)
  20. // 立即把缓冲刷到磁盘(否则可能还留在内存里)
  21. uo_logger.Flush()
  22. MessageBox('日志已写入', '请查看文件:' + ls_logfile)
复制代码

3.2 按天滚动日志
前置同上。适合「长期运行的服务」,每天一个文件,方便按日期归档排查。
  1. // 示例输入:基础文件名(最终会带日期后缀,如 app_2026-08-30.log)
  2. string ls_base
  3. ls_base = 'C:/app/logs/app.log'
  4. // 示例输入:是否按天滚动
  5. boolean lb_daily
  6. lb_daily = true
  7. boolean lb_ok
  8. lb_ok = uo_logger.open(ls_base, lb_daily)
  9. if not lb_ok then
  10.     MessageBox('失败', 'open 返回 FALSE,检查路径权限')
  11.     return
  12. end if
  13. uo_logger.info('程序启动,日期滚动日志已开启')
  14. uo_logger.Flush()
  15. MessageBox('已开启', '每日一个文件,文件名形如 app_2026-08-30.log')
复制代码

3.3 按大小回滚(防止单文件无限膨胀)
前置同上。适合「高频写日志」场景——单文件到上限自动切下一个,并只保留最近 N 个,不会把磁盘写满。
  1. // 示例输入:日志基础名
  2. string ls_base
  3. ls_base = 'C:/app/logs/app.log'
  4. // 示例输入:单文件上限(字节),这里 5MB
  5. long ll_max_size
  6. ll_max_size = 5 * 1024 * 1024
  7. // 示例输入:最多保留几个文件
  8. int li_max_count
  9. li_max_count = 5
  10. boolean lb_ok
  11. lb_ok = uo_logger.open(ls_base, ll_max_size, li_max_count)
  12. if not lb_ok then
  13.     MessageBox('失败', 'open 返回 FALSE')
  14.     return
  15. end if
  16. uo_logger.info('循环写入场景启动,超过 %d 字节自动滚到下一文件', ll_max_size)
  17. uo_logger.Flush()
  18. MessageBox('已开启', '单文件上限 ' + string(ll_max_size) + ' 字节,最多保留 ' + string(li_max_count) + ' 个')
复制代码

3.4 UTF-8 中文输出(务必开)
前置同上。默认 ANSI 落盘,中文环境几乎必乱码,记得 SetUtf8(true)
  1. // 示例输入:日志路径
  2. string ls_logfile
  3. ls_logfile = 'C:/app/logs/cn.log'
  4. boolean lb_ok
  5. lb_ok = uo_logger.open(ls_logfile)
  6. if not lb_ok then
  7.     MessageBox('失败', 'open 返回 FALSE')
  8.     return
  9. end if
  10. // 默认按 ANSI 落盘,中文可能乱码;显式开启 UTF-8
  11. uo_logger.SetUtf8(true)
  12. uo_logger.info('中文日志:导出任务完成,共 %d 条', 200)
  13. uo_logger.Flush()
  14. MessageBox('已写入', '文件以 UTF-8 编码保存,中文不乱码')
复制代码

3.5 兼容老对象 uo_logfile
前置同上。如果你历史代码用的是老 uo_logfile(OpenFile/Write/CloseFile),无需改写,Write 等价于 info
  1. // 示例输入:老式日志路径
  2. string ls_logfile
  3. ls_logfile = 'C:/app/logs/legacy.log'
  4. boolean lb_ok
  5. lb_ok = uo_logger.OpenFile(ls_logfile)
  6. if not lb_ok then
  7.     MessageBox('失败', 'OpenFile 返回 FALSE')
  8.     return
  9. end if
  10. // Write 等价于 info,其余用法一致
  11. uo_logger.Write('老接口兼容日志:订单 %d 处理完成', 10086)
  12. uo_logger.CloseFile()
  13. MessageBox('已写入', '用 OpenFile/Write/CloseFile 兼容写法完成')
复制代码




四、uo_timer 函数全貌

方法 / 事件说明
start(long mesc)启动定时器,每 mesc 毫秒触发本对象的 timer 事件
start(long mesc, readonly powerobject bindObject)同上,但把触发路由到 bindObjecttimer 事件(让别的对象来响应)
stop()停止定时器
event timer()周期到了自动触发的事件,重复逻辑写在这里

注意:uo_timer 是「线程异步」定时器。它在后台线程触发 timer 事件,所以事件里不要直接操作 UI 控件(dw_1.Retrieve、文本框赋值等)——跨线程操作界面是 PB 红线,应通过事件/Post 回到 UI 线程再做。只做「写日志、算数值、调无界面的后台对象」这类事是安全的。




五、uo_timer 典型用法

5.1 在窗口里放一个定时器并周期触发
前置:窗口 w_main 上已放入 uo_timer 对象,命名为 iuo_timer(或声明实例变量 uo_timer iuo_timer)。
步骤:1) 在 iuo_timertimer 事件里贴入「事件代码」;2) 在窗口按钮 cb_start 的 clicked 里贴入「启动代码」;3) 在 cb_stop 里贴入「停止代码」;4) 运行窗口点开始,2 秒一次看到日志与弹窗。

启动代码(按钮 cb_start 的 clicked):
  1. // 示例输入:定时周期(毫秒),这里 2 秒一次
  2. long ll_interval
  3. ll_interval = 2000
  4. // 如果用的是实例变量而非窗体上的对象,先 create
  5. iuo_timer = create uo_timer
  6. // 启动:每 ll_interval 毫秒触发一次 iuo_timer 的 timer 事件
  7. iuo_timer.start(ll_interval)
  8. MessageBox('定时器已启动', '每 ' + string(ll_interval) + ' 毫秒触发一次')
复制代码

停止代码(按钮 cb_stop 的 clicked):
  1. // 示例输入:要停止的定时器实例(必须与启动时同一个)
  2. // iuo_timer 是窗口实例变量,已在启动时使用
  3. if isvalid(iuo_timer) then
  4.     iuo_timer.stop()
  5.     destroy iuo_timer
  6. end if
  7. MessageBox('定时器已停止', '周期任务结束')
复制代码

timer 事件代码(写在 iuo_timer 的 timer 事件里):
  1. // 实例变量 il_tick 计数触发次数(在窗口/对象里声明:long il_tick = 0)
  2. il_tick = il_tick + 1
  3. // 每次触发写一条日志(uo_timer 在后台线程,写文件日志是安全的)
  4. uo_logger.info('定时任务第 %d 次触发,执行心跳检查', il_tick)
  5. // 仅为演示可观察:正式环境请删除此行,改为你的业务逻辑
  6. MessageBox('定时触发', '第 ' + string(il_tick) + ' 次')
复制代码

5.2 把触发路由到别的对象(bindObject 重载)
前置同上。如果你不想单独建 uo_timer 事件,也可以让「窗口自己的 timer 事件」来响应:把窗口 this 作为 bindObject 传进去。
  1. // 示例输入:周期(毫秒)
  2. long ll_interval
  3. ll_interval = 1000
  4. // 把触发路由到当前窗口(this)的 timer 事件,而不是 iuo_timer 的
  5. iuo_timer = create uo_timer
  6. iuo_timer.start(ll_interval, this)
  7. MessageBox('已启动', '触发将走本窗口的 timer 事件')
复制代码

窗口 w_main 的 timer 事件里就可以写周期逻辑了(用法同上,写日志、算数、别碰 UI)。




六、边界情况与常见坑


  • open 不判断返回值:路径不存在、目录没建、文件被别的进程占用,open 都会静默返回 FALSE,之后 info 写啥都不落盘。所有 open 都配 if not lb_ok then ... 判断。



  • printf 占位符与参数要匹配%s 配字符串、%d 配整数,数量也要一致。写成 uo_logger.info('订单 %d', ls_user)(%d 却传字符串)属于 C printf 的未定义行为,多半乱码甚至崩。一个坑:long 用 %d 没问题,但 double 要用 %f,小数别套错。



  • 中文乱码:没 SetUtf8(true) 时按 ANSI 落盘,在 GBK 系统上中文可能变成问号。发布到不同语言环境前一定先确认编码。



  • 不 Flush 看不到最新日志:日志有内存缓冲,Flush() 才立刻落盘。程序异常退出或你想实时 tail 文件时,关键节点后补 Flush()



  • 跨线程操作 UI 是红线:uo_timer 跑在后台线程,timer 事件里直接 dw_1.Retrieve()sle_1.text = ... 会出问题。正确做法:在 timer 事件里只做无界面工作(写日志、算数、调后台对象),需要用界面的部分通过 this.event trigger 事件名()Post 回 UI 线程执行(呼应 Day10 uo_thread 的跨线程红线)。



  • 多个定时器互相独立:你可以建 iuo_timer_aiuo_timer_b 各跑各的周期,互不影响;停的时候要分别 stop() 对应的实例,别漏掉。



  • 析构会自动 stop,但业务清理要靠自己:uo_timer 销毁时析构函数自动 stop(),可它不会帮你保存状态、写「停止日志」。需要在停止时落盘记录什么,自己在 stop() 之前显式写。



  • 全局 uo_logger 是单例:直接用全局 uo_logger 只会写一个文件。如果一个程序要同时写「业务日志」和「错误日志」两个文件,用 create uo_logger 建独立实例分别 open





七、与相近方案对比

7.1 日志方案对比

方案优点缺点适用
MessageBox 调试零成本、立刻看到阻塞界面、不留痕、生产环境用不了临时排错
自己写文件函数完全可控要自己处理打开/缓冲/编码/滚动不想引入组件
老 uo_logfile兼容旧代码功能少、无分级/滚动历史工程
uo_logger分级 + 按天/按大小滚动 + UTF-8 + 零依赖需 PBIDEA 运行库正式项目首选


7.2 定时方案对比

方案线程数量与 UI 冲突适用
窗口 Timer() 函数UI 线程每窗口 1 个易和 DataWindow Timer 控件冲突简单单一定时
Windows SetTimer APIUI 线程需自己管同上不引组件时
uo_thread 周期任务后台线程灵活不冲突重活、要异步
uo_timer后台线程多个不冲突轻量心跳/轮询


结论:轻量、要跑多个、不想占 UI 的定时,直接用 uo_timer;重活、要和数据/界面深度交互的,配合 uo_thread 丢后台。




八、进阶扩展


  • 日志级别随环境升降:开发时 debug/trace 全开,发布时只留 warn/error,既保留排错信息又不刷屏。
  • 定时 + 日志组合:在 uo_timer 的 timer 事件里写 uo_logger.info('心跳 %d', il_tick),就能得到一份「程序是否还活着」的运行轨迹,出事了直接翻日志。
  • 日志归档:按大小/日期滚动产生的历史日志,可用 Day16 讲的 uo_zip 定期打包压缩,避免散落一堆小文件。
  • 多文件日志:用 create uo_logger 建两个实例,一个写业务、一个写错误,故障排查时一眼分开。


把「弹 MessageBox 排错」换成「uo_logger 写日志 + uo_timer 做周期任务」,你的程序会从「人盯着才跑得明白」变成「自己留下运行档案」。这两个组件都很小,但养成习惯后,定位和复盘问题的成本会大幅下降。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-8-30 12:44 , Processed in 0.036039 second(s), 8 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表