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