祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 31|回复: 1

[PBIDEA] PBIDEA:uo_eventlog 事件日志读取全解 —— 一个方法名三重重载、静默降级与截断判据(PB10 基准 · PB12.5 实测)

[复制链接]

[PBIDEA] PBIDEA:uo_eventlog 事件日志读取全解 —— 一个方法名三重重载、静默降级与截断判据(PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

3678

积分

PBAI

积分
3678
贡献
在线时间
小时
昨天 08:02 | 显示全部楼层 |阅读模式

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

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

×
PBIDEA:uo_eventlog 事件日志读取全解 —— 一个方法名三重重载、静默降级与截断判据(PB10 基准 · PB12.5 实测)

【版本声明】
- 本文以 PBIDEA 当前最新版本为准(版本源由维护方统一核验),并对照随版发布的源码整理。
- 核验日期:2026-10-13;当次核验的 PBIDEA 核心运行库:18,133,504 字节 / 2572 个导出,md5 4437a43b3e40…(PB 12.5 轨为 18,129,920 字节 / 2548 个导出,md5 123f53b12c49…)。
- 版本识别口径:逐项解析 PE 导出表(文件体积、目录名都不能作为判据)。
- 源码核验:随版发布的源码导出目录共 8 个库、575 个 .sru,本文所引 uo_eventlog 声明即取自其中的 websuite.pbl 导出源码。
- 适用平台:Windows XP 及以上(XP / 7 / 10 / 11 均可);PowerBuilder 8 及以上。本文的实机验证环境为 Windows 10 + PB 10 与 PB 12.5。
- PBIDEA 版本会持续更新,本文只对上述核验时点的最新版本负责;后续若行为有变,以新版为准。
阅读说明

- 适用版本:一律以顶部【版本声明】为准;PB 侧写法 PB 8 及以上通用,本文以 PowerBuilder 10 为语法基准,代码在 PB 10 与 PB 12.5 两条轨道上各自实机跑通并逐字节核对读数。
- 涉及对象:uo_eventlog(PBIDEA 自定义非可视对象,随 PBIDEA 主运行库一起分发)。
- 支持数据库:本文不涉及任何数据库。
- 操作系统与环境要求:Windows XP 及以上;需安装 PBIDEA 运行库(PbIdea.dll 必须在可执行文件同级目录或 PATH 中,否则 create uo_eventlog 直接抛 PB runtime error: Error opening DLL library PbIdea.dll,本文实测复现过这一条);PB 侧需引入 PBIDEA 的 websuite.pbl。
- 难度系数:★★☆☆☆(调用本身极其简单,只有 1 个方法名;但「读不全」和「日志名被静默吞掉」两个坑会安静地给你错数据,第四、五节必须看)
- 前置知识:会 create / destroy 非可视对象、会用 uo_json 的 Get 取值即可。
- 其它阅读说明:本文所有读数都来自 PB10 与 PB12.5 双轨实机运行,不是文档转述。凡是「实测」二字后面跟数字的,都是本机跑出来的原值;凡是本机没测到的边界,都会明确写「未实测」。




一、先说结论:这个对象能做什么、不能做什么

uo_eventlog 是 PBIDEA 用来读 Windows 事件日志的封装。它小得出人意料——整个对象对外只有一个方法名:EventLogToJson,带三个重载。除这三条签名外,PowerScript 侧没有任何其它公开方法。

先把能力边界划清楚,这决定了后面所有写法:

能力实机结果说明
读取整本日志(不带过滤)⚠️ 只给「最近约 1 万条」不是全量,见第五节
按单个事件 ID 过滤✅ 可用但参数是 int,16 位,见第六节
按事件 ID 数组过滤✅ 可用同上受 16 位限制
按时间区间过滤❌ 无参数三个重载都没有时间参数
分页 / 分批取全量❌ 无参数没有任何 offset / page 概念
返回失败原因❌ 只有 boolean想区分「日志名错」和「没记录」只能自己造指纹
写入 / 清除 / 备份日志❌ 没有方法纯只读,一个写方法都没有
拿到事件正文(Message / Description)❌ 返回结构里没有只有 7 个字段,见第三节
非法日志名报错❌ 不报错,静默回落到 Application全文最危险的坑,见第四节


★一句话总结:它是「读取器」,不是「日志管理器」。 你能做的只有「按名字把一批事件取成 JSON」,剩下的判断(读全了没有、名字对不对、级别怎么算)全部要自己写。

它是 from nonvisualobject 的 NVO、非 autoinstantiate,必须自己 create / destroy:
  1. uo_eventlog luo_el
  2. luo_el = create uo_eventlog
  3. // ... 使用 ...
  4. destroy luo_el
复制代码




二、签名全貌:1 个方法名、3 条签名

下面三条是从 PBIDEA 随版源码 uo_eventlog.sru 的 type prototypes 段原样抄下来的,一条不多、一条不少:
  1. function boolean EventLogToJson(readonly string name, readonly uo_json jsLog) system library "pbidea.dll" alias for "elEventLogToJson"
  2. function boolean EventLogToJson(readonly string name, readonly uo_json jsLog, int filter_id) system library "pbidea.dll" alias for "elEventLogToJson"
  3. function boolean EventLogToJson(readonly string name, readonly uo_json jsLog, int filter_ids[]) system library "pbidea.dll" alias for "elEventLogToJson"
复制代码

三条都指向同一个导出函数 elEventLogToJson,靠参数个数与类型区分重载。

参数语义:

参数类型说明
namereadonly string日志名。支持英文名与其中文别名,大小写不敏感(实测)
jsLogreadonly uo_json结果容器。声明是 readonly,但函数是往里写数据的——实测传入的 uo_json 会被整体填充
filter_idint只保留 eventID 等于该值的事件。int 是 16 位有符号,这是本文第二个大坑,见第六节
filter_idsint 数组保留 eventID 落在数组里的事件;一次可以给多个

关于 readonly:这是 PBIDEA 源码声明的写法,不影响你传一个普通 uo_json 进去。实测三类传参都正常:传已有的 uo_json 实例、传刚 create 的空实例,行为一致。

返回值的真实含义(实测,不是文档说法):

name 取值返回结果条数
合法日志名('Application' / 'System' / 'Security' …)true实际条数
非法日志名('NoSuchLogXYZ')true非 0(回落到 Application)
'Windows PowerShell'(本机真实存在但组件读不到)true0
合法名但该 ID 无匹配(filter_id = 999999)true0
空字符串 ''false0


★注意倒数第二行与第三行:返回 true 不等于「读到了东西」。整张表里唯一能拿到 false 的写法是空名字。所以 EventLogToJson 的返回值只能用来判断「参数名是不是完全没给」,不能用来判断成败。




三、返回结构:正好 7 个字段,顺序固定

jsLog 是一个数组型 uo_json:顶层是数组,每个元素是一个对象。用 ArrCount() 取条数,用 Get(下标, 子对象) 取第 n 条。

单条事件的结构实测如下(用 Get(nIndex, ref strKey, ref any val, ref strType) 逐项读出类型):

序号字段名JSON 类型含义实测样例
1xhnumber本次结果里的序号,1 基,永远从 1 开始1
2nonumber事件记录号(Windows 事件索引),越大越新26544
3timestring发生时间,格式 YYYY-MM-DD HH:MM:SS2026-10-13 06:58:44
4eventIDnumber事件 ID16384
5typenumber级别码4
6typeNamestring级别中文名信息
7sourcestring提供程序(写日志的那个组件)Software Protection Platform Service


没有的字段(重要):没有 Message、没有 Description、没有 User、没有 Category、没有 Task、没有 Keywords、没有 Computer。

也就是说:你拿得到「谁、什么时候、什么级别、什么 ID」,但拿不到「发生了什么事」的正文。 如果你需要事件描述文字,必须自己按 source + eventID 去建映射表,或者绕开这个组件用 Win32 API 自己读。

三个结构上的实测事实,写代码时可以直接依赖:


  • xh 严格递增(System 日志 5878 条逐条校验通过)。
  • no 严格递减(同上),因为顺序就是「从最新往回」。
  • typeName 恒非空(同上),不需要做空值兜底。


取字段的写法(注意 Get 的第二参是引用出参,变量必须先声明好类型):
  1. uo_json luo_item
  2. string ls_no
  3. string ls_time
  4. string ls_id
  5. ajs_root.Get(1, luo_item)          // 取第 1 条(最新那条)
  6. luo_item.Get('no', ls_no)          // 记录号
  7. luo_item.Get('time', ls_time)      // 时间
  8. luo_item.Get('eventID', ls_id)     // 事件 ID
复制代码
★一个容易忽略的细节:Get 出来的数值字段是按 string 接的(实测把 no / eventID / type 取到 string 变量里都能正常拿到 '26544'、'16384'、'4')。要比较大小就自己 Long() 一次。不要直接用字符串比较,原因见第七节那个 string(0) 的坑。




四、★最大的坑:非法日志名静默降级到 Application

这是本文最值钱的一条实测结论,也是我在写正文前做签名探测时意外撞到的。

现象:把一个根本不存在的日志名喂进去,它不报错、不返回 false、也不返回空,而是安静地给你 Application 日志的内容。

实测三组读数:

调用返回条数
EventLogToJson('Application', js)true10000
EventLogToJson('NoSuchLogXYZ', js)true10000
EventLogToJson('System', js)true5878


'NoSuchLogXYZ' 的条数与 'Application' 完全相同。光看条数还不够,本轮进一步比对了「首条的记录号 + source + 时间」三处指纹,三处全部相同——这才确认它不是「恰好条数一样」,而是真的回落到同一本日志。

更隐蔽的一点:连读两本不同的日志之后,再喂非法名,拿到的仍然是上一次那本。实测序列是:
  1. 系统日志 = 5878 条  →  非法名 = 10000 条(= Application)  →  系统日志 = 5878 条
复制代码

也就是说它的回落目标不是固定的「永远是 Application」,而是上一次成功读取的那本日志。

唯一的例外是空名字:EventLogToJson('', js) 返回 false、条数 0。这是全部实测里唯一能拿到 false 的写法。

还有一个反例是「存在但读不到」:本机确实有 Windows PowerShell 这本日志(用 PowerShell 的 Get-EventLog -LogName 'Windows PowerShell' 能列出 4600 多条),但组件读它返回 true 且条数为 0,同进程连续两次都是 0,稳定可重现。也就是说条数 0 也可能是「日志存在但我读不到」,而不是「日志是空的」。

工程结论:name 这个参数,你必须自己保证它是对的。任何来自配置、界面下拉框、用户输入的日志名,都要先在允许列表里过一遍。写成方法就是这样:
  1. public function boolean of_name_ok (string as_log);
  2. // as_log  待校验的日志名
  3. // 返回    true = 在本组件确认可读的日志名白名单内
  4. // 备注    非法名会被静默回落到 Application,所以白名单必须由调用方自己守住
  5. string ls_allowed
  6. string ls_use
  7. ls_allowed = '|application|system|security|setup|'
  8. ls_use = Lower(Trim(as_log))
  9. if Pos(ls_allowed, '|' + ls_use + '|') = 0 then
  10.         return false
  11. end if
  12. return true
  13. end function
复制代码

实测(双轨一致):Application → true、system → true(小写也认)、NoSuchLogXYZ → false、空串 → false。这个方法本体与本文其余代码一起进了第九节的工具类,双轨实机跑通。

而如果你一定要「读到什么就信什么」,那就必须加指纹校验:比对「首条 no + source + time」是否与期望那本日志一致。本文第九节的 of_check_log 就是这个思路——它会在读完之后再读一次 Application,用指纹比对来自证「我刚才读的是不是 Application」。




五、★读不全:它不是「全量读取」,而是「最近约 1 万条」

第二危险的一条。EventLogToJson 不带过滤读取时,返回的不是整本日志。

实测对照(PB 侧读数 vs 用 PowerShell 的 Get-EventLog 取到的真实总量):

日志真实总条数PB 实读条数是否完整
System58785878✅ 完整
Application2654610000❌ 截断
Security2834010006 ~ 10009❌ 截断


规律清楚:当总量小于约 1 万条时读到全部;超过就只给最近约 1 万条。

关键在于最后那句「约」。同一份二进制、同一条命令、同一轨道,在 06:53 ~ 07:54 这一小时里我读到过这些值:

时间Application 读数首条记录号
06:531003526544
07:171003726544
07:26 起(连测 3 次)1000026546
07:541000226548


同一时间窗里 Security 的读数也在 10005 ~ 10009 之间浮动。

★所以:「上限约 1 万条」是量级,不是可以依赖的常量。 你在不同机器、不同日志内容上拿到的值都可能不同。任何「条数等于某个期望值就算读全」的代码,都是不可靠的。

那么怎么判断读全了没有?用这个公式

当且仅当 ArrCount() = Number(首条的 no) 时,这次读到的是完整日志。

原理很简单:no 是 Windows 事件日志的全局记录号,从日志建立开始单调递增、永不回退。如果本次读取没有被截断,那么「最新的那条记录号」就等于「日志里的总条数」;一旦被截断,首条的 no 就会大于本次返回的条数。

实机验证:

日志首条 no返回条数判定与真实情况是否相符
System58785878COMPLETE✅ 相符(真实 5878)
Application2654610000TRUNCATED✅ 相符(真实 26546)


这个判据的好处是:它不依赖「1 万」这个数字。上限改成 5 万也好、版本升级后取消上限也好,判据本身依然成立。

一个能直接用的体检方法
  1. public function string of_check_log (string as_log);
  2. // as_log  要体检的日志名
  3. // 返回    COMPLETE / TRUNCATED / EMPTY 之一,附条数与首条记录号
  4. //
  5. // 判据:Windows 事件日志的记录号 no 全局单调递增且不回退,
  6. //       未被截断时「最新一条的 no」恰好等于「日志总条数」。
  7. uo_eventlog luo_el
  8. uo_json luo_js
  9. uo_json luo_item
  10. string ls_no
  11. long ll_cnt
  12. boolean lb_ret
  13. luo_el = create uo_eventlog
  14. luo_js = create uo_json
  15. lb_ret = luo_el.EventLogToJson(as_log, luo_js)
  16. ll_cnt = luo_js.ArrCount()
  17. if lb_ret = false or ll_cnt = 0 then
  18.         destroy luo_js
  19.         destroy luo_el
  20.         return 'EMPTY rows=' + string(ll_cnt)
  21. end if
  22. luo_js.Get(1, luo_item)
  23. luo_item.Get('no', ls_no)
  24. destroy luo_js
  25. destroy luo_el
  26. if Long(ls_no) = ll_cnt then
  27.         return 'COMPLETE rows=' + string(ll_cnt) + ' headNo=' + ls_no
  28. end if
  29. return 'TRUNCATED rows=' + string(ll_cnt) + ' headNo=' + ls_no
  30. end function
复制代码

实测输出:
  1. Application : TRUNCATED rows=10000 headNo=26546
  2. System      : COMPLETE rows=5878 headNo=5878
复制代码

顺带一提:读取本身几乎不花时间

用墙钟量过一轮(同一个进程,只改读取内容):

场景墙钟耗时
基线(只启动 PBVM,不读日志)7.79 秒
读 System(5878 条)8.44 秒
读 Application(1 万条)8.10 秒
读带过滤(6005)8.14 秒


大部分时间花在进程启动上,读日志本身不到 1 秒。 所以「多读几次换正确性」在性能上是划算的——本文第九节的指纹自证就多读了一次 Application,代价可以忽略。




六、过滤读取:int 是 16 位,32768 以上的事件 ID 永远读不到

三条签名里有两条带过滤参数,参数类型都是 int。在 PowerScript 里 int 是16 位有符号整数,范围 −32768 ~ 32767。这不是一个可以忽略的实现细节,它直接决定了「哪些事件 ID 能查、哪些永远查不到」。

实测:越界的过滤值全部静默变成空集

在 Application 日志上扫了一圈边界:

filter_id返回结果条数说明
0true400真有一批 eventID = 0 的记录
16384true10009正常命中
32767true0上边界
32768true0越界,静默空集
40000true0越界,静默空集


再验证一次「越界值到底被怎么处理」:把 40000 装进 int 变量再打印出来,得到的是 -25536(40000 − 65536 的二进制补码解释),import 0 错、rebuild 0 错、运行不报任何错、结果就是 0 条。用 int 数组装 40000 同样得到 -25536、同样 0 条;把字面量 40000 直接写在调用里也一样是 0 条。
★所以:「事件 ID 大于 32767 的记录,用这个组件永远过滤不出来」,而且它一个字都不提示你。 常见的高位 ID(各类 40000+ 的应用日志 ID、部分审核事件)全部落在这一侧。想查它们,只能用不带过滤的读取(受 1 万条上限约束),或者自己用 Win32 API 读。

顺带补一个「传参类型放宽」的实测:把一个 long 变量直接传给 int 参数,编译器不报错,运行时按低位取值——传 6005(在范围内)能正确拿到 18 条。也就是说编译期给不了你任何越界保护,int 这个类型是唯一的约束。

过滤路径的其它实测行为

场景实测结果
filter_id = 999999(合法 int、但日志里没有这个 ID)返回 true、0 条
过滤 System 日志 eventID = 600518 条
手工遍历 System 数 eventID = 6005 的条数18 条 → 与过滤结果完全一致
过滤 System 日志 ID 数组 {6005, 6006}32 条(6005 的 18 + 6006 的 14)
过滤后的 xh重新从 1 编号(1、2、3…)
过滤后的 no保持原值(5760、4083、3721…),所以过滤结果的 no 是不连续的
带过滤的条数也会浮动(Security 同一 ID 两次读到 10007 / 10009)


★注意最后两行合起来看:过滤结果里的 xh 是「结果内序号」,no 是「日志内位置」,两者不是一回事。 判断完整性时用的是 no,别拿 xh 去比对。

另外,filter = 0 是个特例:它返回 400 条,比不带过滤时的读数(10000)少很多,却比「手工数出来的 eventID = 0 条数」多——手工数得到 118 条,过滤得到 400 条。这是口径差异:过滤走的是日志自己的索引,手工数的是「落在那 1 万条窗口里的 0 号事件」。两者都对,只是问的不是同一个问题。

而 0 号事件的手工统计还有一个隐藏陷阱,见下一节。




七、级别映射:type 与 typeName,以及 string(0) 的坑

type 是数值级别码,typeName 是它的中文名。实测对照(全部取自真实日志、typeName 逐项确证):

typetypeName说明
0未知本机 Application 里真有一批 eventID = 258、type = 0 的记录
1错误
2警告实测 System 里 eventID = 10016 的正是这一类
4信息最常见,Application 里 1 万条中有 9927 条是这个
8成功审核实测 Security 里 eventID = 4798
16失败审核实测 Security 里 eventID = 4625


本机 Application 日志(读数 10000 条)的级别分布实测为:
  1. rows=10000  err=81  warn=0  info=9890
复制代码

★注意「三类之和」不等于总条数:81 + 0 + 9890 = 9971,比 10000 少 29 条。这 29 条既不是错误、不是警告、也不是信息——它们是 type = 0(未知)以及其它未纳入统计的级别码。所以如果你要算「错误率 = 错误数 ÷ 总条数」,分母是 10000,而分子只覆盖三种级别,统计口径必须自己说清楚,否则这个比率天生偏小。

eventID = 0 怎么统计:两种写法实测等效

Application 日志里真有一批 eventID = 0 的记录,统计它们时你会面临一个选择:把 eventID 取到字符串里跟 '0' 比,还是转成数值跟 0 比。

网上流传的一种说法是「PowerScript 的 string(0) 返回空串,所以字符串比较会漏掉 0 值」。本机没有复现出这条——PB10 与 PB12.5 双轨实测 string(0) 返回的都是 '0'(长度 1):
  1. string(0)        长度 = 1,值就是 0
  2. String(0,'0')    长度 = 1,值就是 0
  3. ls_plain = '0'   比较结果 = true
复制代码

两轨读数逐字节相同。进一步在真实的 Application 结果上对照两种统计写法:

写法统计出的 eventID = 0 条数
字符串比较:ls_id = '0'117
数值比较:Long(ls_id) = 0117


逐条一致。 所以这一处你不用担心兼容性,两种写法都可以。本文工具类统一用数值比较,理由是级别判定(type)本来就要转数值,风格上保持一致:
  1. string ls_id
  2. long ll_v
  3. luo_item.Get('eventID', ls_id)
  4. ll_v = Long(ls_id)
  5. if ll_v = 0 then
  6.         ll_zero = ll_zero + 1
  7. end if
复制代码
顺带交代一句边界:string(0) 这一条只在 PB10 与 PB12.5 两条轨道上验过,其它 PB 版本未实测。如果你在更老的 PB 上遇到「0 值统计少了」,可以在自己的环境里用上面那行探针复验一次,成本极低。




八、★uo_json 的子对象不能跨对象传参,会直接崩进程

这一条不是 uo_eventlog 的问题,是 uo_json 的问题,但因为读事件日志必须用它,所以放在这里。

现象:把 ajs_root.Get(i, luo_item) 取到的子对象,作为参数传给另一个函数——进程直接崩溃,exit code 3221225477(0xC0000005,访问违例),没有任何 PowerBuilder 错误信息,TRY-CATCH 也兜不住(它不是异常,是访问违例)。

隔离过程(一类一方法、一例一次独立运行):

用例内容结果
p34父对象内 Get 出子对象并读字段✅ PASS
p35把子对象按值传给另一个函数❌ 崩(0xC0000005)
p36把子对象按 readonly 传给另一个函数❌ 崩
p37把根对象传给另一个函数✅ PASS
p38传根对象 + 下标,在被调方内部 Get✅ PASS


★结论:uo_json 只有「根对象」可以跨对象传递,子对象一旦离开产生它的那个对象,就是雷。

所以工具类的签名应该这样设计——永远传根对象 + 下标,不要传子对象:
  1. // ❌ 不要这样:接收方拿到的子对象会崩
  2. public function string of_item_line (uo_json ajs_item)
  3. // ✅ 要这样:传根 + 下标,接收方自己 Get
  4. public function string of_item_line (uo_json ajs_root, long al_index);
复制代码

对应实现:
  1. public function string of_item_line (uo_json ajs_root, long al_index);
  2. // ajs_root  事件日志根对象(必须传根对象,不能传 Get() 出来的子对象)
  3. // al_index  结果内序号,1 基,范围 1 ~ ajs_root.ArrCount()
  4. // 返回      xh | no | time | id | type | typeName | source
  5. uo_json luo_item
  6. string ls_xh
  7. string ls_no
  8. string ls_time
  9. string ls_id
  10. string ls_type
  11. string ls_tn
  12. string ls_src
  13. ajs_root.Get(al_index, luo_item)
  14. luo_item.Get('xh', ls_xh)
  15. luo_item.Get('no', ls_no)
  16. luo_item.Get('time', ls_time)
  17. luo_item.Get('eventID', ls_id)
  18. luo_item.Get('type', ls_type)
  19. luo_item.Get('typeName', ls_tn)
  20. luo_item.Get('source', ls_src)
  21. return ls_xh + ' | ' + ls_no + ' | ' + ls_time + ' | id=' + ls_id + ' | type=' + ls_type + ' | ' + ls_tn + ' | ' + ls_src
  22. end function
复制代码




九、封装成一个能直接用的工具类

把上面所有结论收拢,得到一个 nvo_eventlog_demo,7 个方法,覆盖「日志名校验、读、按 ID 读、按 ID 数组读、取单行、统计级别、体检」七件事。

9.1 日志名白名单校验

第四节给出的 of_name_ok 就是这一件,直接收进工具类:
  1. public function boolean of_name_ok (string as_log);
  2. // as_log  待校验的日志名
  3. // 返回    true = 在本组件确认可读的日志名白名单内
  4. // 备注    非法名会被静默回落到 Application,所以白名单必须由调用方自己守住
  5. string ls_allowed
  6. string ls_use
  7. ls_allowed = '|application|system|security|setup|'
  8. ls_use = Lower(Trim(as_log))
  9. if Pos(ls_allowed, '|' + ls_use + '|') = 0 then
  10.         return false
  11. end if
  12. return true
  13. end function
复制代码

实测:Application → true、system → true(小写也认)、NoSuchLogXYZ → false、空串 → false,双轨一致。

9.2 基础读取(含指纹摘要)
  1. public function boolean of_read_basic (string as_log, ref uo_json ajs_out, ref string as_info);
  2. // as_log   要读取的日志名,必须自己保证合法(非法名会静默回落到上一本日志)
  3. // ajs_out  输出:事件日志结果数组,调用方负责 create 和 destroy
  4. // as_info  输出:本次读取的条数摘要
  5. uo_eventlog luo_el
  6. uo_json luo_item
  7. boolean lb_ret
  8. long ll_cnt
  9. string ls_no
  10. luo_el = create uo_eventlog
  11. lb_ret = luo_el.EventLogToJson(as_log, ajs_out)
  12. ll_cnt = ajs_out.ArrCount()
  13. ls_no = ''
  14. if ll_cnt > 0 then
  15.         // 取第一条也就是最新一条的记录号,用它和条数比对即可判断是否被截断
  16.         ajs_out.Get(1, luo_item)
  17.         luo_item.Get('no', ls_no)
  18. end if
  19. as_info = 'log=' + as_log + ' ret=' + string(lb_ret) + ' rows=' + string(ll_cnt) + ' headNo=' + ls_no
  20. destroy luo_el
  21. return lb_ret
  22. end function
复制代码

9.3 按单个事件 ID 过滤
  1. public function boolean of_read_by_id (string as_log, long al_eventid, ref uo_json ajs_out, ref string as_info);
  2. // as_log     日志名
  3. // al_eventid 只保留该事件 ID 的记录;★必须落在 -32768 ~ 32767 内,超出会静默变空集
  4. // ajs_out    输出:过滤后的事件数组
  5. // as_info    输出:摘要
  6. uo_eventlog luo_el
  7. boolean lb_ret
  8. long ll_cnt
  9. luo_el = create uo_eventlog
  10. lb_ret = luo_el.EventLogToJson(as_log, ajs_out, al_eventid)
  11. ll_cnt = ajs_out.ArrCount()
  12. as_info = 'log=' + as_log + ' eventid=' + string(al_eventid) + ' ret=' + string(lb_ret) + ' rows=' + string(ll_cnt)
  13. destroy luo_el
  14. return lb_ret
  15. end function
复制代码

用它可以取开关机记录——System 日志的 6005 是「事件日志服务已启动」(开机)、6006 是「事件日志服务已停止」(关机):
  1. long ll_cnt
  2. string ls_info
  3. uo_json luo_js
  4. luo_js = create uo_json
  5. of_read_by_id('System', 6005, luo_js, ref ls_info)     // 实测 18 条
  6. ll_cnt = luo_js.ArrCount()
  7. destroy luo_js
复制代码

实测读数:log=System eventid=6005 ret=true rows=18。

9.4 按事件 ID 数组过滤
  1. public function boolean of_read_by_ids (string as_log, int ai_ids[], ref uo_json ajs_out, ref string as_info);
  2. // as_log   日志名
  3. // ai_ids   事件 ID 数组,一次取多个;元素类型是 int,同样受 16 位限制
  4. // ajs_out  输出:过滤后的事件数组
  5. // as_info  输出:摘要
  6. uo_eventlog luo_el
  7. boolean lb_ret
  8. long ll_cnt
  9. long ll_i
  10. string ls_ids
  11. luo_el = create uo_eventlog
  12. lb_ret = luo_el.EventLogToJson(as_log, ajs_out, ai_ids)
  13. ll_cnt = ajs_out.ArrCount()
  14. ls_ids = ''
  15. for ll_i = 1 to UpperBound(ai_ids)
  16.         ls_ids = ls_ids + string(ai_ids[ll_i]) + ','
  17. next
  18. as_info = 'log=' + as_log + ' ids=' + ls_ids + ' ret=' + string(lb_ret) + ' rows=' + string(ll_cnt)
  19. destroy luo_el
  20. return lb_ret
  21. end function
复制代码

一次取开机 + 关机:
  1. int li_ids[2]
  2. string ls_info
  3. uo_json luo_js
  4. li_ids[1] = 6005
  5. li_ids[2] = 6006
  6. luo_js = create uo_json
  7. of_read_by_ids('System', li_ids, luo_js, ref ls_info)    // 实测 32 条
  8. destroy luo_js
复制代码

实测读数:log=System ids=6005,6006, ret=true rows=32。

9.5 按级别统计
  1. public function string of_level_count (uo_json ajs_root, ref long al_err, ref long al_warn, ref long al_info);
  2. // ajs_root  事件日志根对象(传根对象,不要传子对象)
  3. // al_err    输出:type=1 错误条数
  4. // al_warn   输出:type=2 警告条数
  5. // al_info   输出:type=4 信息条数
  6. // 返回      一行摘要
  7. uo_json luo_item
  8. string ls_type
  9. long ll_i
  10. long ll_cnt
  11. ll_cnt = ajs_root.ArrCount()
  12. al_err = 0
  13. al_warn = 0
  14. al_info = 0
  15. for ll_i = 1 to ll_cnt
  16.         ajs_root.Get(ll_i, luo_item)
  17.         ls_type = '0'
  18.         luo_item.Get('type', ls_type)
  19.         if Long(ls_type) = 1 then al_err = al_err + 1
  20.         if Long(ls_type) = 2 then al_warn = al_warn + 1
  21.         if Long(ls_type) = 4 then al_info = al_info + 1
  22. next
  23. return 'rows=' + string(ll_cnt) + ' err=' + string(al_err) + ' warn=' + string(al_warn) + ' info=' + string(al_info)
  24. end function
复制代码

实测(Application,同一批结果内统计):rows=10000 err=81 warn=0 info=9890。

9.6 完整性 + 回落体检
  1. public function string of_check_log (string as_log);
  2. // as_log  要体检的日志名
  3. // 返回    COMPLETE / TRUNCATED / FALLBACK / EMPTY 之一,附条数与首条记录号
  4. //
  5. // COMPLETE  = 读到全量(首条 no 等于条数)
  6. // TRUNCATED = 只读到最近一批(首条 no 大于条数)
  7. // FALLBACK  = 读到的其实是 Application(非法名被静默回落)
  8. // EMPTY     = 返回 false 或 0 条
  9. uo_eventlog luo_el
  10. uo_json luo_js
  11. uo_json luo_js2
  12. uo_json luo_item
  13. string ls_no
  14. string ls_xh
  15. string ls_src
  16. string ls_time
  17. string ls_no2
  18. string ls_src2
  19. long ll_cnt
  20. long ll_cnt2
  21. boolean lb_ret
  22. luo_el = create uo_eventlog
  23. luo_js = create uo_json
  24. lb_ret = luo_el.EventLogToJson(as_log, luo_js)
  25. ll_cnt = luo_js.ArrCount()
  26. if lb_ret = false or ll_cnt = 0 then
  27.         destroy luo_js
  28.         destroy luo_el
  29.         return 'EMPTY rows=' + string(ll_cnt) + ' ret=' + string(lb_ret)
  30. end if
  31. luo_js.Get(1, luo_item)
  32. luo_item.Get('no', ls_no)
  33. luo_item.Get('source', ls_src)
  34. luo_item.Get('time', ls_time)
  35. // 读的不是 Application 时,再读一次 Application 做指纹比对,用来识破静默回落
  36. if Lower(as_log) <> 'application' then
  37.         luo_js2 = create uo_json
  38.         luo_el.EventLogToJson('Application', luo_js2)
  39.         ll_cnt2 = luo_js2.ArrCount()
  40.         if ll_cnt2 > 0 then
  41.                 luo_js2.Get(1, luo_item)
  42.                 luo_item.Get('no', ls_no2)
  43.                 luo_item.Get('source', ls_src2)
  44.                 if ls_no2 = ls_no and ls_src2 = ls_src then
  45.                         destroy luo_js2
  46.                         destroy luo_js
  47.                         destroy luo_el
  48.                         return 'FALLBACK rows=' + string(ll_cnt) + ' name=' + as_log
  49.                 end if
  50.         end if
  51.         destroy luo_js2
  52. end if
  53. if Long(ls_no) = ll_cnt then
  54.         destroy luo_js
  55.         destroy luo_el
  56.         return 'COMPLETE rows=' + string(ll_cnt) + ' headNo=' + ls_no
  57. end if
  58. luo_js.Get(ll_cnt, luo_item)
  59. luo_item.Get('xh', ls_xh)
  60. destroy luo_js
  61. destroy luo_el
  62. return 'TRUNCATED rows=' + string(ll_cnt) + ' headNo=' + ls_no + ' tailXh=' + ls_xh + ' headSrc=' + ls_src + ' headTime=' + ls_time
  63. end function
复制代码

实测输出(同一次运行):
  1. Application  : TRUNCATED rows=10000 headNo=26546 tailXh=10000 headSrc=Software Protection Platform Service headTime=2026-10-13 06:58:44
  2. System       : COMPLETE rows=5878 headNo=5878
  3. NoSuchLogXYZ : FALLBACK rows=10000 name=NoSuchLogXYZ
复制代码

★最后一行是全文的浓缩:你喂了一个不存在的名字,它给了你 1 万条真实数据、返回 true、一声不响。 没有指纹校验的代码,会把 NoSuchLogXYZ 的核心事件当成 Application 的事件报给用户。

9.7 一个最小窗口

把 7 个方法接到一个窗口上,4 个按钮,每个按钮以 MessageBox 结尾(便于观察):
  1. // 窗口实例变量(在 declare 区声明)
  2. // nvo_eventlog_demo inuo_el
  3. // ---- 窗口 create 事件 ----
  4. inuo_el = create nvo_eventlog_demo
  5. // ---- 窗口 destroy 事件 ----
  6. destroy inuo_el
  7. // ---- 按钮 1:读取 Application 日志 ----
  8. uo_json luo_js
  9. string ls_info
  10. long ll_cnt
  11. long ll_i
  12. luo_js = create uo_json
  13. inuo_el.of_read_basic('Application', luo_js, ref ls_info)
  14. ll_cnt = luo_js.ArrCount()
  15. Parent.mle_output.text = 'of_read_basic -> ' + ls_info + '~r~n'
  16. for ll_i = 1 to 5
  17.         Parent.mle_output.text = Parent.mle_output.text + inuo_el.of_item_line(luo_js, ll_i) + '~r~n'
  18. next
  19. destroy luo_js
  20. MessageBox('1、读取 Application', ls_info)
  21. // ---- 按钮 2:按级别统计 ----
  22. uo_json luo_js
  23. string ls_info
  24. string ls_sum
  25. long ll_err
  26. long ll_warn
  27. long ll_info
  28. luo_js = create uo_json
  29. inuo_el.of_read_basic('Application', luo_js, ref ls_info)
  30. ls_sum = inuo_el.of_level_count(luo_js, ref ll_err, ref ll_warn, ref ll_info)
  31. Parent.mle_output.text = ls_info + '~r~n' + ls_sum
  32. destroy luo_js
  33. MessageBox('2、级别统计', '错误=' + string(ll_err) + ' 警告=' + string(ll_warn) + ' 信息=' + string(ll_info))
  34. // ---- 按钮 3:按 ID 数组过滤 ----
  35. int li_ids[2]
  36. uo_json luo_js
  37. string ls_info
  38. li_ids[1] = 6005
  39. li_ids[2] = 6006
  40. luo_js = create uo_json
  41. inuo_el.of_read_by_ids('System', li_ids, luo_js, ref ls_info)
  42. Parent.mle_output.text = ls_info
  43. destroy luo_js
  44. MessageBox('3、按 ID 数组过滤', ls_info)
  45. // ---- 按钮 4:完整性体检 ----
  46. string ls_1
  47. string ls_2
  48. string ls_3
  49. ls_1 = inuo_el.of_check_log('Application')
  50. ls_2 = inuo_el.of_check_log('System')
  51. ls_3 = inuo_el.of_check_log('NoSuchLogXYZ')
  52. Parent.mle_output.text = 'Application : ' + ls_1 + '~r~n' + 'System : ' + ls_2 + '~r~n' + 'NoSuchLogXYZ : ' + ls_3
  53. MessageBox('4、完整性体检', Parent.mle_output.text)
复制代码
提醒一处 PB 的书写习惯:在控件自己的事件里引用父窗口上的控件,要走 Parent.(上面按钮脚本里的 Parent.mle_output.text 就是这个用法);在窗口自己的脚本里则可以直接写控件名 mle_output.text。写混了不一定报错,但运行时可能拿不到你以为的那个控件。另外窗口 destroy 事件里要 destroy inuo_el,否则工具对象泄漏。




十、坑清单


  • ★非法日志名不报错、静默回落到「上一次成功读取的那本日志」。实测 'NoSuchLogXYZ' 返回 true 且条数与 Application 完全相同,no/source/time 三处指纹也全同。必须自己做名字白名单或指纹校验。
  • ★返回值 true 不代表读到了东西。'Windows PowerShell' 返回 true 但 0 条;filter_id = 999999 返回 true 但 0 条。整轮实测里只有空名字 '' 能拿到 false。
  • ★不带过滤的读取不是全量,只给「最近约 1 万条」。本机 Application 真实 26546 条、只读到 10000 条。
  • ★「约 1 万条」不是常量。同一二进制、同一命令,半小时内 Application 从 10035 变到 10000;Security 在 10005 ~ 10009 之间浮动。别用条数判断完整性。
  • ★完整性判据只有一个:ArrCount() = Number(首条的 no)。成立即完整,不成立即被截断。该判据与 1 万这个数字无关。
  • ★filter_id / filter_ids 是 int,16 位有符号,上限 32767。32768 / 40000 一律静默变空集(返回 true、0 条)。事件 ID 大于 32767 的记录永远过滤不出来。
  • ★越界值不会报错,而是按补码被截断:40000 存进 int 得 -25536,import 0 错、rebuild 0 错、运行无错。
  • ★long 变量传给 int 参数编译器不报错(实测传 6005 正确命中 18 条),编译期给不了越界保护。
  • ★uo_json 子对象不能跨对象传参,会直接崩进程(exit code 3221225477 / 0xC0000005),无任何错误信息、TRY-CATCH 兜不住。只有根对象可以传;要取子对象内容就传「根 + 下标」,在被调方内部 Get。
  • string(0) 在 PB10 与 PB12.5 上返回的是 '0'(实测),不是空串;因此「字符串比较」与「数值比较」统计 eventID = 0 的结果逐条一致(117 / 117)。这一处不需要写兼容代码。
  • 返回结构只有 7 个字段(xh / no / time / eventID / type / typeName / source),拿不到事件正文,没有 Message / User / Category。
  • xh 与 no 不是一回事:xh 是结果内序号(每次从 1 重编),no 是日志内记录号(全局递增、过滤后仍保持原值)。
  • 过滤后的 no 不连续,是按记录号从新往旧扫出来的顺序。
  • filter = 0 的口径与手工统计不同:过滤得 400 条,手工在那 1 万条窗口里数得 118 条,两者都对,问的不是同一个问题。
  • 同一实例反复调用是「替换」不是「追加」:连续两次读同一日志,第一次和第二次条数相同(实测 first=10035 second=10035),旧内容被整体覆盖,不会累积。
  • Security 日志的读取需要权限,本机在管理员上下文能读到约 1 万条;普通用户上下文未实测。
  • type = 0 的 typeName 是「未知」,不是「错误」。本机 Application 里真有一批 type = 0 的记录(eventID = 258)。
  • 缺 PbIdea.dll 会直接抛运行错误(实测把 DLL 移走复现):PB runtime error: Error opening DLL library PbIdea.dll for external function at line 1 in constructor event of object uo_json。要连 Pbidea_cs.dll / SciLexer.dll / WebBrowser.dll 一起放在可执行文件同级目录。
  • 日志名大小写不敏感(application / system / SYSTEM 均读取成功),不要在这个方向上浪费时间。
  • 中文别名可用(应用程序 / 应用 / 系统 / 安全 / 安装 等),与英文名等价。





十一、PB12.5 差异

本轮一共 23 组关键用例 × 2 条轨道(PB10 / PB12.5)各自独立运行一次,逐条比对输出。

结论:uo_eventlog 的签名、返回值、结构、异常行为在 PB10 与 PB12.5 之间没有发现任何版本差异。 23 例全部 PASS,其中 16 例读数逐字节完全相同;剩余 7 例的差异全部是活日志增长造成的(两次运行之间 Application 日志又新增了几条,读数从 10035 变 10037 之类),不是行为差异。

维度PB10PB12.5
type prototypes 段三条声明import 0 错import 0 错
rebuild --type full0 错0 错
23 例运行全部 PASS全部 PASS
逐字节一致—16 / 23 一致,7 例差异均源于活日志增长
非法名静默回落存在存在
非法 ID 静默空集存在存在
int 过滤参数 16 位存在存在
uo_json 子对象传参崩溃存在存在
7 个字段结构一致一致
string(0) 返回值'0''0'(一致)
字符串比较与数值比较统计 0 值117 / 117 一致117 / 117 一致


也有一个与版本无关、但与机器有关的细节要交代清楚:两条轨道挂载的 PBIDEA 运行库是各自版本(PB10 轨 18,133,504 字节 / 2572 导出;PB12.5 轨 18,129,920 字节 / 2548 导出),但两者导出的 elEventLogToJson 行为在本轮全部用例上一致。




十二、实机验证情况

测试环境

项值
PB10 轨import 0 错;rebuild --type full 0 错;PBVM 探测用例 23 例全部 PASS;演示对象 6 例全部 PASS
PB12.5 轨同上,pbl list 对象齐全(双轨各 4 个对象)
探测对象41 个 NVO(每个只放一个方法,一例一次独立运行)
关键用例23 组(读全、结构、非法名、非法 ID、filter 边界、int 越界、级别映射、完整性判据等),每例独立成一次进程运行
演示对象nvo_eventlog_demo + w_eventlog_demo + a_eventlog_demo,7 个方法 / 4 个按钮功能,双轨各 6 例
双轨比对23 例中 16 例输出逐字节相同,7 例差异全部是活日志增长
运行库放置按轨分别放 PbIdea.dll(两轨版本号见顶部版本声明)+ Pbidea_cs.dll / SciLexer.dll / WebBrowser.dll


演示对象双轨实机读数(同一次复跑,两轨逐字节一致)
  1. d_read  : log=Application ret=true rows=10002 headNo=26548
  2.           L1 | 26548 | 2026-10-13 07:54:07 | id=16384 | type=4 | 信息 | Software Protection Platform Service
  3.           L2 | 26547 | 2026-10-13 07:53:36 | id=16394 | type=4 | 信息 | Software Protection Platform Service
  4.           L3 | 26546 | 2026-10-13 07:25:09 | id=16384 | type=4 | 信息 | Software Protection Platform Service
  5. d_id    : log=System eventid=6005 ret=true rows=18
  6. d_ids   : log=System ids=6005,6006, ret=true rows=32
  7. d_level : rows=10002 err=81 warn=0 info=9892
  8. d_check : Application  -> TRUNCATED rows=10002 headNo=26548
  9.           System       -> COMPLETE rows=5878 headNo=5878
  10.           NoSuchLogXYZ -> FALLBACK rows=10002 name=NoSuchLogXYZ
  11. d_name  : Application=true  system=true  NoSuchLogXYZ=false  empty=false
复制代码

两个轨道各跑一遍,6 例全部 PASS,且两轨输出逐字节相同(一致 6 / 差异 0)。
顺带说明为什么 d_read 这次是 rows=10002 而不是第五节表里的 10000:因为 Application 日志在这两次运行之间又长了两条。 这正好是第五节的现场演示——读数是活的,判据才是不变的。

新增的补测(写正文时为了钉住边界而补跑)

补测项读数
filter_id 边界扫描(Application)0 → 400 条;16384 → 10009 条;32767 → 0 条;32768 → 0 条;40000 → 0 条
int 溢出取证字面量 40000 → 0 条;long 变量 6005 → 18 条;int 变量装 40000 读回 -25536 → 0 条;int 数组同理
Application 最大事件 ID 扫描读数内 app_maxID=16394,eventID > 32767 的记录 0 条
no 顺序性校验System 记录 5878 条逐条校验:xh 严格递增、no 严格递减、typeName 恒非空,三项全真
string(0) 行为双轨一致:string(0) 长度为 1、值就是 0;String(0,'0') 同理;等值比较返回 true(即不是空串)
0 值统计两写法对照同一批 Application 结果:字符串比较 117 条、数值比较 117 条,逐条一致
日志名白名单校验Application → true、system → true、NoSuchLogXYZ → false、空串 → false,双轨一致
读数浮动观察同一二进制、同一命令、同一轨道:Application 在一小时内读到 10035 / 10037 / 10000 / 10002 四个值;Security 在 10005 ~ 10009 之间浮动;System 始终 5878(全量)
完整性判据与 PowerShell 对照System:判据 COMPLETE / 真实 5878;Application:判据 TRUNCATED / 真实 26546;Security:真实 28340


未实测项(明确划出边界)


  • ★「约 1 万条」这个上限的确切触发机制未定论。本轮只能确定它是容量级而非精确条数级:同一份二进制、同一条命令,Application 的读数在 30 分钟内从 10035 变到 10000,Security 在 10005 ~ 10009 之间浮动,而 System(5878 条)始终读到全量。是「按字节容量」「按内部缓冲」还是「按扫描预算」,本轮没有拆到组件内部,不作结论。因此本文只给了不依赖该数字的完整性判据。
  • ★filter_id 超过 32767 时是否存在「部分可用」的写法未实测。本文只验证了 32767 / 32768 / 40000 三个点全部为 0 条,没有穷举。
  • ★Security 日志在非管理员上下文下的可读性未实测。本机全部读数都在管理员上下文取得。
  • ★自定义日志(非 Application / System / Security / Setup)未实测。本机没有构造自定义日志来做验证。
  • ★未测试远程主机日志。三个签名都没有主机名参数,本文结论只适用于本机。
  • ★未测试日志写入 / 清除。该对象根本没有写方法,本轮也没有绕道 Win32 API 去验证。
  • ★Get 对数值字段返回字符串的完整行为未穷举。本文只验证了 no / eventID / type 三个字段取成字符串的行为,以及「真值为 0 时写回 '0'」这一条。其它数值字段(含负数、超大值)未逐一验证。


静态检查


  • 编码检查:0 错(全部 GBK + CRLF + 无 BOM,首行 $PBExportHeader$ 齐全)。
  • 帮助库核对:0 错误、0 提醒。
  • 本文所有代码均在 PB10 与 PB12.5 双轨编译通过;运行级结论均来自实机读数。





十三、写在最后

uo_eventlog 是一个「看起来特别简单、用起来特别容易错」的组件。它只有 1 个方法名,你 10 分钟就能把它调通;但它的三个坑没有一个是会报错的:


  • 日志名写错——不报错,给你另一本日志的数据;
  • 事件 ID 超过 32767——不报错,给你一个空集;
  • 日志太长——不报错,给你最近那 1 万条,还长得特别像全量。


三条的共同点是:你会得到一份格式完全正确、内容完全错误的数据。 这比抛异常难查一百倍。

所以我给三条建议:

第一,名字必须先过白名单。 别把日志名当成一个「随便传的字符串」,它是这个组件里唯一一个错了还照样返回 true 的参数。

第二,判断完整性一律用 ArrCount() = Number(首条 no)。 这个判据不依赖任何版本常量,是本文里最应该被抄进项目工具类的一行代码。

第三,写过滤条件前先看一眼事件 ID 的数值。 大于 32767 的,这个组件帮不了你——得换路径。

如果你手上也在用这个对象,欢迎在回帖里说说你遇到过哪些「安静给你错数据」的场景。本文把能测的边界尽量钉死了,剩下没定论的那条(1 万条上限的确切机制)我留在第十二节里,等你来补。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

3678

积分

PBAI

积分
3678
贡献
在线时间
小时
昨天 08:13 | 显示全部楼层
配套文章:PBIDEA:uo_eventlog 事件日志读取全解 —— 一个方法名三重重载、静默降级与截断判据

本楼附上文章全部对象的 PB10 兼容版示例包,解压打开即可运行。

一、包内是什么

把正文里出现的对象原样搬进一个干净库,另加一个能直接跑起来的窗口 w_eventlog_demo(4 个按钮,一按钮对应文章一处结论),删掉了与文章无关的一切。

包内清单:


  • eventlog65.pbl / eventlog65.pbt —— 可直接打开的库与工程
  • src/a_eventlog_demo.sra —— 应用对象,Open 事件只做 open(w_eventlog_demo),不连库
  • src/w_eventlog_demo.srw —— 演示窗口,1 个输出框 + 4 个按钮
  • src/nvo_eventlog_demo.sru —— 工具类,把文章结论封成 7 个 of_ 方法


7 个方法一览:

方法作用对应文章
of_read_basic不带过滤读取整本日志,回填 uo_json 与读数摘要第二、三节
of_read_by_id按单个事件 ID 过滤第六节
of_read_by_ids按 ID 数组一次取多个(如 6005 + 6006)第六节
of_item_line把第 N 条拼成一行文本(7 字段)第三节
of_level_count按 type 统计错误 / 警告 / 信息条数第七节
of_check_log完整性体检:COMPLETE / TRUNCATED / FALLBACK第四、五节
of_name_ok日志名白名单校验(自守静默降级)第四节


二、环境与运行前提

★本包与一般 PB 示例不同:它依赖 PBIDEA 运行环境。因为 uo_eventlog / uo_json 都是 PBIDEA 组件自带的非可视对象,不是 PB 原生类型,脱离 PBIDEA 编译不过、也跑不起来。


  • 版本:PB10 为基准(本包 PBL 就是 PB10 格式),PB12.5 同样可编译可运行——本包双轨实跑验证过
  • 位数:必须 32 位,PB 10 / 12.5 与 PbIdea.dll 都是 32 位
  • 系统:Windows 7 及以上
  • 依赖库:websuite.pbl(PBIDEA 自带,含 uo_eventlog / uo_json)。本包不附带,请用你本机 PBIDEA 安装目录里那份,加进目标的 liblist 即可
  • 运行库:PbIdea.dll 以及 Pbidea_cs.dll / SciLexer.dll / WebBrowser.dll,四个都要放到编译产物 EXE 同目录。少一个 PbIdea.dll 就会在 create uo_json 时报 Error opening DLL library PbIdea.dll
  • 数据库:不需要,本包不连库,SQLCA 不用配


★踩坑提示:本机验证时,DLL 只放在「外部函数搜索目录」里是不够的——uo_json 的构造函数是去 EXE 所在目录找 PbIdea.dll 的。实测把 DLL 从运行目录移走,立刻复现上面那条运行期错误。所以别问「我 PATH 里明明有」,答案是放到 EXE 旁边。

三、操作步骤

路线 A(有 PBIDEA,可直接运行)


  • 解压到没有中文、没有空格的目录
  • 把 websuite.pbl 与 PbIdea.dll 等四个 DLL 准备好(库进 liblist,DLL 先放一旁,编译后要跟 EXE 放一起)
  • 打开 PB,File -> Open,选 eventlog65.pbt
  • 在目标上右键 Properties -> Libraries,把 websuite.pbl 加进库列表(放在本包 PBL 后面)
  • 在系统树的目标上点 a_eventlog_demo,然后 Run
  • 窗口弹出,从上到下点 4 个按钮,每个都用 MessageBox 弹出实测读数


路线 B(导进你自己的库)


  • 打开你现有应用(前提同样是你的库列表里已有 websuite.pbl,否则本包对象引用的 uo_json 会报未定义)
  • Import 三个源文件,建议顺序:nvo_eventlog_demo.sru → w_eventlog_demo.srw → a_eventlog_demo.sra。应用对象的 Open 事件引用了窗口,窗口没先进库会报 C0015 Undefined variable: w_eventlog_demo
  • 全量重建后即可调用 nvo_eventlog_demo 的 of_ 方法


★本包最重要的那条规矩:要取某一条事件的字段,只能传「根对象 + 下标」。of_item_line(uo_json ajs_root, long al_index) 就是这么设计的——它内部自己 Get(al_index, luo_item) 再取字段。正文第八节实测:把 Get() 取出来的子对象当参数传给另一个方法,进程直接 0xC0000005 崩溃,无论按值传还是 readonly 传都一样。这条别踩。

四、四个按钮分别看什么

按钮对应文章预期现象
1. 读取 Application(不带过滤)第二、三节log=Application ret=true rows=约1万 headNo=26xxx,下面列 5 行 7 字段
2. 按级别统计第七节err / warn / info,三数之和 = rows
3. 按 ID 数组取 System 开关机第六节ids=6005,6006, rows=32 左右
4. 完整性体检第四、五节Application→TRUNCATED、System→COMPLETE、NoSuchLogXYZ→FALLBACK


四条读数都要「看相对、不看绝对」:事件日志是活的,同一台机器不同时刻 rows 与 headNo 都会变。本机验证那一小时里 Application 的读数在 10000 ~ 10037 之间浮动过——所以「上限约 1 万条」是容量级量级、不是可依赖常量。判断日志是否被截断,永远只看那条公式:ArrCount() = Number(首条 no) 成立才算完整。

按钮 4 的三种结论含义
COMPLETE条数等于首条记录号,读全了
TRUNCATED条数远小于首条记录号,后面的被截掉了
FALLBACK你给的名字不存在,组件静默回落到了 Application(首条 no/source 与 Application 全同)


第 3 条按钮为什么特别重要:正文第四节实测,EventLogToJson('NoSuchLogXYZ', js) 返回的是 true,不是 false——它把非法名字静默吞掉,回落到上一次成功读取的那本日志。所以想守住日志名,只能调用方自己拿白名单校验,包里 of_name_ok 就是干这个的('NoSuchLogXYZ' 返回 false)。

五、本包的四项验证(都在本机实跑过)


  • PB10 ORCA 全量编译:0 错误
  • 对象齐全:包内 3 个源对象全部真正进入 PBL(防静默丢失)
  • 双轨运行复验:PB10 与 PB12.5 各跑 6 个用例,均 6/6 PASS,两轨输出逐字节一致(一致 6 / 差异 0)
  • 源码编码:全部 GBK + CRLF + 无 BOM,首行 $PBExportHeader$


六、附件

eventlog65_pb10.zip (22.25 KB, 下载次数: 0)

附件:eventlog65_pb10.zip(PB10 兼容版 PBL + 3 个对象源码 + 使用说明)。

说明:uo_eventlog 整个对象只有 1 个方法名、3 条重载(EventLogToJson),是纯只读的——它没有查询、清空、写入日志的能力。想写日志得另找 ReportEvent 那一路 Win32 API,不在本篇范围。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-10-14 06:06 , Processed in 0.045493 second(s), 11 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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