-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhato-log.scm
More file actions
184 lines (165 loc) · 6.95 KB
/
hato-log.scm
File metadata and controls
184 lines (165 loc) · 6.95 KB
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
;; hato-log.scm -- Apache-style logging levels
;;
;; Copyright (c) 2005-2009 Alex Shinn. All rights reserved.
;; BSD-style license: http://synthcode.com/license.txt
(require-library posix)
(module hato-log
(define-logger log-open log-close log-display log-format
log-error-message log-call-chain log-error&call-chain
current-log-port)
(import scheme chicken extras data-structures ports posix)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define current-log-port (current-error-port))
(define current-log-file #f)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-syntax define-logger
(er-macro-transformer
(lambda (expr r c)
(let* ((level-info (cadr expr))
(loggers
(let lp ((ls (caddr expr))
(i 0)
(res '()))
(cond ((null? ls)
(reverse res))
((pair? (car ls))
(if (<= (cadar ls) i)
(error "log levels must be increasing" (caddr expr))
(lp (cdr ls) (+ (cadar ls) 1) (cons (car ls) res))))
(else
(lp (cdr ls) (+ i 1) (cons (list (car ls) i) res))))))
(normalize-level
(lambda (desc)
(let ((str (symbol->string desc)))
(if (and (> (string-length str) 4)
(string=? "log-" (substring str 0 4)))
(string->symbol (substring str 4))
desc))))
(log-descriptions
(map (lambda (x)
(cons
(if (pair? (cddr x)) (caddr x) (normalize-level (car x)))
(cadr x)))
loggers))
(level-getter (car level-info))
(level-setter (cadr level-info))
(default-level
(if (pair? (cddr level-info))
(caddr level-info)
(cadr (car (reverse loggers)))))
(level-var '*log-level*)
(level-indexer 'x->level)
(_define (r 'define))
(x (r 'x)))
`(,(r 'begin)
(,_define (,level-indexer ,x)
(,(r 'cond)
((,(r 'assq) ,x ',log-descriptions)
,(r '=>) ,(r 'cdr))
((,(r 'integer?) ,x) ,x)
(,(r 'else) (,(r' error) "invalid log level" ,x))))
(,_define ,level-var
(,(r 'cons) (,level-indexer ',default-level) '()))
(,_define (,level-getter) (,(r 'car) ,level-var))
(,_define (,level-setter ,x)
(,(r 'set-car!) ,level-var (,level-indexer ,x)))
,@(map
(lambda (logger description)
(let ((name (car logger))
(level (cadr logger)))
`(,(r 'define-syntax) ,name
(,(r 'er-macro-transformer)
(,(r 'lambda) (,x ,(r 'r2) ,(r 'c2))
(,(r 'quasiquote)
((,(r 'unquote) (,(r 'r2) 'if))
((,(r 'unquote) (,(r 'r2) '>=))
(,(r 'car) ,level-var)
,level)
((,(r 'unquote) (,(r 'r2) 'log-format))
,(symbol->string (car description))
(,(r 'unquote-splicing) (,(r 'cdr) ,x))))))))))
loggers
log-descriptions))))))
;;(define-constant *week-day-abbrevs*
;; (vector "Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"))
;;(define (week-day-abbrev i)
;; (if (<= 0 i 6) (vector-ref *week-day-abbrevs* i) "___"))
(define (current-log-date-string)
(define (pad0 n len)
(let* ((s (number->string n))
(diff (- len (string-length s))))
(if (> diff 0) (string-append (make-string diff #\0) s) s)))
(let ((now (seconds->local-time (current-seconds))))
;; this makes lexicographic sort == chronological sort
(sprintf "~A-~A-~A ~A:~A:~A"
(pad0 (+ 1900 (vector-ref now 5)) 4)
(pad0 (+ 1 (vector-ref now 4)) 2)
(pad0 (vector-ref now 3) 2)
(pad0 (vector-ref now 2) 2)
(pad0 (vector-ref now 1) 2)
(pad0 (vector-ref now 0) 2))))
;; Use file-locking to let multiple processes write to the same log
;; file. On error try to re-open the log file. We keep the port open
;; so that even if you mv the file (e.g. when rotating logs) we keep
;; writing to it in the new location. To force writing to a new file in
;; the original location, use cp+rm instead of mv, or alternately SIGHUP
;; the process after the mv.
(define (log-display type str)
(let* ((prefix (sprintf "[~A] [~A] " (current-log-date-string) type))
(str (string-append prefix ; prefix all lines in message
(string-intersperse (string-split str "\n")
(string-append "\n" prefix))
"\n")))
(condition-case
(let ((lock (and (output-port? current-log-port)
(file-lock current-log-port))))
;; this is redundant with POSIX O_APPEND
;;(set-file-position! current-log-port 0 seek/end)
(display str current-log-port)
(flush-output current-log-port)
(and lock (file-unlock lock)))
(e1 () ; try to re-open log-file, use stderr as backup
(condition-case
(begin
(log-close)
(log-open)
(let ((lock (file-lock current-log-port)))
(display str current-log-port)
(flush-output current-log-port)
(file-unlock lock)))
(e2 ()
(let ((err (current-error-port)))
(if (and (output-port? err)
(not (eq? err current-log-port)))
(display str err)))))))))
(define (log-format type fmt . args)
(log-display type (apply sprintf fmt args)))
(define (log-open . o)
(if (pair? o) (set! current-log-file (car o)))
(if current-log-file
(set! current-log-port (open-output-file current-log-file append:))))
(define (log-close)
(if (output-port? current-log-port)
(close-output-port current-log-port)))
(define (log-error-message type exn)
(log-format type "~A" (call-with-output-string
(lambda (out) (print-error-message exn out "")))))
(define (log-call-chain . o)
(let ((call-chain (if (pair? o) (car o) (get-call-chain 1 #t))))
(for-each
(lambda (info)
(let ((more1 (##sys#slot info 1))
(more2 (##sys#slot info 2)))
(fprintf current-log-port "\t~A\t\t" (##sys#slot info 0))
(if more2 (fprintf current-log-port "[~A] " more2))
(if more1
(##sys#with-print-length-limit
100
(lambda () (##sys#print more1 #t current-log-port))))
(newline current-log-port)))
call-chain)))
(define (log-error&call-chain type exn)
(let ((call-chain (get-call-chain 1 #t)))
(log-error-message type exn)
(log-call-chain call-chain)))
)