xie*_*pan 2 function common-lisp
我知道这超出了标准,但是在具体实现中是否有官方方法来获取当前函数名称,例如 SBCL?有时我想将当前函数名称放入日志消息中。我相信如果有一个一致的方法来获取当前的函数名称,它会使维护代码变得更容易,至少我不必每次更改函数名称时都更改日志消息。
目前我正在 defun 周围使用包装宏
(defmacro defun2 (name (&rest args) &body body)
`(defun ,name (,@args)
(let ((fun-name ',name))
,@body)))
(defun2 foo (x y)
(format nil "fun ~a: x is ~a, y is ~a~%"
fun-name x y))
Run Code Online (Sandbox Code Playgroud)
但我很想知道是否有更简单的方法。
假设您想向应用程序添加日志记录,因此您可以定义自己的小宏和函数,就像使用其他语言一样:
(defpackage :logging (:use :cl))
(in-package :logging)
Run Code Online (Sandbox Code Playgroud)
为了能够关闭日志记录,或者根据需要更改您的实现,您决定将日志记录更好地封装为宏:
(defmacro note (&rest things)
`(note% (first (sb-debug:list-backtrace :count 1))
(list ,@things)))
Run Code Online (Sandbox Code Playgroud)
上面,我用来sb-debug:list-backtrace捕获当前的堆栈帧。它不可移植,当您的函数是可移植的时,inline您不会看到它的名称,而是看到调用者的名称。但这是大多数语言中每个日志库都接受的可接受的妥协。
然后,您可以根据需要自由地实现它,例如使用条件系统:
(define-condition note (simple-warning)
((timestamp
:initarg :timestamp
:reader note-timestamp)
(origin
:initarg :origin
:reader note-origin)
(data
:initarg :data
:reader note-data))
(:report (lambda (c s)
(format s
"~d ~a ~@<~s~:>"
(note-timestamp c)
(note-origin c)
(note-data c)))))
Run Code Online (Sandbox Code Playgroud)
这里的函数note%会发出警告,但其想法是,在更高的级别上,您捕获所有这些条件并将它们写在其他地方。如果没有处理程序,SBCL 会将其打印在 REPL 上,这样也可以:
(defun note% (origin data)
(warn 'note
:timestamp (local-time:now)
:origin origin
:data data))
Run Code Online (Sandbox Code Playgroud)
最后,您可以按如下方式进行测试:
(defun test (in)
(flet ((y (x) (note :x x)))
(note :before :in in)
(y 6)
(note :after)))
Run Code Online (Sandbox Code Playgroud)
例如:
(test 15)
WARNING: 2022-10-19T15:17:05.297198Z (TEST 15) (:BEFORE :IN 15)
WARNING: 2022-10-19T15:17:05.297603Z ((FLET Y IN TEST) 6) (:X 6)
WARNING: 2022-10-19T15:17:05.297706Z (TEST 15) (:AFTER)
Run Code Online (Sandbox Code Playgroud)
您可能希望在向用户提供代码时激活跟踪,而不是执行上述所有操作。您选择要跟踪的函数,然后重定向*TRACE-OUTPUT*到您的日志(或使用自定义报告函数,请参见下文)。
SBCL 实现假定字符串参数表示包,但这不是可移植的。然而,这种不可移植性的影响较小,因为它只在一个地方完成,并且不会触及所有代码。如果您愿意,总是可以列出所有符号并显式跟踪它们,或者使用读取器宏以TRACE适合您的方式调用特定于实现的方式。
例如,使用 SBCL,让我们跟踪新创建的包中的所有符号:
(trace "LOGGING")
Run Code Online (Sandbox Code Playgroud)
并运行相同的测试:
(test 15)
0: (LOGGING::TEST 15)
1: (LOGGING::NOTE% (LOGGING::TEST 15) (:BEFORE :IN 15))
2: (LOGGING::NOTE-TIMESTAMP #<LOGGING::NOTE {100EC7CC33}>)
2: NOTE-TIMESTAMP returned @2022-10-19T15:22:05.315488Z
2: (LOGGING::NOTE-ORIGIN #<LOGGING::NOTE {100EC7CC33}>)
2: NOTE-ORIGIN returned (TEST 15)
2: (LOGGING::NOTE-DATA #<LOGGING::NOTE {100EC7CC33}>)
2: NOTE-DATA returned (:BEFORE :IN 15)
WARNING: 2022-10-19T15:22:05.315488Z (TEST 15) (:BEFORE :IN 15)
1: NOTE% returned NIL
1: (LOGGING::NOTE% ((FLET LOGGING::Y :IN LOGGING::TEST) 6) (:X 6))
2: (LOGGING::NOTE-TIMESTAMP #<LOGGING::NOTE {100EC9DBB3}>)
2: NOTE-TIMESTAMP returned @2022-10-19T15:22:05.319801Z
2: (LOGGING::NOTE-ORIGIN #<LOGGING::NOTE {100EC9DBB3}>)
2: NOTE-ORIGIN returned ((FLET Y :IN TEST) 6)
2: (LOGGING::NOTE-DATA #<LOGGING::NOTE {100EC9DBB3}>)
2: NOTE-DATA returned (:X 6)
WARNING: 2022-10-19T15:22:05.319801Z ((FLET Y IN TEST) 6) (:X 6)
1: NOTE% returned NIL
1: (LOGGING::NOTE% (LOGGING::TEST 15) (:AFTER))
2: (LOGGING::NOTE-TIMESTAMP #<LOGGING::NOTE {100ECAE773}>)
2: NOTE-TIMESTAMP returned @2022-10-19T15:22:05.323732Z
2: (LOGGING::NOTE-ORIGIN #<LOGGING::NOTE {100ECAE773}>)
2: NOTE-ORIGIN returned (TEST 15)
2: (LOGGING::NOTE-DATA #<LOGGING::NOTE {100ECAE773}>)
2: NOTE-DATA returned (:AFTER)
WARNING: 2022-10-19T15:22:05.323732Z (TEST 15) (:AFTER)
1: NOTE% returned NIL
0: TEST returned NIL
Run Code Online (Sandbox Code Playgroud)
在不更改代码的情况下,我现在可以准确地看到发生了什么。事实上我根本不需要实现日志记录功能。
某些实现允许您配置跟踪的完成方式,因此如果您想添加时间戳,您也应该能够这样做。在 SBCL 中,这是:report 接受函数名称的选项。在这里,我使用自定义my-report函数拦截每个条目:
(trace "LOGGING" :report my-report)
(defun my-report (&rest args)
(fresh-line *trace-output*)
(print (list* (local-time:now) args) *trace-output*))
Run Code Online (Sandbox Code Playgroud)
例如:
(test 15)
(@2022-10-19T15:34:58.543114Z 0 LOGGING::TEST :ENTER #<SB-DI::COMPILED-FRAME SB-INT:SIMPLE-EVAL-IN-LEXENV> (15))
(@2022-10-19T15:34:58.552193Z 1 LOGGING::NOTE% :ENTER #<SB-DI::COMPILED-FRAME LOGGING::TEST> ((LOGGING::TEST 15) (:BEFORE :IN 15)))
(@2022-10-19T15:34:58.552359Z 2 LOGGING::NOTE-TIMESTAMP :ENTER #<SB-DI::COMPILED-FRAME (SB-KERNEL::CONDITION-REPORT LOGGING::NOTE)> (#<LOGGING::NOTE {100F5E7F23}>))
(@2022-10-19T15:34:58.552461Z 2 NOTE-TIMESTAMP :EXIT
#<SB-DI::COMPILED-FRAME (SB-KERNEL::CONDITION-REPORT NOTE)>
(@2022-10-19T15:34:58.552306Z))
....
Run Code Online (Sandbox Code Playgroud)