原创 无峰 [ ABAP 技巧与实战 ](javascript:void\(0\);)
一
前言
有时候项目上基于如下原因会追查特定事务代码的执行情况.
• 性能分析
• 安全性分析
• ……
有些标准的功能可以查询事务代码的执行情况. 但似乎只有调用记录, 没有需要的细节信息,比如:选择条件,查询记录数等.因此考虑自行记录程序(/报表)的执行日志
**本文主要介绍报表执行日志的记录**
二
思维导图
把程序调用日志相关的内容整理了一下.
三
标准的程序调用分析
标准分析有两个事务代码 STATS 和 SM20
用ST12粗略分析了一下这两个事务代码查询数据的存储
STATS 似乎是存在文件系统中的. 没有记录到表里, 因此内容保留周期有限,查询性能较差.
SM20 数据存储在 PRSAULOG, P_RSAU_LOG 中. 可以长期保留,除非有后台作业清除表内容.
执行一个标准报表MB51后.
查看标准的程序调用分析功能
STATS
这个事务偏性能分析, 可以查询到特定用户,特定事务代码执行的系统性能开销信息
STATS限制日期范围受 length影响, 最多99小时. end date 按 start date + length 计算而来.
这个设计应该是考虑性能问题. 实际测试发现 STATS只能查询最近3天的内容
SM20 安全审计日志
安全审计日志需要先开启安全审计功能
关于安全审计功能的相关减少, 详见如下链接
https://blog.csdn.net/lostworld_gy/article/details/86090644
四
自定义类记录
基于标准分析没有想要的内容,主要是选择屏幕内容.
因此考虑通过自定义类方法来记录 zcl_report=>save_rep_log_pub
如下图,在自定义程序的如下三个位置分别调用类方法即可.
控制表
ZTREPORT_C1
报表日志配置(如未配置,视同为都记录)
记录表
ZTREPORT_LOG
报表执行日志
选择屏幕内容部分记录到 INDX(ZP)
> INDX应用详见链接 无峰,公众号:ABAP 技巧与实战 [ SAP小技巧 使用EXPORT/IMPORT保存特殊数据
> ](https://mp.weixin.qq.com/s?__biz=Mzg4MjA1MjE4NQ==&mid=2247486037&idx=1&sn=40786ce2465ba450a93ba6f4ffca7635&chksm=cf5dd763f82a5e7544af109794dadf873036bd3949f8fb07553176b814bbaf4bf8f707349caa&token=2032717129&lang=zh_CN#rd)
五
记录结果的查询
可以指定统计的日期维度,按限制的条件查询统计结果或明细结果
在统计结果中,点击条目数可以显示明细调用
执行结果按统计/明细两个窗口呈现.
明细中可以通过点击显示变式查看当时的查询条件
可以通过带变式执行功能重新执行程序并带入当时的选择条件(跳过选择屏幕)
七
对动态报表的支持
动态报表的执行按动态报表的查询ID代替程序名来记录执行情况. ZDQ中开放了一个日志按钮查询历史执行情况. 点击后, 可以查看当前查询ID的执行情况,
动态报表因为使用了动态选择屏幕, 不支持显示变式功能. 但支持带变式执行功能.
八
对标准程序的支持
自定义程序可以在代码中添加相应的语句取记录执行日志. 但是不可能修改标准程序去添加记录日志的代码. 因此考虑把相关的日志记录放到底层报表执行框架中 .
反复跟踪测试发现如下位置可以完成
报表执行时记录一次. 获取初始时间戳
程序 RSDBRUNT FORM POP_SCREEN. 报表程序执行时,都会进入这个节点 ,可以作为报表执行的开始节点
其它程序通过SUBMIT调用程序, 也会进入这个节点. 但是此时MEMKEY-REPORT = '' 无法记录.
IF -ucomm = 'ONLI' AND memkey-report IS NOT INITIAL.DATA(lv_active) = zcl_enh_control=>check_enh_actvt( 'ZENH_10068' ).IF lv_active = 'X'.INCLUDE zenh_10068.ENDIF.ENDIF.ALV显示前,记录一次, 获取读取得记录数, SQL执行后得时间戳信息(大部分报表都通过ALV显示结果,因此在ALV显示之前增强记录即可. 增强在
SET_TABLE_FOR_FIRST_DISPLAY 中
DATA(lv_active_2) = zcl_enh_control=>check_enh_actvt( 'ZENH_10068' ).IF LV_ACTIVE_2 = 'X'.DATA(LV_REPID) = SY-CPROG.
zcl_report=> save_rep_log_pub( iv_point = '01' iv_lines = lines( IT_OUTTAB )
IV_REPID = LV_REPID ).
ENDIF.程序 RSDBRUNT FORM %_RETURN_TO_SELSCREEN. 报表程序退出时,会返回选择屏幕,
通过这个节点返回,可以作为一次报表执行的结束节点
if MEMKEY-REPORT <> ''.
DATA(lv_active_3) = zcl_enh_control=>check_enh_actvt( 'ZENH_10068' ).IF LV_ACTIVE_3 = 'X'.zcl_report=>save_rep_log_pub( iv_point = '99' ).
ENDIF.endif.
上述增强实现后, 自定义程序也无需附加代码记录日志了.
九
重点强调
RSDBRUNT是底层框架程序, 当框架程序中调用的类修改出现问题,(比如可选参数调整为必输参数)修改RSDBRUNT 中的部分代码,导致异常. 此时
SE38,SE24 等代码编辑器都无法执行了. 无法修复导致异常的程序.
**但是ECLIPSE 代码编辑器不受影响, 可以修复引起错误的代码.**
以后要多尝试用eclipse 编辑代码咯.
十
日志记录的代码部分
METHOD save_rep_log_pub. "保存日志 DATA: lv_runtime TYPE i, lv_timestamp TYPE c LENGTH 14. DATA: lv_repid TYPE sy-repid.
IF iv_frame = 'X'. gv_frame = 'X'. ENDIF. IF iv_frame = '' AND gv_frame = 'X'. "如果框架中调用了. 则程序的的调用直接退出 RETURN. ENDIF.
IF iv_repid IS NOT INITIAL. lv_repid = iv_repid. "程序名/CHAR/40.0/ ELSE. lv_repid = sy-cprog. "程序名/CHAR/40.0/ ENDIF.
GET TIME. lv_timestamp = sy-datum && sy-uzeit. GET RUN TIME FIELD lv_runtime. IF gs_c1_sta IS INITIAL. SELECT SINGLE * FROM ztreport_c1 INTO gs_c1_sta WHERE repid = lv_repid. IF sy-subrc <> 0. gs_c1_sta = VALUE #( repid = lv_repid exelog = 'X' varlog = 'X' ). ENDIF. ENDIF. CHECK gs_c1_sta-exelog = 'X'. CASE iv_point. WHEN '00'. "开始 CLEAR gs_log_sta. CALL FUNCTION 'NUMBER_GET_NEXT' EXPORTING nr_range_nr = '02' object = 'ZLOGID' IMPORTING number = gs_log_sta-logid. "报表执行日志ID/CHAR/16.0/主键 gs_log_sta-repid = lv_repid. "程序名/CHAR/40.0/ gs_log_sta-tcode = sy-tcode.* SELECT SINGLE tcode FROM tstc INTO gs_log_sta-tcode WHERE tcode = sy-tcode. "如果当前TCODE 就是程序的TCODE. 记录一下.* IF sy-subrc <> 0.* SELECT SINGLE tcode FROM tstc INTO gs_log_sta-tcode WHERE pgmna = lv_repid.* ENDIF. gs_log_sta-tmbeg = lv_timestamp. "开始的时间戳/CHAR/14.0/ gs_log_sta-runtime00 = lv_runtime. CALL METHOD zcl_comm_bsd=>get_zsbc001( EXPORTING iv_updkz = 'I' CHANGING cs_data = gs_log_sta ). gs_log_sta-spyea = sy-datum(4). gs_log_sta-spmon = sy-datum(6). MODIFY ztreport_log FROM gs_log_sta. "保存变式 IF gs_c1_sta-varlog = 'X'. DATA: lt_sel TYPE TABLE OF rsparams. CALL FUNCTION 'RS_REFRESH_FROM_SELECTOPTIONS' EXPORTING curr_report = lv_repid* IMPORTING* SP = TABLES selection_table = lt_sel selection_table_255 = gt_sel255_sta* EXCEPTIONS* NOT_FOUND = 1* NO_REPORT = 2* OTHERS = 3 . DATA: ls_indx TYPE indx. ls_indx-aedat = sy-datum. ls_indx-enddt = sy-datum. ls_indx-usera = sy-uname. ls_indx-pgmid = lv_repid. EXPORT gt_sel255 = gt_sel255_sta TO DATABASE indx(zp) FROM ls_indx ID gs_log_sta-logid. ENDIF. WHEN '01'. "主体查询结束 IF gs_log_sta-logid <> ''. gs_log_sta-runtime01 = lv_runtime. gs_log_sta-sec00_01 = ( gs_log_sta-runtime01 - gs_log_sta-runtime00 ) / 1000 / 1000. gs_log_sta-lines = iv_lines. IF gs_log_sta-lines <> 0. gs_log_sta-line_cost = ( gs_log_sta-runtime01 - gs_log_sta-runtime00 ) / 1000 / gs_log_sta-lines. ENDIF. MODIFY ztreport_log FROM gs_log_sta. ENDIF. WHEN '99'. "主体查询结束 IF gs_log_sta-logid <> ''. gs_log_sta-runtime99 = lv_runtime....