1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
|
;;; lui-logging.el --- Logging support for lui
;; Copyright (C) 2006 Jorgen Schaefer,
;; 2012 Anthony Martinez
;; Author: Anthony Martinez <pi+circe@pihost.us>
;; This file is part of Lui.
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License
;; as published by the Free Software Foundation; either version 3
;; of the License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
;; 02110-1301 USA
;;; Commentary:
;; This lui module enables logging. Lui applications can change the
;; values of `lui-logging-format-arguments' to provide further
;; possibilities of customizing `lui-logging-file-format' for users.
;;; Code:
(require 'lui-format)
(require 'url-util)
(defgroup lui-logging nil
"Logging support."
:prefix "lui-logging-"
:group 'lui)
(defcustom lui-logging-format "[%T] {text}"
"The format used for log file entries.
This is first passed through `format-time-string' and then through
`lui-format'. The following format strings exist:
{text} - the text to be logged"
:type 'string
:group 'lui-logging)
(defcustom lui-logging-directory "~/.logs"
"The directory where log files are stored."
:type 'directory
:group 'lui-logging)
(defcustom lui-logging-file-format "{buffer}_%Y-%m-%d.txt"
"The format to be used for the log file name.
This is first passed through `format-time-string', and then
through `lui-format'. Possible lui format strings are:
{buffer} - the buffer name where the logging happened.
Lui applications can provide further format strings. See
`lui-logging-format-arguments' in the appropriate buffer."
:type 'string
:group 'lui-logging)
(defcustom lui-logging-flush-delay 0
"The number of seconds to delay writing newly-received messages
to disk. This can increase performance/decrease IO-wait at the
cost of a little bit of safety."
:type 'integer
:group 'lui-logging)
(defvar lui-logging-format-arguments nil
"A list of arguments to be passed to `lui-format'.
This can be used to extend the formatting possibilities of the
file name for lui applications.")
(make-variable-buffer-local 'lui-logging-format-arguments)
(defvar lui-logging-file-name-unreserved-chars
;; All but '/' is fine actually, but also omit '%' because otherwise there's
;; ambiguity between one introduced by encoding and a literal one.
'(?! ?\" ?# ?$ ?& ?` ?\( ?\) ?* ?+ ?,?: ?\; ?< ?= ?> ?? ?@?\[ ?\\ ?\] ?^ ?`
?\{ ?| ?\})
"A list of characters that should not be percent-encoded by
`url-hexify-string' while generating a logging file name.")
(defvar lui-pending-logs
(make-hash-table :test 'equal)
"Storage for log messages awaiting write. It is structured as a
hash table mapping filenames to a list-of-strings, which serves as
a queue.")
(defvar lui-logging-timer nil
"The timer used to flush lui-logged buffers")
(defun lui-logging-delayed-p ()
(> lui-logging-flush-delay 0))
(defun enable-lui-logging ()
"Enable lui logging for this buffer. Also create the log
file's directory, should it not exist."
(interactive)
(add-hook 'lui-pre-output-hook 'lui-logging
nil t))
(defun disable-lui-logging ()
"Disable lui logging for this buffer, and flush any pending
logs to disk."
(interactive)
(remove-hook 'lui-pre-output-hook 'lui-logging t)
(lui-logging-flush))
(defun enable-lui-logging-globally ()
"Enable lui logging for all Lui buffers.
This affects current as well as future buffers."
(interactive)
(add-hook 'lui-mode-hook 'enable-lui-logging)
(dolist (buf (buffer-list))
(with-current-buffer buf
(when lui-input-marker
(enable-lui-logging)))))
(defun disable-lui-logging-globally ()
"Disable logging in all future Lui buffers.
This affects current as well as future buffers."
(interactive)
(remove-hook 'lui-mode-hook 'enable-lui-logging)
(dolist (buf (buffer-list))
(with-current-buffer buf
(when lui-input-marker
(disable-lui-logging)))))
(defun lui-logging-file-name ()
"Create the name of the log file based on `lui-logging-file-format'."
(let* ((time-formatted (format-time-string lui-logging-file-format))
(buffer (let ((url-unreserved-chars
(append url-unreserved-chars
lui-logging-file-name-unreserved-chars))
(downcased (downcase (buffer-name (current-buffer)))))
(url-hexify-string downcased)))
(filename (apply 'lui-format
time-formatted
:buffer buffer
lui-logging-format-arguments)))
(concat lui-logging-directory "/" filename)))
(defun lui-logging-flush ()
"Flush out the lui-logging queue, and clear the timer set by
`lui-logging'."
(maphash #'lui-logging-flush-file lui-pending-logs)
(clrhash lui-pending-logs)
(cancel-timer lui-logging-timer)
(setq lui-logging-timer nil))
(defun lui-logging-write-to-log (file-name content)
"Actually perform a write to the logfile."
(let ((coding-system-for-write 'raw-text)
(dir (file-name-directory file-name)))
(when (not (file-directory-p dir))
(make-directory dir t))
(write-region content nil file-name t 'nomessage)))
(defun lui-logging-flush-file (file-name queue)
"Consume the logging queue and write the content to the log
file."
(let ((content (apply #'concat (nreverse queue))))
(lui-logging-write-to-log file-name content)))
(defun lui-logging-format-string (text)
"Generate a string to be either directly written or enqueued."
(substring-no-properties
(lui-format
(format-time-string lui-logging-format)
:text text)))
(defun lui-logging-enqueue (file-name text)
"Given a filename, push text onto its queue, and tickle the
timer, if necessary."
(puthash file-name
(cons text (gethash file-name lui-pending-logs))
lui-pending-logs)
(when (null lui-logging-timer)
(setq lui-logging-timer
(run-with-timer lui-logging-flush-delay nil
#'lui-logging-flush))))
(defun lui-logging ()
"If output-queueing is enabled, append the to-be-logged string
to the output queue. Otherwise, write directly to the logfile.
This should be added to `lui-pre-output-hook' by way of
`enable-lui-logging'."
(let ((text (lui-logging-format-string (buffer-string))))
(if (lui-logging-delayed-p)
(lui-logging-enqueue (lui-logging-file-name) text)
(lui-logging-write-to-log (lui-logging-file-name) text))))
(provide 'lui-logging)
;;; lui-logging.el ends here
|