ADN Open CIS
Сообщество программистов Autodesk в СНГ

27/11/2015

Создать лог работы собственного кода

Иногда становится необходимым просмотреть последовательность и затраты времени на выполнение каких-либо команд, функций и т.п. Здесь расскажу о том, как это у меня организовано, какие коды используются.

Первый вопрос - это куда записывать лог-файл. Я принял, что лог должен записываться в определенный каталог, расположенный либо в %temp% текущего пользователя, либо в %appdata%. Оба решения имеют свои плюсы и минусы.

При записи в %temp% вычистить лог очень легко. Это плюс. Удалить логи слишком легко - и это минус.

При записи в %appdata% надо предусмотреть автоматическое удаление слишком разросшихся файлов. Здесь этот вопрос не рассматриваю (просто чтобы не запутать лишними подробностями ;))

Запись лога на сервер я всерьез не рассматриваю,- там слишком много может быть ограничений. А механизм должен работать всегда.

Второй вопрос - это имя лог-файла. В принципе, тут никаких ограничений, но для примера примем, что имя лог-файла должно формироваться по принципу <ДоменКомпьютера>-<ИмяКомпьютера>-<ИмяПользователя>.log. Если объем файла превышает 2Mb, старый файл копируется с указанием даты, и создается новый.

Далее нам гарантированно понадобятся функции показа текущего лога, функции записи в лог нужной информации, а также - внимание! - еще один "переключатель". Последняя функция позволит нам с легкостью переключать режим ведения лога. Например, при разработке, тестировании или при непонятных ситуациях у пользователя можно включить режим лога, а потом уже и анализировать лог.

Получим имя лог-файла:

Код - Auto/Visual LISP: [Выделить]
  1. (defun _kpblc-get-file-log (/ path file handle)
  2.                            ;|
  3. *    Возвращает имя файла лога. Если файл существует и его размер больше 2Mb,
  4. * то файл автоматически копируется и пересоздается
  5. |;
  6.   (setq path (_kpblc-dir-create (strcat (vl-string-right-trim "\\" (getenv "appdata")) "\\kpblc\\")))
  7.   (cond
  8.     ((not
  9.        (findfile
  10.          (setq file (strcat path
  11.                             (getenv "userdomain")
  12.                             "-"
  13.                             (getenv "computername")
  14.                             "-"
  15.                             (getenv "username")
  16.                             ".log"
  17.                             ) ;_ end of strcat
  18.                ) ;_ end of setq
  19.          ) ;_ end of findfile
  20.        ) ;_ end of not
  21.      file
  22.      )
  23.     ((and (findfile file)
  24.           (> (vl-file-size file) (* 2 (expt 2 20)))
  25.           ) ;_ end of and
  26.      (vl-file-copy
  27.        file
  28.        (strcat (_kpblc-dir-path-no-splash (vl-filename-directory file))
  29.                "\\"
  30.                (vl-filename-base file)
  31.                "_"
  32.                (rtos (getvar "cdate") 2 6)
  33.                ) ;_ end of strcat
  34.        ) ;_ end of vl-file-copy
  35.      (setq handle (open file "w"))
  36.      (close handle)
  37.      file
  38.      )
  39.     (t file)
  40.     ) ;_ end of cond
  41.   ) ;_ end of defun

Потребуется одна служебная функция:

Код - Auto/Visual LISP: [Выделить]
  1. (defun _kpblc-dir-create (path / tmp)
  2.                          ;|
  3. *    Гарантированное создание каталога.
  4. *    Параметры вызова:
  5.   path  создаваемый каталог
  6. |;
  7.   (cond
  8.     ((vl-file-directory-p path) path)
  9.     ((setq tmp (_kpblc-dir-create (vl-filename-directory path)))
  10.      (vl-mkdir
  11.        (strcat tmp
  12.                "\\"
  13.                (vl-filename-base path)
  14.                (cond ((vl-filename-extension path))
  15.                      (t "")
  16.                      ) ;_ end of cond
  17.                ) ;_ end of strcat
  18.        ) ;_ end of vl-mkdir
  19.      (if (vl-file-directory-p path)
  20.        path
  21.        ) ;_ end of if
  22.      )
  23.     ) ;_ end of cond
  24.   ) ;_ end of defun

Далее напишем тот самый "переключатель" (функция записи лога будет ссылаться на его данные):

Код - Auto/Visual LISP: [Выделить]
  1. (defun _kpblc-log-toggle (param)
  2.                          ;|
  3. *    Функция включения или отключения процедуры лога
  4. *    Параметры вызова:
  5.   param   включить лог (t) или отключить (nil)
  6. |;
  7.   (vl-bb-set '*kpblc-settings*
  8.              (_kpblc-list-add-or-subst (vl-bb-ref '*kpblc-settings*) "log" param)
  9.              ) ;_ end of vl-bb-set
  10.   (princ (strcat "\nЛог "
  11.                  (if param
  12.                    "запущен"
  13.                    "остановлен"
  14.                    ) ;_ end of if
  15.                  ) ;_ end of strcat
  16.          ) ;_ end of princ
  17.   (princ)
  18.   ) ;_ end of defun

Что здесь происходит? Указанная настройка (вести лог или нет) записывается во внедокументную перемнную *kpblc-settings*. Т.е. указанная настройка будет доступна во всех документах текущей сессии AutoCAD. Такое решение было обусловлено тем, что иногда при открытии какого-либо файла мне уже надо было вести лог выполняющихся действий. Но это мое решение...

Точно так же потребуется одна служебная функция замены элемента в списке:

Код - Auto/Visual LISP: [Выделить]
  1. (defun _kpblc-list-add-or-subst (lst key value)
  2.                                 ;|
  3. *    Производит замену или дополнение элемента списка новым
  4. *    Параметры вызова:
  5.   lst      обрабатываемый список
  6.   key      ключ
  7.   value    устанавливаемое значение
  8. |;
  9.   (if (not value)
  10.     (vl-remove-if (function (lambda (x) (= (car x) key))) lst)
  11.     (if (cdr (assoc key lst))
  12.       (subst (cons key value) (assoc key lst) lst)
  13.       (cons (cons key value)
  14.             (vl-remove-if
  15.               (function
  16.                 (lambda (x)
  17.                   (= (car x) key)
  18.                   ) ;_ end of lambda
  19.                 ) ;_ end of function
  20.               lst
  21.               ) ;_ end of vl-remove-if
  22.             ) ;_ end of cons
  23.       ) ;_ end of if
  24.     ) ;_ end of if
  25.   ) ;_ end of defun

Теперь можно приступать уже собственно к разработке функции записи в лог какой-либо информации. Вопрос фактически один: а что мы собираемся вообще записывать?

  1. Нам может понадобиться время (с точностью, например, до секунд). А может и не понадобиться
  2. Точно так же может понадобиться указание имени выполняемой lisp-функции
  3. В некоторых случаях лог должен записываться невзирая на установленные "переключателем" настройки
  4. Может понадобиться дополнительное пояснение
  5. В обязательном варианте записываем полное имя текущего документа. Если документ еще не сохранялся, то в качестве имени пишем "Файл не сохранен"

Получается нечто типа:

Код - Auto/Visual LISP: [Выделить]
  1. (defun _kpblc-log (lst / handle sep)
  2.                   ;|
  3. *    выполняет запись сообщения в лог
  4. *    Параметр вызова:
  5.  lst    список дополнительных параметров
  6.   '(("time" . <Указывать время>)
  7.     ("cmd" . <Указывается имя функции>)
  8.     ("req" . <Лог выполнять в любом случае>)
  9.     ("msg" . <Дополнительное пояснение>)
  10.     )
  11. |;
  12.   (if (or (cdr (assoc "req" lst))
  13.           (cdr (assoc "log" (vl-bb-ref '*kpblc-settings*)))
  14.           ) ;_ end of or
  15.     (progn
  16.       (setq sep    "\t"
  17.             handle (open (_kpblc-get-file-log) "a")
  18.             ) ;_ end of setq
  19.       (write-line
  20.         (strcat
  21.           (if (= (vla-get-fullname (vla-get-activedocument (vlax-get-acad-object))) "")
  22.             "Файл не сохранен"
  23.             (strcat (vl-string-right-trim "\\" (getvar "dwgprefix")) "\\" (getvar "dwgname"))
  24.             ) ;_ end of if
  25.           sep
  26.           (cond ((cdr (assoc "cmd" lst)))
  27.                 (t "Имя lisp не указано")
  28.                 ) ;_ end of cond
  29.           sep
  30.           (if (cdr (assoc "time" lst))
  31.             (_kpblc-conv-date-to-string)
  32.             ""
  33.             ) ;_ end of if
  34.           (if (cdr (assoc "msg" lst))
  35.             (strcat sep (cdr (assoc "msg" lst)))
  36.             ""
  37.             ) ;_ end of if
  38.           ) ;_ end of strcat
  39.         handle
  40.         ) ;_ end of write-line
  41.       (close handle)
  42.       ) ;_ end of progn
  43.     ) ;_ end of if
  44.   ) ;_ end of defun

Потребуется функция преобразования текущей даты в формат "ГГГГ-ММ-ДД чч:мм:сс":

Код - Auto/Visual LISP: [Выделить]
  1. (defun _kpblc-conv-date-to-string (/ date sdate stime)
  2.                                   ;|
  3. *    Преобразовывает текущую дату и время в строковое представление
  4. |;
  5.   (setq date  (getvar "cdate")
  6.         sdate (fix date)
  7.         stime (- date sdate)
  8.         sdate (itoa sdate)
  9.         stime (itoa (fix (* stime 1e6)))
  10.         ) ;_ end of setq
  11.   (while (< (strlen stime) 6)
  12.     (setq stime (strcat "0" stime))
  13.     ) ;_ end of while
  14.   (strcat (substr sdate 1 4)
  15.           "-"
  16.           (substr sdate 5 2)
  17.           "-"
  18.           (substr sdate 7)
  19.           " "
  20.           (substr stime 1 2)
  21.           ":"
  22.           (substr stime 3 2)
  23.           ":"
  24.           (substr stime 5)
  25.           ) ;_ end of strcat
  26.   ) ;_ end of defun

Теперь осталось только нарисовать "показ" лога. Ну, тут уже совсем просто:

Код - Auto/Visual LISP: [Выделить]
  1. (defun _kpblc-log-show (/ file)
  2.                        ;|
  3. *    Выводит окно лога в отдельном приложении (блокнот)
  4. |;
  5.   (if (setq file (findfile (_kpblc-get-file-log)))
  6.     (startapp "notepad.exe"
  7.               (_kpblc-get-file-log)
  8.               ) ;_ end of startapp
  9.     (alert "Файл лога не обнаружен!")
  10.     ) ;_ end of if
  11.   (princ)
  12.   ) ;_ end of defun

Немного пояснений. В качестве разделителя данных в лог-файле выбран символ табуляции: это позволит, сменив расширение на csv, выполнить открытие лога в Excel / LibreOffice / etc и выполнить практически любые действия.

В качестве примера использования функций:

Код - Auto/Visual LISP: [Выделить]
  1. (defun test-log ()
  2.   (_kpblc-log '(("req" . t) ("time" . t) ("cmd" . "test")))
  3.   (_kpblc-log '(("req" . t) ("time" . t) ("cmd" . "test") ("msg" . "Проверочное сообщение")))
  4.   (entmakex (list (cons 0 "LINE")
  5.                   (cons 10 '(0. 0. 0.))
  6.                   (cons 11 '(10. 10. 0.))
  7.                   ) ;_ end of list
  8.             ) ;_ end of entmakex
  9.   (_kpblc-log '(("time" . t) ("cmd" . "test") ("msg" . "Необязательное сообщение")))
  10.   (princ)
  11.   ) ;_ end of defun

В логе будет нечто типа:

Файл не сохранен test 2015-11-26 16:44:44

Файл не сохранен test 2015-11-26 16:44:44 Проверочное сообщение

Переключим выполнение лога:

И снова вызовем (test-log):

Файл не сохранен test 2015-11-26 16:44:44

Файл не сохранен test 2015-11-26 16:44:44 Проверочное сообщение

Файл не сохранен test 2015-11-26 16:51:44

Файл не сохранен test 2015-11-26 16:51:44 Проверочное сообщение

Файл не сохранен test 2015-11-26 16:51:44 Необязательное сообщение

Как видно, добавилось "необязательное" сообщение.

Все коды, кроме "тестировочного", можно забрать здесь

Автор: Алексей Кулик

Обсуждение: http://adn-cis.org/forum/index.php?topic=3238

Опубликовано 27.11.2015