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
|
local fs = require 'bee.filesystem'
local time = require 'bee.time'
local monotonic = time.monotonic
local osDate = os.date
local ioOpen = io.open
local tablePack = table.pack
local tableConcat = table.concat
local tostring = tostring
local debugTraceBack = debug.traceback
local mathModf = math.modf
local debugGetInfo = debug.getinfo
local ioStdErr = io.stderr
local m = {}
m.file = nil
m.startTime = time.time() - monotonic()
m.size = 0
m.maxSize = 100 * 1024 * 1024
m.level = 'info'
m.levelMap = {
['trace'] = 1,
['debug'] = 2,
['info'] = 3,
['warn'] = 4,
['error'] = 5,
}
local function trimSrc(src)
if src:sub(1, 1) == '@' then
src = src:sub(2)
end
return src
end
local function init_log_file()
if not m.file then
m.file = ioOpen(m.path, 'w')
if not m.file then
return
end
m.file:write('')
m.file:close()
m.file = ioOpen(m.path, 'ab')
if not m.file then
return
end
m.file:setvbuf 'no'
end
end
local function pushLog(level, ...)
if not m.path then
return
end
local t = tablePack(...)
for i = 1, t.n do
t[i] = tostring(t[i])
end
local str = tableConcat(t, '\t', 1, t.n)
if level == 'error' then
str = str .. '\n' .. debugTraceBack(nil, 3)
end
local info = debugGetInfo(3, 'Sl')
local text = m.raw(0, level, str, info.source, info.currentline, monotonic())
return text
end
function m.trace(...)
pushLog('trace', ...)
end
function m.debug(...)
pushLog('debug', ...)
end
function m.info(...)
pushLog('info', ...)
end
function m.warn(...)
pushLog('warn', ...)
end
function m.error(...)
-- Don't use tail calls,
-- Otherwise, the count of `debug.getinfo` will be wrong
local msg = pushLog('error', ...)
return msg
end
function m.raw(thd, level, msg, source, currentline, clock)
if m.levelMap[level] < (m.levelMap[m.level] or m.levelMap['info']) then
return msg
end
if level == 'error' then
ioStdErr:write(msg .. '\n')
if not m.firstError then
m.firstError = msg
end
end
if m.size > m.maxSize then
return msg
end
init_log_file()
local sec, ms = mathModf((m.startTime + clock) / 1000)
local timestr = osDate('%H:%M:%S', sec)
local agl = ''
if #level < 5 then
agl = (' '):rep(5 - #level)
end
local buf
if currentline == -1 then
buf = ('[%s.%03.f][%s]%s[#%d]: %s\n'):format(timestr, ms * 1000, level, agl, thd, msg)
else
buf = ('[%s.%03.f][%s]%s[#%d:%s:%s]: %s\n'):format(timestr, ms * 1000, level, agl, thd, trimSrc(source), currentline, msg)
end
m.size = m.size + #buf
if m.file then
if m.size > m.maxSize then
m.file:write(buf:sub(1, m.size - m.maxSize))
m.file:write('[REACH MAX SIZE]')
else
m.file:write(buf)
end
end
if log.print then
print(buf)
end
return buf
end
function m.init(root, path)
local lastBuf
if m.file then
m.file:close()
m.file = nil
local file = ioOpen(m.path, 'rb')
if file then
lastBuf = file:read(m.maxSize)
file:close()
end
end
m.path = path:string()
m.prefixLen = #root:string()
m.size = 0
pcall(function ()
if not fs.exists(path:parent_path()) then
fs.create_directories(path:parent_path())
end
end)
if lastBuf then
init_log_file()
if m.file then
m.file:write(lastBuf)
m.size = m.size + #lastBuf
end
end
end
return m
|