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
|
if not modules then modules = { } end modules ['trac-fil'] = {
version = 1.001,
comment = "for the moment for myself",
author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
copyright = "PRAGMA ADE / ConTeXt Development Team",
license = "see context related readme files"
}
local rawset, tonumber, type, pcall = rawset, tonumber, type, pcall
local format, concat = string.format, table.concat
local openfile = io.open
local date = os.date
local sortedpairs = table.sortedpairs
local P, C, Cc, Cg, Cf, Ct, Cs, Carg = lpeg.P, lpeg.C, lpeg.Cc, lpeg.Cg, lpeg.Cf, lpeg.Ct, lpeg.Cs, lpeg.Carg
local lpegmatch = lpeg.match
local patterns = lpeg.patterns
local cardinal = patterns.cardinal
local whitespace = patterns.whitespace^0
local timestamp = Cf(Ct("") * (
Cg (Cc("year") * (cardinal/tonumber)) * P("-")
* Cg (Cc("month") * (cardinal/tonumber)) * P("-")
* Cg (Cc("day") * (cardinal/tonumber)) * P(" ")
* Cg (Cc("hour") * (cardinal/tonumber)) * P(":")
* Cg (Cc("minute") * (cardinal/tonumber)) * P(":")
* Cg (Cc("second") * (cardinal/tonumber)) * P("+")
* Cg (Cc("thour") * (cardinal/tonumber)) * P(":")
* Cg (Cc("tminute") * (cardinal/tonumber))
)^0, rawset)
local keysvalues = Cf(Ct("") * (
Cg(C(patterns.letter^0) * whitespace * "=" * whitespace * Cs(patterns.unquoted) * whitespace)
)^0, rawset)
local statusline = Cf(Ct("") * (
whitespace * P("[") * Cg(Cc("timestamp") * timestamp ) * P("]")
* whitespace * Cg(Cc("status" ) * keysvalues)
),rawset)
patterns.keysvalues = keysvalues
patterns.statusline = statusline
patterns.timestamp = timestamp
loggers = loggers or { }
local timeformat = format("[%%s%s]",os.timezone(true))
local dateformat = "!%Y-%m-%d %H:%M:%S"
function loggers.makeline(t)
local result = { } -- minimize time that file is open
result[#result+1] = format(timeformat,date(dateformat))
for k, v in sortedpairs(t) do
local tv = type(v)
if tv == "string" then
if v ~= "password" then
result[#result+1] = format(" %s=%q",k,v)
end
elseif tv == "number" or tv == "boolean" then
result[#result+1] = format(" %s=%q",k,tostring(v))
end
end
return concat(result," ")
end
local function append(filename,...)
local f = openfile(filename,"a+")
if not f then
dir.mkdirs(file.dirname(filename))
f = openfile(filename,"a+")
end
if f then
f:write(...)
f:close()
return true
else
return false
end
end
function loggers.store(filename,data) -- a log service is nicer
if type(data) == "table"then
data = loggers.makeline(data)
end
pcall(append,filename,data,"\n")
end
function loggers.collect(filename,result)
if lfs.isfile(filename) then
local r = lpegmatch(Ct(statusline^0),io.loaddata(filename))
if result then -- append
local nofresult = #result
for i=1,#r do
nofresult = nofresult + 1
result[nofresult] = r[i]
end
return result
else
return r
end
else
return result or { }
end
end
function loggers.fields(results) -- returns hash of fields with counts so that we can decide on importance
local fields = { }
if results then
for i=1,#results do
local r = results[i]
for k, v in next, r do
local f = fields[k]
if not f then
fields[k] = 1
else
fields[k] = f + 1
end
end
end
end
return fields
end
local template = [[<!-- log entries: begin --!>
<table>
<tr>%s</tr>
%s
</table>
<!-- log entries: end --!>
]]
function loggers.tohtml(entries,fields)
if not fields or #fields == 0 then
return ""
end
if type(entries) == "string" then
entries = loggers.collect(entries)
end
local scratch, lines = { }, { }
for i=1,#entries do
local entry = entries[i]
local status = entry.status
for i=1,#fields do
local field = fields[i]
local v = status[field.name]
if v ~= nil then
v = tostring(v)
local f = field.format
if f then
v = format(f,v)
end
scratch[i] = format("<td nowrap='nowrap' align='%s'>%s</td>",field.align or "left",v)
else
scratch[i] = "<td/>"
end
end
lines[i] = format("<tr>%s</tr>",concat(scratch))
end
for i=1,#fields do
local field = fields[i]
scratch[i] = format("<th nowrap='nowrap' align='left'>%s</th>", field.label or field.name)
end
local result = format(template,concat(scratch),concat(lines,"\n"))
return result, entries
end
-- loggers.store("test.log", { name = "whatever", more = math.random(1,100) })
-- local fields = {
-- { name = "name", align = "left" },
-- { name = "more", align = "right" },
-- }
-- local entries = loggers.collect("test.log")
-- local html = loggers.tohtml(entries,fields)
-- inspect(entries)
-- inspect(fields)
-- inspect(html)
|