1if not modules then modules = { } end modules ['luat-log'] = {
2 version = 1.001,
3 comment = "companion to trac-log.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18
19
20local next, type, select, print = next, type, select, print
21local format, gmatch, find = string.format, string.gmatch, string.find
22local concat, insert, remove = table.concat, table.insert, table.remove
23local topattern = string.topattern
24local utfchar = utf.char
25
26local writenl = texio.writeselectornl
27local writelf = texio.writeselectorlf
28local write = texio.writeselector
29
30local setmetatableindex = table.setmetatableindex
31local formatters = string.formatters
32local settings_to_hash = utilities.parsers.settings_to_hash
33local sortedkeys = table.sortedkeys
34
35
36
37local variant = "default"
38
39
40logs = logs or { }
41local logs = logs
42
43
44
45formatters.add (
46 formatters, "unichr",
47 [["U+" .. format("%%05X",%s) .. " (" .. utfchar(%s) .. ")"]]
48)
49
50formatters.add (
51 formatters, "chruni",
52 [[utfchar(%s) .. " (U+" .. format("%%05X",%s) .. ")"]]
53)
54
55
56
57local function ignore() end
58
59setmetatableindex(logs, function(t,k) t[k] = ignore ; return ignore end)
60
61local report, subreport, status, settarget, setformats, settranslations
62
63local direct, subdirect, writer, pushtarget, poptarget, setlogfile, settimedlog, setprocessor, setformatters, newline
64
65
66
67
68
69local function ansisupported(specification)
70 if specification ~= "ansi" and specification ~= "ansilog" then
71 return false
72 elseif os and os.enableansi then
73 return os.enableansi()
74 else
75 return false
76 end
77end
78
79do
80
81 if arg and ansisupported then
82
83 for k, v in next, arg do
84 if v == "--ansi" or v == "--c:ansi" then
85 if ansisupported("ansi") then
86 variant = "ansi"
87 end
88 break
89 elseif v == "--ansilog" or v == "--c:ansilog" then
90 if ansisupported("ansilog") then
91 variant = "ansilog"
92 end
93 break
94 end
95 end
96 end
97
98 local whereto = "both"
99 local target = nil
100 local targets = nil
101
102 local formats = table.setmetatableindex("self")
103 local translations = table.setmetatableindex("self")
104
105 local report_yes, subreport_yes, direct_yes, subdirect_yes, status_yes
106 local report_nop, subreport_nop, direct_nop, subdirect_nop, status_nop
107
108
109
110 local variants = {
111 default = {
112 formats = {
113 report_yes = formatters["%-15s > %s\n"],
114 report_nop = formatters["%-15s >\n"],
115 direct_yes = formatters["%-15s > %s"],
116 direct_nop = formatters["%-15s >"],
117 subreport_yes = formatters["%-15s > %s > %s\n"],
118 subreport_nop = formatters["%-15s > %s >\n"],
119 subdirect_yes = formatters["%-15s > %s > %s"],
120 subdirect_nop = formatters["%-15s > %s >"],
121 status_yes = formatters["%-15s : %s\n"],
122 status_nop = formatters["%-15s :\n"],
123 },
124 targets = setmetatableindex( {
125 logfile = 2,
126 log = 2,
127 file = 2,
128 console = 1,
129 terminal = 1,
130 both = 3,
131 },
132 function(t,k)
133 local v = 3 t[k] = v return v
134 end),
135 },
136 ansi = {
137 formats = {
138 report_yes = formatters["[0;33m%-15s [0;1m>[0m %s\n"],
139 report_nop = formatters["[0;33m%-15s [0;1m>[0m\n"],
140 direct_yes = formatters["[0;33m%-15s [0;1m>[0m %s"],
141 direct_nop = formatters["[0;33m%-15s [0;1m>[0m"],
142 subreport_yes = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s\n"],
143 subreport_nop = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m\n"],
144 subdirect_yes = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m %s"],
145 subdirect_nop = formatters["[0;33m%-15s [0;1m>[0;35m %s [0;1m>[0m"],
146 status_yes = formatters["[0;33m%-15s [0;1m:[0m %s\n"],
147 status_nop = formatters["[0;33m%-15s [0;1m:[0m\n"],
148 },
149 targets = setmetatableindex( { 1, 1, 1,
150 logfile = false,
151 log = false,
152 file = false,
153 console = 1,
154 terminal = 1,
155 both = 1,
156 }, function(t,k) local v = 1 t[k] = v return v end),
157 }
158 }
159
160 variants.ansilog = {
161 formats = variants.ansi.formats,
162 targets = variants.default.targets,
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 logs.flush = io.flush
178
179 writer = function(...)
180 if target then
181 writenl(target,...)
182 end
183 end
184
185 newline = function()
186 if target then
187 writelf(target)
188
189 end
190 end
191
192 report = function(a,b,c,...)
193 if not target then
194
195 elseif c ~= nil then
196 writenl(target,report_yes(translations[a],formatters[formats[b]](c,...)))
197 elseif b then
198 writenl(target,report_yes(translations[a],formats[b]))
199 elseif a then
200 writenl(target,report_nop(translations[a]))
201 else
202 writenl(target)
203 end
204 end
205
206 direct = function(a,b,c,...)
207 if not target then
208 return ""
209 elseif c ~= nil then
210 return direct_yes(translations[a],formatters[formats[b]](c,...))
211 elseif b then
212 return direct_yes(translations[a],formats[b])
213 elseif a then
214 return direct_nop(translations[a])
215 else
216 return ""
217 end
218 end
219
220 subreport = function(a,s,b,c,...)
221 if not target then
222
223 elseif c ~= nil then
224 writenl(target,subreport_yes(translations[a],translations[s],formatters[formats[b]](c,...)))
225 elseif b then
226 writenl(target,subreport_yes(translations[a],translations[s],formats[b]))
227 elseif a then
228 writenl(target,subreport_nop(translations[a],translations[s]))
229 else
230 writenl(target)
231 end
232 end
233
234 subdirect = function(a,s,b,c,...)
235 if not target then
236 return ""
237 elseif c ~= nil then
238 return subdirect_yes(translations[a],translations[s],formatters[formats[b]](c,...))
239 elseif b then
240 return subdirect_yes(translations[a],translations[s],formats[b])
241 elseif a then
242 return subdirect_nop(translations[a],translations[s])
243 else
244 return ""
245 end
246 end
247
248 status = function(a,b,c,...)
249 if not target then
250
251 elseif c ~= nil then
252 writenl(target,status_yes(translations[a],formatters[formats[b]](c,...)))
253 elseif b then
254 writenl(target,status_yes(translations[a],formats[b]))
255 elseif a then
256 writenl(target,status_nop(translations[a]))
257 else
258 writenl(target)
259 end
260 end
261
262 settarget = function(askedwhereto)
263 whereto = askedwhereto or whereto or "both"
264 target = targets[whereto]
265 if not target then
266 whereto = "both"
267 target = targets[whereto]
268 end
269 if target == targets.both or target == targets.terminal then
270 logs.flush = io.flush
271 else
272 logs.flush = ignore
273 end
274 end
275
276 local stack = { }
277
278 pushtarget = function(newtarget)
279 insert(stack,target)
280 settarget(newtarget)
281 end
282
283 poptarget = function()
284 if #stack > 0 then
285 settarget(remove(stack))
286 end
287 end
288
289 setformats = function(f)
290 formats = f
291 end
292
293 settranslations = function(t)
294 translations = t
295 end
296
297 setprocessor = function(f)
298 local writeline = writenl
299 writenl = function(target,...)
300 if target then
301 writeline(target,f(...))
302 end
303 end
304 end
305
306 setformatters = function(specification)
307 local t = nil
308 local f = nil
309 local d = variants.default
310 if not specification then
311
312 elseif type(specification) == "table" then
313 t = specification.targets
314 f = specification.formats or specification
315 else
316 if not ansisupported(specification) then
317 specification = "default"
318 end
319 local v = variants[specification]
320 if v then
321 t = v.targets
322 f = v.formats
323 variant = specification
324 end
325 end
326 targets = t or d.targets
327 target = targets[whereto]
328 if f then
329 d = d.formats
330 else
331 f = d.formats
332 d = f
333 end
334 setmetatableindex(f,d)
335 report_yes = f.report_yes
336 report_nop = f.report_nop
337 subreport_yes = f.subreport_yes
338 subreport_nop = f.subreport_nop
339 direct_yes = f.direct_yes
340 direct_nop = f.direct_nop
341 subdirect_yes = f.subdirect_yes
342 subdirect_nop = f.subdirect_nop
343 status_yes = f.status_yes
344 status_nop = f.status_nop
345 settarget(whereto)
346 end
347
348 setformatters(variant)
349
350 setlogfile = ignore
351 settimedlog = ignore
352
353end
354
355
356
357logs.report = report
358logs.subreport = subreport
359logs.status = status
360logs.settarget = settarget
361logs.pushtarget = pushtarget
362logs.poptarget = poptarget
363logs.setformats = setformats
364logs.settranslations = settranslations
365
366logs.setlogfile = setlogfile
367logs.settimedlog = settimedlog
368logs.setprocessor = setprocessor
369logs.setformatters = setformatters
370
371logs.direct = direct
372logs.subdirect = subdirect
373logs.writer = writer
374logs.newline = newline
375
376local data = { }
377local states = nil
378local force = false
379
380function logs.reporter(category,subcategory)
381 local logger = data[category]
382 if not logger then
383 local state = states == true
384 if not state and type(states) == "table" then
385 for c, _ in next, states do
386 if find(category,c) then
387 state = true
388 break
389 end
390 end
391 end
392 logger = {
393 reporters = { },
394 state = state,
395 }
396 data[category] = logger
397 end
398 local reporter = logger.reporters[subcategory or "default"]
399 if not reporter then
400 if subcategory then
401 reporter = function(...)
402 if force or not logger.state then
403 subreport(category,subcategory,...)
404 end
405 end
406 logger.reporters[subcategory] = reporter
407 else
408 local tag = category
409 reporter = function(...)
410 if force or not logger.state then
411 report(category,...)
412 end
413 end
414 logger.reporters.default = reporter
415 end
416 end
417 return reporter
418end
419
420logs.new = logs.reporter
421
422
423
424local ctxreport = logs.writer
425
426function logs.setmessenger(m)
427 ctxreport = m
428end
429
430function logs.messenger(category,subcategory)
431
432 if subcategory then
433 return function(...)
434 ctxreport(subdirect(category,subcategory,...))
435 end
436 else
437 return function(...)
438 ctxreport(direct(category,...))
439 end
440 end
441end
442
443
444
445local function setblocked(category,value)
446 if category == true or category == "all" then
447
448 category, value = "*", true
449 elseif category == false then
450
451 category, value = "*", false
452 elseif value == nil then
453
454 value = true
455 end
456 if category == "*" then
457 states = value
458 for k, v in next, data do
459 v.state = value
460 end
461 else
462 alllocked = false
463 states = settings_to_hash(category,type(states)=="table" and states or nil)
464 for c in next, states do
465 local v = data[c]
466 if v then
467 v.state = value
468 else
469 local p = topattern(c,true,true)
470 for k, v in next, data do
471 if find(k,p) then
472 v.state = value
473 end
474 end
475 end
476 end
477 end
478end
479
480function logs.disable(category,value)
481 setblocked(category,value == nil and true or value)
482end
483
484function logs.enable(category)
485 setblocked(category,false)
486end
487
488function logs.categories()
489 return sortedkeys(data)
490end
491
492function logs.show()
493 local n, c, s, max = 0, 0, 0, 0
494 for category, v in table.sortedpairs(data) do
495 n = n + 1
496 local state = v.state
497 local reporters = v.reporters
498 local nc = #category
499 if nc > c then
500 c = nc
501 end
502 for subcategory, _ in next, reporters do
503 local ns = #subcategory
504 if ns > c then
505 s = ns
506 end
507 local m = nc + ns
508 if m > max then
509 max = m
510 end
511 end
512 local subcategories = concat(sortedkeys(reporters),", ")
513 if state == true then
514 state = "disabled"
515 elseif state == false then
516 state = "enabled"
517 else
518 state = "unknown"
519 end
520
521 report("logging","category %a, subcategories %a, state %a",category,subcategories,state)
522 end
523 report("logging","categories: %s, max category: %s, max subcategory: %s, max combined: %s",n,c,s,max)
524end
525
526local delayed_reporters = { }
527
528setmetatableindex(delayed_reporters,function(t,k)
529 local v = logs.reporter(k.name)
530 t[k] = v
531 return v
532end)
533
534function utilities.setters.report(setter,...)
535 delayed_reporters[setter](...)
536end
537
538directives.register("logs.blocked", function(v)
539 setblocked(v,true)
540end)
541
542directives.register("logs.target", function(v)
543 settarget(v)
544end)
545
546do
547
548 local report = logs.reporter("pages")
549 local texgetcount = tex and tex.getcount
550
551 local real, user, sub = 0, 0, 0
552
553 function logs.start_page_number()
554 real = texgetcount("realpageno")
555 user = texgetcount("userpageno")
556 sub = texgetcount("subpageno")
557 end
558
559 local timing = false
560 local usage = false
561 local lasttime = nil
562
563 logs.private = {
564 enablepagetiming = function()
565 usage = true
566 end,
567 getpagetiming = function()
568 return type(usage) == "table" and usage
569 end,
570 }
571
572 trackers.register("pages.timing", function() timing = "" end)
573
574 function logs.stop_page_number()
575 if timing or usage then
576 local elapsed = statistics.currenttime(statistics)
577 local average, page
578 if not lasttime or real < 2 then
579 average = elapsed
580 page = elapsed
581 else
582 average = elapsed / (real - 1)
583 page = elapsed - lasttime
584 end
585 lasttime = elapsed
586 if timing then
587 timing = formatters[", total %0.03f, page %0.03f, average %0.03f"](elapsed,page,average)
588 end
589 if usage then
590 usage = {
591 page = {
592 real = real,
593 user = user,
594 sub = sub,
595 },
596 time = {
597 elapsed = elapsed,
598 page = page,
599 average = average,
600 }
601 }
602 end
603 end
604 if real <= 0 then
605 report("flushing page%s",timing)
606 elseif user <= 0 then
607 report("flushing realpage %s%s",real,timing)
608 elseif sub <= 0 then
609 report("flushing realpage %s, userpage %s%s",real,user,timing)
610 else
611 report("flushing realpage %s, userpage %s, subpage %s%s",real,user,sub,timing)
612 end
613 logs.flush()
614 end
615
616end
617
618
619
620do
621
622 local texerror = tex and tex.error or print
623
624 function logs.texerrormessage(fmt,first,...)
625 texerror(first and formatters[fmt](first,...) or fmt)
626 end
627
628end
629
630
631
632
633
634
635
636
637
638
639if package.helpers.report then
640 package.helpers.report = logs.reporter("package loader")
641end
642
643
644
645
646
647
648
649do
650
651 local finalactions = { }
652 local fatalerrors = { }
653 local possiblefatal = { }
654 local quitonerror = { }
655 local loggingerrors = false
656
657 function logs.loggingerrors()
658 return loggingerrors
659 end
660
661 local function register(v)
662 loggingerrors = v
663 if type(v) == "string" then
664 local target = settings_to_hash(v)
665 for k, v in next, target do
666 target[k] = string.topattern(k)
667 end
668 return target
669 else
670 return { }
671 end
672 end
673
674 directives.register("logs.errors", function(v) fatalerrors = register(v) end)
675 directives.register("logs.quitonerror",function(v) quitonerror = register(v) end)
676
677 function logs.registerfinalactions(...)
678 insert(finalactions,...)
679 end
680
681 local what = nil
682 local report = nil
683 local state = nil
684 local target = nil
685 local fatal = false
686
687 local function startlogging(t,r,w,s)
688 target = t
689 state = force
690 force = true
691 report = type(r) == "function" and r or logs.reporter(r)
692 what = w
693 pushtarget(target)
694 newline()
695 if s then
696 report("start %s: %s",what,s)
697 else
698 report("start %s",what or "")
699 end
700 if target == "logfile" then
701 newline()
702 end
703 return report
704 end
705
706 local function stoplogging()
707 if target == "logfile" then
708 newline()
709 end
710 report("stop %s",what or "")
711 if target == "logfile" then
712 newline()
713 end
714 poptarget()
715 state = oldstate
716 if fatal then
717 logs.report("error logging","error marked as fatal")
718 luatex.abort()
719 end
720 end
721
722 function logs.startfilelogging(...)
723 return startlogging("logfile", ...)
724 end
725
726 logs.stopfilelogging = stoplogging
727
728 local done = false
729
730 function logs.starterrorlogging(r,w,...)
731 if not done then
732 pushtarget("terminal")
733 newline()
734 logs.report("error logging","start possible issues")
735 poptarget()
736 done = true
737 end
738 if fatalerrors[w] then
739 possiblefatal[w] = true
740 else
741 for k, v in next, quitonerror do
742 if find(w,v) then
743 fatal = true
744 end
745 end
746 for k, v in next, fatalerrors do
747 if find(w,v) then
748 possiblefatal[w] = true
749 break
750 end
751 end
752 end
753 return startlogging("terminal",r,w,...)
754 end
755
756 logs.stoperrorlogging = stoplogging
757
758 function logs.finalactions()
759 if #finalactions > 0 then
760 for i=1,#finalactions do
761 finalactions[i]()
762 end
763 if done then
764 pushtarget("terminal")
765 newline()
766 logs.report("error logging","stop possible issues")
767 poptarget()
768 end
769 return next(possiblefatal) and sortedkeys(possiblefatal) or false
770 end
771 end
772
773end
774
775
776
777local dummy = function() end
778
779function logs.application(t)
780 return {
781 name = t.name or tex.jobname,
782 banner = t.banner,
783 report = logs.reporter(t.name),
784 moreinfo = dummy,
785 export = dummy,
786 help = dummy,
787 identify = dummy,
788 version = dummy,
789 }
790end
791
792
793
794do
795
796 local datetime = os.date
797 local sleep = os.sleep
798 local openfile = io.open
799
800 local f_syslog = formatters["%s %s => %s => %s => %s\r"]
801
802 function logs.system(whereto,process,jobname,category,fmt,arg,...)
803 local message = f_syslog(datetime("%d/%m/%y %H:%m:%S"),process,jobname,category,arg == nil and fmt or format(fmt,arg,...))
804 for i=1,10 do
805 local f = openfile(whereto,"a")
806 if f then
807 f:write(message)
808 f:close()
809 break
810 else
811 sleep(0.1)
812 end
813 end
814 end
815
816end
817 |