PBIDEA:uo_eventlog 事件日志读取全解 —— 一个方法名三重重载、静默降级与截断判据(PB10 基准 · PB12.5 实测)
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:
uo_eventlog luo_el
luo_el = create uo_eventlog
// ... 使用 ...
destroy luo_el
二、签名全貌:1 个方法名、3 条签名
下面三条是从 PBIDEA 随版源码 uo_eventlog.sru 的 type prototypes 段原样抄下来的,一条不多、一条不少:
function boolean EventLogToJson(readonly string name, readonly uo_json jsLog) system library "pbidea.dll" alias for "elEventLogToJson"
function boolean EventLogToJson(readonly string name, readonly uo_json jsLog, int filter_id) system library "pbidea.dll" alias for "elEventLogToJson"
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 的第二参是引用出参,变量必须先声明好类型):
uo_json luo_item
string ls_no
string ls_time
string ls_id
ajs_root.Get(1, luo_item) // 取第 1 条(最新那条)
luo_item.Get('no', ls_no) // 记录号
luo_item.Get('time', ls_time) // 时间
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 + 时间」三处指纹,三处全部相同——这才确认它不是「恰好条数一样」,而是真的回落到同一本日志。
更隐蔽的一点:连读两本不同的日志之后,再喂非法名,拿到的仍然是上一次那本。实测序列是:
系统日志 = 5878 条→非法名 = 10000 条(= Application)→系统日志 = 5878 条
也就是说它的回落目标不是固定的「永远是 Application」,而是上一次成功读取的那本日志。
唯一的例外是空名字:EventLogToJson('', js) 返回 false、条数 0。这是全部实测里唯一能拿到 false 的写法。
还有一个反例是「存在但读不到」:本机确实有 Windows PowerShell 这本日志(用 PowerShell 的 Get-EventLog -LogName 'Windows PowerShell' 能列出 4600 多条),但组件读它返回 true 且条数为 0,同进程连续两次都是 0,稳定可重现。也就是说条数 0 也可能是「日志存在但我读不到」,而不是「日志是空的」。
工程结论:name 这个参数,你必须自己保证它是对的。任何来自配置、界面下拉框、用户输入的日志名,都要先在允许列表里过一遍。写成方法就是这样:
public function boolean of_name_ok (string as_log);
// as_log待校验的日志名
// 返回 true = 在本组件确认可读的日志名白名单内
// 备注 非法名会被静默回落到 Application,所以白名单必须由调用方自己守住
string ls_allowed
string ls_use
ls_allowed = '|application|system|security|setup|'
ls_use = Lower(Trim(as_log))
if Pos(ls_allowed, '|' + ls_use + '|') = 0 then
return false
end if
return true
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 万也好、版本升级后取消上限也好,判据本身依然成立。
一个能直接用的体检方法
public function string of_check_log (string as_log);
// as_log要体检的日志名
// 返回 COMPLETE / TRUNCATED / EMPTY 之一,附条数与首条记录号
//
// 判据:Windows 事件日志的记录号 no 全局单调递增且不回退,
// 未被截断时「最新一条的 no」恰好等于「日志总条数」。
uo_eventlog luo_el
uo_json luo_js
uo_json luo_item
string ls_no
long ll_cnt
boolean lb_ret
luo_el = create uo_eventlog
luo_js = create uo_json
lb_ret = luo_el.EventLogToJson(as_log, luo_js)
ll_cnt = luo_js.ArrCount()
if lb_ret = false or ll_cnt = 0 then
destroy luo_js
destroy luo_el
return 'EMPTY rows=' + string(ll_cnt)
end if
luo_js.Get(1, luo_item)
luo_item.Get('no', ls_no)
destroy luo_js
destroy luo_el
if Long(ls_no) = ll_cnt then
return 'COMPLETE rows=' + string(ll_cnt) + ' headNo=' + ls_no
end if
return 'TRUNCATED rows=' + string(ll_cnt) + ' headNo=' + ls_no
end function
实测输出:
Application : TRUNCATED rows=10000 headNo=26546
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 条)的级别分布实测为:
rows=10000err=81warn=0info=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):
string(0) 长度 = 1,值就是 0
String(0,'0') 长度 = 1,值就是 0
ls_plain = '0' 比较结果 = true
两轨读数逐字节相同。进一步在真实的 Application 结果上对照两种统计写法:
写法统计出的 eventID = 0 条数
字符串比较:ls_id = '0'117
数值比较:Long(ls_id) = 0117
逐条一致。 所以这一处你不用担心兼容性,两种写法都可以。本文工具类统一用数值比较,理由是级别判定(type)本来就要转数值,风格上保持一致:
string ls_id
long ll_v
luo_item.Get('eventID', ls_id)
ll_v = Long(ls_id)
if ll_v = 0 then
ll_zero = ll_zero + 1
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 只有「根对象」可以跨对象传递,子对象一旦离开产生它的那个对象,就是雷。
所以工具类的签名应该这样设计——永远传根对象 + 下标,不要传子对象:
// ❌ 不要这样:接收方拿到的子对象会崩
public function string of_item_line (uo_json ajs_item)
// ✅ 要这样:传根 + 下标,接收方自己 Get
public function string of_item_line (uo_json ajs_root, long al_index);
对应实现:
public function string of_item_line (uo_json ajs_root, long al_index);
// ajs_root事件日志根对象(必须传根对象,不能传 Get() 出来的子对象)
// al_index结果内序号,1 基,范围 1 ~ ajs_root.ArrCount()
// 返回 xh | no | time | id | type | typeName | source
uo_json luo_item
string ls_xh
string ls_no
string ls_time
string ls_id
string ls_type
string ls_tn
string ls_src
ajs_root.Get(al_index, luo_item)
luo_item.Get('xh', ls_xh)
luo_item.Get('no', ls_no)
luo_item.Get('time', ls_time)
luo_item.Get('eventID', ls_id)
luo_item.Get('type', ls_type)
luo_item.Get('typeName', ls_tn)
luo_item.Get('source', ls_src)
return ls_xh + ' | ' + ls_no + ' | ' + ls_time + ' | id=' + ls_id + ' | type=' + ls_type + ' | ' + ls_tn + ' | ' + ls_src
end function
九、封装成一个能直接用的工具类
把上面所有结论收拢,得到一个 nvo_eventlog_demo,7 个方法,覆盖「日志名校验、读、按 ID 读、按 ID 数组读、取单行、统计级别、体检」七件事。
9.1 日志名白名单校验
第四节给出的 of_name_ok 就是这一件,直接收进工具类:
public function boolean of_name_ok (string as_log);
// as_log待校验的日志名
// 返回 true = 在本组件确认可读的日志名白名单内
// 备注 非法名会被静默回落到 Application,所以白名单必须由调用方自己守住
string ls_allowed
string ls_use
ls_allowed = '|application|system|security|setup|'
ls_use = Lower(Trim(as_log))
if Pos(ls_allowed, '|' + ls_use + '|') = 0 then
return false
end if
return true
end function
实测:Application → true、system → true(小写也认)、NoSuchLogXYZ → false、空串 → false,双轨一致。
9.2 基础读取(含指纹摘要)
public function boolean of_read_basic (string as_log, ref uo_json ajs_out, ref string as_info);
// as_log 要读取的日志名,必须自己保证合法(非法名会静默回落到上一本日志)
// ajs_out输出:事件日志结果数组,调用方负责 create 和 destroy
// as_info输出:本次读取的条数摘要
uo_eventlog luo_el
uo_json luo_item
boolean lb_ret
long ll_cnt
string ls_no
luo_el = create uo_eventlog
lb_ret = luo_el.EventLogToJson(as_log, ajs_out)
ll_cnt = ajs_out.ArrCount()
ls_no = ''
if ll_cnt > 0 then
// 取第一条也就是最新一条的记录号,用它和条数比对即可判断是否被截断
ajs_out.Get(1, luo_item)
luo_item.Get('no', ls_no)
end if
as_info = 'log=' + as_log + ' ret=' + string(lb_ret) + ' rows=' + string(ll_cnt) + ' headNo=' + ls_no
destroy luo_el
return lb_ret
end function
9.3 按单个事件 ID 过滤
public function boolean of_read_by_id (string as_log, long al_eventid, ref uo_json ajs_out, ref string as_info);
// as_log 日志名
// al_eventid 只保留该事件 ID 的记录;★必须落在 -32768 ~ 32767 内,超出会静默变空集
// ajs_out 输出:过滤后的事件数组
// as_info 输出:摘要
uo_eventlog luo_el
boolean lb_ret
long ll_cnt
luo_el = create uo_eventlog
lb_ret = luo_el.EventLogToJson(as_log, ajs_out, al_eventid)
ll_cnt = ajs_out.ArrCount()
as_info = 'log=' + as_log + ' eventid=' + string(al_eventid) + ' ret=' + string(lb_ret) + ' rows=' + string(ll_cnt)
destroy luo_el
return lb_ret
end function
用它可以取开关机记录——System 日志的 6005 是「事件日志服务已启动」(开机)、6006 是「事件日志服务已停止」(关机):
long ll_cnt
string ls_info
uo_json luo_js
luo_js = create uo_json
of_read_by_id('System', 6005, luo_js, ref ls_info) // 实测 18 条
ll_cnt = luo_js.ArrCount()
destroy luo_js
实测读数:log=System eventid=6005 ret=true rows=18。
9.4 按事件 ID 数组过滤
public function boolean of_read_by_ids (string as_log, int ai_ids[], ref uo_json ajs_out, ref string as_info);
// as_log 日志名
// ai_ids 事件 ID 数组,一次取多个;元素类型是 int,同样受 16 位限制
// ajs_out输出:过滤后的事件数组
// as_info输出:摘要
uo_eventlog luo_el
boolean lb_ret
long ll_cnt
long ll_i
string ls_ids
luo_el = create uo_eventlog
lb_ret = luo_el.EventLogToJson(as_log, ajs_out, ai_ids)
ll_cnt = ajs_out.ArrCount()
ls_ids = ''
for ll_i = 1 to UpperBound(ai_ids)
ls_ids = ls_ids + string(ai_ids) + ','
next
as_info = 'log=' + as_log + ' ids=' + ls_ids + ' ret=' + string(lb_ret) + ' rows=' + string(ll_cnt)
destroy luo_el
return lb_ret
end function
一次取开机 + 关机:
int li_ids
string ls_info
uo_json luo_js
li_ids = 6005
li_ids = 6006
luo_js = create uo_json
of_read_by_ids('System', li_ids, luo_js, ref ls_info) // 实测 32 条
destroy luo_js
实测读数:log=System ids=6005,6006, ret=true rows=32。
9.5 按级别统计
public function string of_level_count (uo_json ajs_root, ref long al_err, ref long al_warn, ref long al_info);
// ajs_root事件日志根对象(传根对象,不要传子对象)
// al_err 输出:type=1 错误条数
// al_warn 输出:type=2 警告条数
// al_info 输出:type=4 信息条数
// 返回 一行摘要
uo_json luo_item
string ls_type
long ll_i
long ll_cnt
ll_cnt = ajs_root.ArrCount()
al_err = 0
al_warn = 0
al_info = 0
for ll_i = 1 to ll_cnt
ajs_root.Get(ll_i, luo_item)
ls_type = '0'
luo_item.Get('type', ls_type)
if Long(ls_type) = 1 then al_err = al_err + 1
if Long(ls_type) = 2 then al_warn = al_warn + 1
if Long(ls_type) = 4 then al_info = al_info + 1
next
return 'rows=' + string(ll_cnt) + ' err=' + string(al_err) + ' warn=' + string(al_warn) + ' info=' + string(al_info)
end function
实测(Application,同一批结果内统计):rows=10000 err=81 warn=0 info=9890。
9.6 完整性 + 回落体检
public function string of_check_log (string as_log);
// as_log要体检的日志名
// 返回 COMPLETE / TRUNCATED / FALLBACK / EMPTY 之一,附条数与首条记录号
//
// COMPLETE= 读到全量(首条 no 等于条数)
// TRUNCATED = 只读到最近一批(首条 no 大于条数)
// FALLBACK= 读到的其实是 Application(非法名被静默回落)
// EMPTY = 返回 false 或 0 条
uo_eventlog luo_el
uo_json luo_js
uo_json luo_js2
uo_json luo_item
string ls_no
string ls_xh
string ls_src
string ls_time
string ls_no2
string ls_src2
long ll_cnt
long ll_cnt2
boolean lb_ret
luo_el = create uo_eventlog
luo_js = create uo_json
lb_ret = luo_el.EventLogToJson(as_log, luo_js)
ll_cnt = luo_js.ArrCount()
if lb_ret = false or ll_cnt = 0 then
destroy luo_js
destroy luo_el
return 'EMPTY rows=' + string(ll_cnt) + ' ret=' + string(lb_ret)
end if
luo_js.Get(1, luo_item)
luo_item.Get('no', ls_no)
luo_item.Get('source', ls_src)
luo_item.Get('time', ls_time)
// 读的不是 Application 时,再读一次 Application 做指纹比对,用来识破静默回落
if Lower(as_log) <> 'application' then
luo_js2 = create uo_json
luo_el.EventLogToJson('Application', luo_js2)
ll_cnt2 = luo_js2.ArrCount()
if ll_cnt2 > 0 then
luo_js2.Get(1, luo_item)
luo_item.Get('no', ls_no2)
luo_item.Get('source', ls_src2)
if ls_no2 = ls_no and ls_src2 = ls_src then
destroy luo_js2
destroy luo_js
destroy luo_el
return 'FALLBACK rows=' + string(ll_cnt) + ' name=' + as_log
end if
end if
destroy luo_js2
end if
if Long(ls_no) = ll_cnt then
destroy luo_js
destroy luo_el
return 'COMPLETE rows=' + string(ll_cnt) + ' headNo=' + ls_no
end if
luo_js.Get(ll_cnt, luo_item)
luo_item.Get('xh', ls_xh)
destroy luo_js
destroy luo_el
return 'TRUNCATED rows=' + string(ll_cnt) + ' headNo=' + ls_no + ' tailXh=' + ls_xh + ' headSrc=' + ls_src + ' headTime=' + ls_time
end function
实测输出(同一次运行):
Application: TRUNCATED rows=10000 headNo=26546 tailXh=10000 headSrc=Software Protection Platform Service headTime=2026-10-13 06:58:44
System : COMPLETE rows=5878 headNo=5878
NoSuchLogXYZ : FALLBACK rows=10000 name=NoSuchLogXYZ
★最后一行是全文的浓缩:你喂了一个不存在的名字,它给了你 1 万条真实数据、返回 true、一声不响。 没有指纹校验的代码,会把 NoSuchLogXYZ 的核心事件当成 Application 的事件报给用户。
9.7 一个最小窗口
把 7 个方法接到一个窗口上,4 个按钮,每个按钮以 MessageBox 结尾(便于观察):
// 窗口实例变量(在 declare 区声明)
// nvo_eventlog_demo inuo_el
// ---- 窗口 create 事件 ----
inuo_el = create nvo_eventlog_demo
// ---- 窗口 destroy 事件 ----
destroy inuo_el
// ---- 按钮 1:读取 Application 日志 ----
uo_json luo_js
string ls_info
long ll_cnt
long ll_i
luo_js = create uo_json
inuo_el.of_read_basic('Application', luo_js, ref ls_info)
ll_cnt = luo_js.ArrCount()
Parent.mle_output.text = 'of_read_basic -> ' + ls_info + '~r~n'
for ll_i = 1 to 5
Parent.mle_output.text = Parent.mle_output.text + inuo_el.of_item_line(luo_js, ll_i) + '~r~n'
next
destroy luo_js
MessageBox('1、读取 Application', ls_info)
// ---- 按钮 2:按级别统计 ----
uo_json luo_js
string ls_info
string ls_sum
long ll_err
long ll_warn
long ll_info
luo_js = create uo_json
inuo_el.of_read_basic('Application', luo_js, ref ls_info)
ls_sum = inuo_el.of_level_count(luo_js, ref ll_err, ref ll_warn, ref ll_info)
Parent.mle_output.text = ls_info + '~r~n' + ls_sum
destroy luo_js
MessageBox('2、级别统计', '错误=' + string(ll_err) + ' 警告=' + string(ll_warn) + ' 信息=' + string(ll_info))
// ---- 按钮 3:按 ID 数组过滤 ----
int li_ids
uo_json luo_js
string ls_info
li_ids = 6005
li_ids = 6006
luo_js = create uo_json
inuo_el.of_read_by_ids('System', li_ids, luo_js, ref ls_info)
Parent.mle_output.text = ls_info
destroy luo_js
MessageBox('3、按 ID 数组过滤', ls_info)
// ---- 按钮 4:完整性体检 ----
string ls_1
string ls_2
string ls_3
ls_1 = inuo_el.of_check_log('Application')
ls_2 = inuo_el.of_check_log('System')
ls_3 = inuo_el.of_check_log('NoSuchLogXYZ')
Parent.mle_output.text = 'Application : ' + ls_1 + '~r~n' + 'System : ' + ls_2 + '~r~n' + 'NoSuchLogXYZ : ' + ls_3
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
演示对象双轨实机读数(同一次复跑,两轨逐字节一致)
d_read: log=Application ret=true rows=10002 headNo=26548
L1 | 26548 | 2026-10-13 07:54:07 | id=16384 | type=4 | 信息 | Software Protection Platform Service
L2 | 26547 | 2026-10-13 07:53:36 | id=16394 | type=4 | 信息 | Software Protection Platform Service
L3 | 26546 | 2026-10-13 07:25:09 | id=16384 | type=4 | 信息 | Software Protection Platform Service
d_id : log=System eventid=6005 ret=true rows=18
d_ids : log=System ids=6005,6006, ret=true rows=32
d_level : rows=10002 err=81 warn=0 info=9892
d_check : Application-> TRUNCATED rows=10002 headNo=26548
System -> COMPLETE rows=5878 headNo=5878
NoSuchLogXYZ -> FALLBACK rows=10002 name=NoSuchLogXYZ
d_name: Application=truesystem=trueNoSuchLogXYZ=falseempty=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 万条上限的确切机制)我留在第十二节里,等你来补。 配套文章: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(PB10 兼容版 PBL + 3 个对象源码 + 使用说明)。
说明:uo_eventlog 整个对象只有 1 个方法名、3 条重载(EventLogToJson),是纯只读的——它没有查询、清空、写入日志的能力。想写日志得另找 ReportEvent 那一路 Win32 API,不在本篇范围。
页:
[1]