-
Notifications
You must be signed in to change notification settings - Fork 18
/
logview.el
4526 lines (4020 loc) · 234 KB
/
logview.el
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
;;; logview.el --- Major mode for viewing log files -*- lexical-binding: t -*-
;; Copyright (C) 2015-2024 Paul Pogonyshev
;; Author: Paul Pogonyshev <pogonyshev@gmail.com>
;; Maintainer: Paul Pogonyshev <pogonyshev@gmail.com>
;; Version: 0.19.1snapshot
;; Keywords: files, tools
;; Homepage: https://github.com/doublep/logview
;; Package-Requires: ((emacs "25.1") (datetime "0.8") (extmap "1.0"))
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of
;; the License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see http://www.gnu.org/licenses.
;;; Commentary:
;; Logview mode provides syntax highlighting, filtering and other
;; features for various log files. The main target are files similar
;; to ones generated by Log4j, Logback and other Java logging
;; libraries, but there is really nothing Java-specific in the mode
;; and it should work just fine with any log that follows similar
;; structure, probably after some configuration.
;;; Code:
;; Internally, we cannot use the point for most purposes, since correct interpretation of
;; `logview-entry' text property value heavily depends on knowing *exact* entry beginning.
;; When moving point, Emacs always adjusts it so it doesn't fall inside an invisible
;; range, which screws things up for us. In the same vein, we always operate on
;; temporarily widened buffer, because it is not even possible to query text properties
;; outside of narrowed-to region.
;;
;; While the above sounds like potential problems only for the case someone hides half of
;; an entry or narrows from the middle of an entry, it really isn't. I have experienced
;; bugs even in normal testing with entries fully hidden or shown only.
;;
;; In short, use point (e.g. `goto-char') only when delivering results of internal
;; computations to the user.
(eval-when-compile (require 'cl-lib)
(require 'help-mode))
(require 'datetime)
(require 'extmap)
;; For `string-trim' in earlier Emacs versions.
(require 'subr-x)
;; We _append_ self to the list of mode rules so as to not clobber
;; other rules, as '.log' is a common file extension. This also gives
;; the user an easy way to prevent 'logview' from being autoselected.
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.log\\(?:\\.[0-9\\-]+\\)?\\'" . logview-mode) t)
;;; Public variables.
;; This needs to go before customization, since the values are used in
;; compound widget types.
(defvar logview-std-submodes
'(("SLF4J" . ((format . "TIMESTAMP [THREAD] LEVEL NAME -")
(levels . "SLF4J")
(aliases . ("Log4j" "Log4j2" "Logback"))))
;; We misuse thread as a field for hostname.
("UNIX" . ((format . "TIMESTAMP THREAD NAME:")))
("Apache Error Log" . ((format . "[TIMESTAMP] [NAME:LEVEL] [THREAD] MESSAGE")
(levels . "RFC 5424 lowercase")))
("Monolog" . ((format . "[TIMESTAMP] NAME[THREAD].LEVEL: MESSAGE")
(levels . "RFC 5424")
(aliases . ("PHP" "PSR-3")))))
"Alist of standard submodes.
This value is used as the fallback for customizable
`logview-additional-submodes'.")
(defvar logview-std-level-mappings
'(("SLF4J" . ((error "ERROR")
(warning "WARN")
(information "INFO")
(debug "DEBUG")
(trace "TRACE")
(aliases "Log4j" "Log4j2" "Logback")))
("JUL" . ((error "SEVERE")
(warning "WARNING")
(information "INFO")
(debug "CONFIG" "FINE")
(trace "FINER" "FINEST")))
("RFC 5424" . ((error "EMERGENCY" "ALERT" "CRITICAL" "ERROR")
(warning "WARNING")
(information "NOTICE" "INFO")
(debug "DEBUG")
(trace)
(aliases "syslog")))
("RFC 5424 lowercase" . ((error "emergency" "alert" "critical" "error")
(warning "warning")
(information "notice" "info")
(debug "debug")
(trace)
(aliases "Apache error log"))))
"Standard mappings of actual log levels to mode's final levels.
This alist value is used as the fallback for customizable
`logview-additional-level-mappings'.")
(defvar logview-std-timestamp-formats
(let (formats)
(dolist (data '(("ISO 8601 datetime + millis" "yyyy-MM-dd HH:mm:ss.SSS")
("ISO 8601 datetime + micros" "yyyy-MM-dd HH:mm:ss.SSSSSS")
("ISO 8601 datetime" "yyyy-MM-dd HH:mm:ss")
("ISO 8601 datetime (with 'T') + millis" "yyyy-MM-dd'T'HH:mm:ss.SSS")
("ISO 8601 datetime (with 'T') + micros" "yyyy-MM-dd'T'HH:mm:ss.SSSSSS")
("ISO 8601 datetime (with 'T')" "yyyy-MM-dd'T'HH:mm:ss")
("ISO 8601 time only + millis" "HH:mm:ss.SSS")
("ISO 8601 time only + micros" "HH:mm:ss.SSSSSS")
("ISO 8601 time only" "HH:mm:ss")
(nil "EEE MMM dd HH:mm:ss.SSSSSS yyyy")
(nil "MMM d HH:mm:ss")
(nil "MMM d h:mm:ss a")
(nil "h:mm:ss a")))
(push (list (or (car data) (cadr data)) (cons 'java-pattern (cadr data))) formats)
(when (string-match-p "\\." (cadr data))
(nconc (car formats) '((datetime-options :any-decimal-separator t))))
(when (car data)
(nconc (car formats) (list (list 'aliases (cadr data))))))
(nreverse formats))
"Alist of standard timestamp formats.
This value is used as the fallback for customizable
`logview-additional-timestamp-formats'.")
(defvar logview-font-lock-defaults '(nil nil t nil
(font-lock-fontify-region-function . logview--fontify-region))
"Value of `font-lock-defaults' in Logview buffers.
Derived modes shouldn't touch the value of this variable.
Instead, in their initialization functions they can modify
`font-lock-defaults' (already having a buffer-local value) as
needed. They may also use `font-lock-add-keywords' and similar
functions.
However, remember that Logview uses heavily optimized font-lock
overrides. Some functionality you'd expect a major mode to
support might not work. You can file a bug in the issue tracker
if you have troubles writing a derived mode.")
;;; Customization.
(defgroup logview nil
"Log viewing mode."
:group 'text)
(defun logview--set-submode-affecting-variable (variable value)
(set variable value)
(when (fboundp #'logview--maybe-guess-submodes-again)
(logview--maybe-guess-submodes-again)))
(defun logview--set-highlight-affecting-variable (variable value)
(set variable value)
(dolist (buffer (buffer-list))
(with-current-buffer buffer
(when (and (eq major-mode 'logview-mode) (with-no-warnings logview--highlighted-view-name))
(logview--refontify-buffer)))))
(defvar logview--additional-submodes-type
(let* ((italicize (lambda (string) (propertize string 'face 'italic)))
(mapping-option (lambda (mapping)
(let ((name (car mapping))
(aliases (cdr (assq 'aliases (cdr mapping)))))
(list 'const
:tag (if aliases
(format "%s (aka: %s)" (funcall italicize name) (mapconcat italicize aliases ", "))
(funcall italicize name))
name)))))
(list 'repeat (list 'cons '(string :tag "Name")
(list 'list :tag "Definition"
'(cons :tag "" (const :tag "Format:" format) string)
(list 'set :inline t
(list 'cons :tag "" '(const :tag "Level map:" levels)
(append '(choice)
(mapcar mapping-option logview-std-level-mappings)
'((string :tag "Other name"))))
(list 'cons :tag "" '(const :tag "Timestamp:" timestamp)
(list 'choice
'(const :tag "Any supported" nil)
(list 'repeat
(append '(choice)
(mapcar mapping-option logview-std-timestamp-formats)
'((string :tag "Other name"))))))
'(cons :tag "" (const :tag "Aliases:" aliases) (repeat string))))))))
(defcustom logview-additional-submodes nil
"Association list of log submodes (file parsing rules).
A few common submodes are already defined by the mode in variable
`logview-std-submodes', but the ones you add here always take
precedence.
Submode definition has one required and several optional fields:
format
The only mandatory and the most important field that defines
how log entries are built from pieces. There are currently
four such supported pieces: \"TIMESTAMP\", \"LEVEL\", \"NAME\"
and \"THREAD\". All four are optional. For example, Log4j,
by default formats entries according to this pattern:
TIMESTAMP [THREAD] LEVEL NAME -
Additionally, you can use special placeholder \"IGNORED\" if
needed. Usecase for it are log files that contain too many
fields to map to the ones Logview supports natively.
Finally, you can explicitly specify \"MESSAGE\" field at the
very end of the format string. Normally, you can leave that
to Logview, just as in the example above. However, when the
mode adds the field itself, it also prepends it with a space,
which might be incorrect for some special custom submodes.
Regular expressions to match specified entry pieces are
generated by Logview based on surrounding characters (e.g.
spaces in the example for Log4j except for \"THREAD\", which
is surrounded by brackets). However, if these are not
suitable in your case, for \"NAME\", \"THREAD\" and \"IGNORED\"
you can explicitly specify the desired regexp. For example:
<<RX:IGNORED:[a-z]+>>
will ignore one or more Latin letters, but not anything else.
levels [may be optional]
Level mapping (see `logview-additional-level-mappings') used
for this submode. This field is optional only if the submode
lacks levels altogether.
There are some predefined values valid for this field:
\"SLF4J\" (and its aliases \"Log4j\", \"Log4j2\",
\"Logback\", \"JUL\" and the syslog standard \"RFC 5424\".
See variable `logview-std-level-mappings' for details.
timestamp [optional]
If set, must be a list of timestamp format names to try (see
`logview-additional-timestamp-formats'). If not set or
empty, all defined timestamp formats will be tried.
aliases [optional]
Submode can have any number of optional aliases, which work just
as the name."
:group 'logview
:type logview--additional-submodes-type
:set 'logview--set-submode-affecting-variable
:set-after '(logview-additional-timestamp-formats logview-additional-level-mappings))
(defcustom logview-additional-level-mappings nil
"Association list of log level mappings.
A few common maps are already defined by the mode in variable
`logview-std-level-mappings', but the ones you add here always
take precedence.
Each mapping has a name, by which it is referred from submode
definition. Mapping itself consists of five lists of strings:
error levels, warning levels, information levels, debug levels
and trace levels. In these lists you should add all possible
real levels that can appear in log file, in descending order of
severity.
For example, for Java SLF4J (Log4j, Logback, etc.) the mapping
looks like this:
Error levels: ERROR
Warning levels: WARN
Information levels: INFO
Debug levels: DEBUG
Trace levels: TRACE
This is not a coincidence, as the mode is primarily targeted at
SLF4J log files.
However, mapping for JUL (java.util.logging) framework looks more
complicated:
Error levels: SEVERE
Warning levels: WARNING
Information levels: INFO
Debug levels: CONFIG, FINE
Trace levels: FINER, FINEST
JUL has seven severity levels and we need to map them to five the
mode supports. So the last two lists contain two levels each.
It is also legal to have empty lists, usually if there are less
than five levels, or if some of the levels do not conceptually
map to the levels of the mode. This is the case with RFC 5424:
Error levels: EMERGENCY, ALERT, CRITICAL, ERROR
Warning levels: WARNING
Information levels: NOTICE, INFO
Debug levels: DEBUG
Trace levels:
Mapping can have any number of optional aliases, which work just
as the name."
:group 'logview
:type '(repeat (cons (string :tag "Name")
(list :tag "Definition"
(cons :tag "" (const :tag "Error levels:" error) (repeat string))
(cons :tag "" (const :tag "Warning levels:" warning) (repeat string))
(cons :tag "" (const :tag "Information levels:" information) (repeat string))
(cons :tag "" (const :tag "Debug levels:" debug) (repeat string))
(cons :tag "" (const :tag "Trace levels:" trace) (repeat string))
(set :inline t
(cons :tag "" (const :tag "Aliases:" aliases) (repeat string))))))
:set 'logview--set-submode-affecting-variable)
(defcustom logview-additional-timestamp-formats nil
"Association list of additional timestamp formats.
A few common formats are already defined by the mode in variable
`logview-std-timestamp-formats', but the ones you add here always
take precedence.
Each format has a name, by which it can be referred from submode
definition. A format is defined by Java-like pattern. If the
pattern contains text strings, e.g. month names, you can specify
the locale to use (defaults to English).
See `datetime' library for the help about patterns, or read
https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html
A more complicated and mostly obsolete way to specify format is
by using regular expression timestamp must match. It is strongly
recommended to make the expression as strict as possible to avoid
false positives. For example, if you entered something like
\"\\w+\" as an expression, this would often lead to Logview mode
autoselecting wrong submode and thus parsing log files
incorrectly. Regular expression is ignored if Java pattern is
also specified.
Timestamp format can have any number of optional aliases, which
work just as the name."
:group 'logview
:type '(repeat (cons (string :tag "Name")
(list :tag "Definition"
(set :inline t
(cons :tag "" (const :tag "Java pattern:" java-pattern) string)
(cons :tag "" (const :tag "Locale:" locale) symbol)
(cons :tag "" (const :tag "Datetime options:" datetime-options) plist)
(cons :tag "" (const :tag "Regular expression:" regexp) regexp)
(cons :tag "" (const :tag "Aliases:" aliases) (repeat string))))))
:set 'logview--set-submode-affecting-variable)
(defcustom logview-guess-lines 500
"When guessing submodes, consider this many lines at the top.
If any line corresponds to a defined submode, all the others are
not even looked at. If several lines look like log entry starts,
but still cannot be matched against known submodes, the rest is
skipped, see variable `logview-max-promising-lines'. However,
setting this to a ridiculously large number can still be slow."
:group 'logview
:type 'integer)
(defcustom logview-max-promising-lines 3
"Abort submode guessing after this many \"promising\" lines.
If a line generally looks like a start of log entry to Logview,
it is considered \"promising\". If several such lines still give
no matching submode, Logview aborts guessing. This helps
avoiding very long unsuccessful guessing times even when
`logview-guess-lines' is large.
Setting this to zero makes the mode match against all
`logview-guess-lines'."
:group 'logview
:type 'integer)
(defcustom logview-auto-revert-mode nil
"Automatically put recognized buffers into Auto-Revert mode.
Buffers for which no appropriate submode can be guessed are not
affected as well buffers not associated with files. Having this
set to \"Off\" doesn't prevent Global Auto-Revert mode from
affecting Logview buffers.
Whenever new text is added to the buffer, it is automatically
parsed, highlighted and all currently active filters are applied
to it.
To temporarily activate or deactivate Auto-Revert (Tail) mode in
a Logview buffer type `\\<logview-mode-map>\\[auto-revert-mode]' or `\\<logview-mode-map>\\[auto-revert-tail-mode]'."
:group 'logview
:type '(choice (const :tag "Off" nil)
(const :tag "Auto-Revert mode" auto-revert-mode)
(const :tag "Auto-Revert Tail mode" auto-revert-tail-mode)))
(defcustom logview-reassurance-chars 5000
"Compare this many characters before appending file tail.
This value is used by the command `logview-append-log-file-tail'
to compare part of the file on disk with part of the buffer to
make sure (even if not with 100% guarantee) that the buffer
really represents beginning of its backing file. The command
will refuse to complete operation unless this check succeeds."
:group 'logview
:type 'integer)
(defcustom logview-target-gap-length 60
"Default target gap length for `\\<logview-mode-map>\\[logview-next-timestamp-gap]' and similar commands.
This must be a non-negative number of seconds. Can be changed
temporarily for a single buffer with `\\<logview-mode-map>\\[logview-change-target-gap-length]'."
:group 'logview
:type 'number)
(defcustom logview-copy-visible-text-only t
"Whether to copy, kill, etc. only visible selected text.
Standard Emacs behavior is to copy even invisible text, but that
typically doesn't make much sense with filtering.
To temporarily change this on per-buffer basis type `\\<logview-mode-map>\\[logview-toggle-copy-visible-text-only]'."
:group 'logview
:type 'boolean)
(defcustom logview-search-only-in-messages nil
"Whether to incrementally search only in messages.
Normally search is not restricted and matches can be found
anywhere. However, it is sometimes useful to ignore other parts
of log entries, e.g. timestamp when searching for numbers.
To temporarily change this on per-buffer basis type `\\<logview-mode-map>\\[logview-toggle-search-only-in-messages]'."
:group 'logview
:type 'boolean)
(defcustom logview-preview-filter-changes t
"Whether to preview filters as they are being edited.
This preview is activated whenever you change the filters in the
buffer popped up by `\\<logview-mode-map>\\[logview-edit-filters]' or `\\<logview-mode-map>\\[logview-edit-thread-narrowing-filters]'.
To temporarily change this on per-buffer basis type `\\<logview-mode-map>\\[logview-toggle-filter-preview]'."
:group 'logview
:type 'boolean)
(defcustom logview-show-ellipses t
"Whether to show ellipses to indicate hidden log entries.
To temporarily change this on per-buffer basis type `\\<logview-mode-map>\\[logview-toggle-show-ellipses]'."
:group 'logview
:type 'boolean)
(defcustom logview-highlighted-entry-part 'whole
"Which parts of an entry get highlighted with `\\<logview-mode-map>\\[logview-highlight-view-entries]'."
:group 'logview
:type '(choice (const :tag "The whole entry" whole)
(const :tag "Entry header (date, level, etc.)" header)
(const :tag "Entry message" message))
:set 'logview--set-highlight-affecting-variable)
(defcustom logview-pulse-entries '(section-movement navigation-view timestamp-gap)
"When to briefly highlight the current entry.
You can also pulse the current entry unconditionally with `\\<logview-mode-map>\\[logview-pulse-current-entry]' command."
:group 'logview
:type '(set :inline t
(const :tag "After section movement commands" section-movement)
(const :tag "After navigating a view with `\\<logview-mode-map>\\[logview-next-navigation-view-entry]' or `\\<logview-mode-map>\\[logview-previous-navigation-view-entry]'" navigation-view)
(const :tag "After navigating within the current entry with `\\<logview-mode-map>\\[logview-go-to-message-beginning]'" message-beginning)
(const :tag "After finding large gaps in entry timestamps (`\\<logview-mode-map>\\[logview-next-timestamp-gap]' and similar)" timestamp-gap)
(const :tag "After other entry movement commands" movement)))
(defcustom logview-views-file (locate-user-emacs-file "logview.views")
"Simple text file in which defined views are stored."
:group 'logview
:type 'file)
(defcustom logview-cache-filename (locate-user-emacs-file "logview-cache.extmap")
"Internal non-human-readable cache.
Customizable in case you want to put it somewhere else. This
file can be safely deleted, but will be recreated by Logview next
time you use the mode. Used to make startup faster."
:group 'logview
:type 'file)
(defcustom logview-completing-read-function nil
"Completion system used by Logview."
:group 'logview
:type '(radio
(const :tag "Auto" nil)
(function-item completing-read)
(function-item ido-completing-read)
(function :tag "Custom function")))
(defgroup logview-faces nil
"Faces for Logview mode."
:group 'logview)
(defface logview-level-error
'((t :inherit error))
"Face to use for error level strings.")
(defface logview-error-entry
'((((background dark))
:background "#600000")
(t
:background "#ffe0e0"))
"Face to use for error log entries."
:group 'logview-faces)
(defface logview-level-warning
'((t :inherit warning))
"Face to use for warning level strings."
:group 'logview-faces)
(defface logview-warning-entry
'((((background dark))
:background "#606000")
(t
:background "#ffffe0"))
"Face to use for warning log entries."
:group 'logview-faces)
(defface logview-level-information
'((t :inherit success))
"Face to use for information level strings."
:group 'logview-faces)
(defface logview-information-entry
'((((background dark))
:background "#004000")
(t
:background "#e8ffe8"))
"Face to use for information log entries."
:group 'logview-faces)
(defface logview-level-debug
nil
"Face to use for debug level strings."
:group 'logview-faces)
(defface logview-debug-entry
nil
"Face to use for debug log entries."
:group 'logview-faces)
(defface logview-level-trace
'((t :inherit shadow))
"Face to use for trace level strings."
:group 'logview-faces)
(defface logview-trace-entry
'((((background dark))
:background "#404040")
(t
:background "#f0f0f0"))
"Face to use for trace log entries."
:group 'logview-faces)
(defface logview-timestamp
'((t :inherit font-lock-builtin-face))
"Face to use for log entry timestamp."
:group 'logview-faces)
(defface logview-name
'((t :inherit font-lock-string-face))
"Face to use for logger name."
:group 'logview-faces)
(defface logview-thread
'((t :inherit font-lock-variable-name-face))
"Face to use for logger thread."
:group 'logview-faces)
(defface logview-section
'((t :inverse-video t
:weight bold))
"Face to use for a section's header."
:group 'logview-faces)
(defface logview-highlight
'((((background dark))
:background "#8030e0")
(t
:background "#f8d0ff"))
"Face to highlight entries of a view chosen with `\\<logview-mode-map>\\[logview-highlight-view-entries]'.
Variable `logview-highlighted-entry-part' controls how exactly
this face is applied."
:group 'logview-faces)
(defface logview-pulse
'((((background dark))
:background "#606000")
(t
:background "#c0c0ff"))
"Face to briefly highlight entries to draw attention.
Variable `logview-pulse-entries' controls in which situations
this face is used."
:group 'logview-faces)
(defface logview-unsearchable
'((t :inherit shadow))
"Face used to “dim” unsearchable text when searching incrementally.
Currently used only if `logview-search-only-in-messages' is
active. That option can be activated in multiple ways, including
by typing `\\<logview-isearch-map>\\[logview-toggle-search-only-in-messages]' during the search.")
(defface logview-unprocessed
'((t :inherit shadow))
"Face to highlight otherwise unfontified and unfiltered entries.
Logview tries to make Emacs more responsive by periodically
making \"pauses\" in its fontification and filtering process. In
large buffers with strict filters that exclude most entries, this
can mean that you can sometimes see not-yet-processed entries.
Those will be highlighted (or rather dimmed, with the default
settings) with this face.")
(defface logview-edit-filters-type-prefix
'((((background dark))
:background "#604000"
:weight bold)
(t
:background "#ffe0c0"
:weight bold))
"Face to use for type prefixes in filter editing buffer."
:group 'logview-faces)
;;; Internal variables and constants.
;; {LOCKED-NARROWING}
;; Earlier Emacs 29 snapshots: need to set this variable to nil.
(defvar long-line-threshold)
;; Keep in sync with `logview--entry-*' and `logview--find-region-entries'.
(defconst logview--timestamp-group 1)
(defconst logview--level-group 2)
(defconst logview--name-group 3)
(defconst logview--thread-group 4)
(defconst logview--ignored-group 5)
(defconst logview--message-group 6)
(defconst logview--final-levels '(error warning information debug trace)
"List of final (submode-independent) levels, most to least severe.")
(defconst logview--entry-part-regexp (rx (or (seq bow (or (group "TIMESTAMP") (group "LEVEL") (group "NAME")
(group "THREAD") (group "IGNORED") (group "MESSAGE")) ;; 1--6, see above
eow)
(seq "<<RX:" (or (group "NAME") (group "THREAD") (group "IGNORED")) ;; 7--9
":" (group (+? anything)) ">>")))) ;; 10
(defconst logview--timestamp-entry-part-regexp (rx bow "TIMESTAMP" eow))
(defvar logview--datetime-matching-options '(:second-fractional-extension t
:only-4-digit-years t
:accept-leading-space t
:require-leading-zeros t
:forbid-unnecessary-zeros t))
(defvar logview--datetime-parsing-options '(:second-fractional-extension t
:case-insensitive t
:lax-whitespace t))
(defvar logview--all-timestamp-formats-cache nil)
(defconst logview--valid-filter-prefixes '("lv" "LV" "a+" "a-" "t+" "t-" "m+" "m-"))
(defvar logview--custom-submode-revision 0)
(defvar logview--custom-submode-state nil)
(defvar logview--submode-guessing-timer nil)
(defvar logview--need-submode-guessing nil)
(defvar-local logview--custom-submode-guessed-with 0)
;; Don't access these as variables directly, use functions with the same name instead.
(defvar-local logview--point-min nil)
(defvar-local logview--point-max nil)
(defvar-local logview--submode-name nil)
(defvar-local logview--entry-regexp nil)
(defvar-local logview--submode-features nil)
(defvar-local logview--submode-level-data nil
"An alist of level strings to (INDEX . (ENTRY-FACE . STRING-FACE)).")
(defvar-local logview--submode-level-faces nil
"A vector of (ENTRY-FACE . STRING-FACE).")
(defvar-local logview--submode-timestamp-parser nil)
(defvar-local logview--timestamp-difference-format-string nil)
(defvar-local logview--timestamp-gap-format-string nil)
(defvar-local logview--as-important-level nil)
(defvar-local logview--hide-all-details nil)
(defvar-local logview--timestamp-difference-base nil
"Either nil or (ENTRY . START).
ENTRY will have its timestamp parsed.")
(defvar-local logview--timestamp-difference-per-thread-bases nil
"Either nil or a hash-table of strings to cons cells.
See `logview--timestamp-difference-base' for details.")
(defvar-local logview--timestamp-difference-to-section-headers nil)
(defvar-local logview--buffer-target-gap-length nil)
(defvar-local logview--last-found-large-gap nil)
(defvar-local logview--main-filter-text "")
(defvar-local logview--thread-narrowing-filter-text "")
(defvar-local logview--narrow-to-section-headers nil)
(defvar-local logview--preview-filter-text nil
"Filters for which to show a preview.
Take precedence over real filters. When set, must be a cons cell
of (IS-MAIN . FILTER-TEXT).")
(defvar-local logview--effective-filter nil
"See `logview--do-parse-filters' for the format.")
;; I also considered using private cons cells where we could reset `car' e.g. from
;; `logview-filtered' to nil. However, this is very shaky and will stop working if any
;; minor mode tweaks `invisible' property the "wrong" way. Another possibility would be
;; to use uninterned symbols, but that would be very confusing, since in the output they
;; would look the same. Therefore, I decided to include "generation" in the symbol
;; instead.
(defvar-local logview--filtered-symbol 'logview-filtered/0)
(defvar-local logview--hidden-entry-symbol 'logview-hidden-entry/0)
(defvar-local logview--hidden-details-symbol 'logview-hidden-details/0)
(defvar logview--submode-name-history nil)
(defvar logview--timestamp-format-history nil)
(defvar logview--name-regexp-history nil)
(defvar logview--thread-name-history nil)
(defvar logview--thread-regexp-history nil)
(defvar logview--message-regexp-history nil)
(defvar-local logview--process-buffer-changes nil)
(defvar logview--views nil)
(defvar logview--views-initialized nil)
(defvar logview--views-need-saving nil)
(defvar logview--view-name-history)
(defvar-local logview--section-view-name nil)
(defvar-local logview--section-header-filter nil)
(defvar-local logview--sections-thread-bound t)
(defvar-local logview--navigation-view-name nil)
(defvar-local logview--highlighted-view-name nil)
(defvar-local logview--highlighted-filter nil)
(defvar-local logview--filter-editing-buffer nil)
(defvar-local logview--thread-narrowing-filter-editing-buffer nil)
(defvar logview--view-editing-buffer nil)
;; Not too small to avoid calling `logview--fontify-region' and
;; `logview--find-region-entries' often: calling and setup involves some overhead.
(defvar logview--lazy-region-size 50000)
(defvar logview--max-fontified-in-row 10)
(defvar logview--pending-refontifications nil)
(defvar-local logview--postpone-fontification nil)
(defvar-local logview--num-fontified-in-row 0)
(defvar-local logview-filter-edit--mode nil)
(defvar-local logview-filter-edit--editing-views-for-submode nil)
(defvar-local logview-filter-edit--parent-buffer nil)
(defvar-local logview-filter-edit--window-configuration nil)
(defvar-local logview-filter-edit--preview-timer nil)
(defvar logview-filter-edit--filters-hint-comment
"# Press C-c C-c to save edited filters, C-c C-k to quit without saving.
# Use C-c C-a to apply the changes without quitting the buffer.
")
(defvar logview-filter-edit--thread-narrowing-filters-hint-comment
"# Press C-c C-c to save edited filters, C-c C-k to quit without saving.
# Use C-c C-a to apply the changes without quitting the buffer.
# Only `t+' and `t-' filters are valid for thread narrowing.
")
(defvar logview-filter-edit--views-hint-comment
"# Press C-c C-c to save edited views, C-c C-k to quit without saving.
# Use C-c C-a to apply the changes without quitting the buffer.
")
(defconst logview--view-header-regexp (rx bol (group "view") (1+ " ") (group (1+ nonl)) eol))
(defconst logview--view-submode-regexp (rx bol (group "submode") (1+ " ") (group (1+ nonl)) eol))
(defconst logview--view-index-regexp (rx bol (group "index") (1+ " ") (group (? "-") (1+ digit)) eol))
(defvar logview--cheat-sheet
'(("Movement"
(logview-go-to-message-beginning "Beginning of entry’s message")
(logview-next-entry logview-previous-entry "Next / previous entry")
(logview-next-as-important-entry logview-previous-as-important-entry "Next / previous ‘as important’ entry")
(logview-next-navigation-view-entry logview-previous-navigation-view-entry "Next / previous entry in the navigation view (see below)")
(logview-next-timestamp-gap logview-previous-timestamp-gap "Next / previous large gap in entry timestamps")
(logview-next-timestamp-gap-in-this-thread logview-next-timestamp-gap-in-this-thread "Same, but only within this thread")
(logview-first-entry logview-last-entry "First / last entry")
"‘As important’ means entries with the same or higher level. See also
commands in ‘Sections’ below")
("Narrowing and widening"
(logview-narrow-from-this-entry logview-narrow-up-to-this-entry "Narrow from / up to this entry")
(logview-widen "Widen")
(logview-widen-upwards logview-widen-downwards "Widen upwards / downwards")
"See also commands in ‘Sections’ below")
("Filtering by level"
(logview-show-only-errors "Show only errors")
(logview-show-errors-and-warnings "Show errors and warnings")
(logview-show-errors-warnings-and-information "Show errors, warnings and information")
(logview-show-errors-warnings-information-and-debug "Show all levels except trace")
(logview-show-all-levels "Show entries of all levels")
(logview-show-only-as-important "Show entries ‘as important’ as the current one"))
("Always show entries of certain levels"
(logview-disable-unconditional-show "Disable ‘always show’")
(logview-always-show-errors "Always (i.e. regardless of text filters) show errors")
(logview-always-show-errors-and-warnings "Always show errors and warnings")
(logview-always-show-errors-warnings-and-information "Always show errors, warnings and information")
(logview-always-show-errors-warnings-information-and-debug "Always show all levels except trace"))
("Text-based filtering"
(logview-edit-filters "Edit filters as text in a separate buffer")
(logview-add-include-name-filter logview-add-exclude-name-filter "Add name include / exclude filter")
(logview-add-include-thread-filter logview-add-exclude-thread-filter "Add thread include / exclude filter")
(logview-add-include-message-filter logview-add-exclude-message-filter "Add message include / exclude filter"))
("Resetting filters"
(logview-reset-level-filters "Reset level filters")
(logview-reset-name-filters "Reset name filters")
(logview-reset-thread-filters "Reset thread filters")
(logview-reset-message-filters "Reset message filters")
(logview-reset-all-filters "Reset all filters")
(logview-reset-all-filters-restrictions-and-hidings "Reset filters, widen and show explicitly hidden entries"))
("Views"
(logview-switch-to-view "Switch to a view")
(logview-set-section-view "Choose a view for determining sections")
(logview-set-navigation-view "Choose a view for navigation")
(logview-highlight-view-entries "Select a view to highlight its entries")
(logview-unhighlight-view-entries "Remove view highlighting")
(logview-save-filters-as-view-for-submode "Save the filters as a view for the current submode")
(logview-save-filters-as-global-view "Save the filters as a globally available view")
(logview-edit-submode-views "Edit views for the current submode")
(logview-edit-all-views "Edit all views")
(logview-assign-quick-access-index "Assign a quick access index to the current view")
(logview-delete-view "Delete a view")
"You can also switch to a view by its quick access index: \\[logview-switch-to-view-by-index <0>]..\\[logview-switch-to-view-by-index <9>].
For larger indices use prefix argument, e.g.: \\[digit-argument <1>] \\[digit-argument <4>] \\[logview-switch-to-view]. This also
works for \\[logview-set-navigation-view] and \\[logview-highlight-view-entries] commands.")
("Sections"
(logview-go-to-section-beginning "Beginning of the current section")
(logview-go-to-section-end "End of the current section")
(logview-next-section logview-previous-section "Next / previous section")
(logview-next-section-any-thread logview-previous-section-any-thread "Next / previous section in any thread")
(logview-first-section logview-last-section "First / last section")
(logview-first-section-any-thread logview-last-section-any-thread "First / last section in any thread")
(logview-narrow-to-section "Narrow to the current section and filter out other threads")
(logview-narrow-to-section-keep-threads "Narrow to the current section, but don’t touch thread filters")
(logview-toggle-narrow-to-section-headers "Toggle “narrowing” to section headers, i.e. showing only headers")
(logview-toggle-sections-thread-bound "Toggle whether sections are thread-bound")
"See also \\[logview-set-section-view].")
("Explicitly hide or show entries"
(logview-hide-entry "Hide entry")
(logview-hide-region-entries "Hide entries in the region")
(logview-show-entries "Show some explicitly hidden entries")
(logview-show-region-entries "Show explicitly hidden entries in the region")
(logview-reset-manual-entry-hiding "Show all explicitly hidden entries in the buffer"))
("Hide or show details of individual entries"
(logview-toggle-entry-details "Toggle details of the current entry")
(logview-toggle-region-entry-details "Toggle details of entries in the region")
(logview-toggle-details-globally "Toggle details in the whole buffer")
(logview-reset-manual-details-hiding "Show all hidden entry details in the buffer")
"Here ‘details’ are the message lines after the first.")
("Display timestamp differences"
(logview-difference-to-current-entry "Show difference to the current entry for all other entries")
(logview-thread-difference-to-current-entry "Show difference only for the entries of the same thread")
(logview-difference-to-section-headers "Show timestamp differences within each section")
(logview-go-to-difference-base-entry "Go to the entry difference to which timestamp is shown")
(logview-forget-difference-base-entries "Don’t show timestamp differences")
(logview-forget-thread-difference-base-entry "Don’t show timestamp differences for this thread")
(logview-cancel-difference-to-section-headers "Don’t show timestamp differences to section headers"))
("Change options for current buffer"
(logview-change-target-gap-length "Set gap length for ‘\\[logview-next-timestamp-gap]’ and similar commands")
(auto-revert-mode "Toggle Auto-Revert mode")
(auto-revert-tail-mode "Toggle Auto-Revert Tail mode")
(logview-toggle-copy-visible-text-only "Toggle ‘copy only visible text’")
(logview-toggle-search-only-in-messages "Toggle ‘search only in messages’")
(logview-toggle-filter-preview "Toggle ‘preview filter changes’")
(logview-toggle-show-ellipses "Toggle ‘show ellipses’")
"Options can be customized globally or changed in each buffer.")
("Miscellaneous"
(logview-pulse-current-entry "Briefly highlight the current entry")
(logview-choose-submode "Manually choose appropriate submode")
(logview-customize-submode-options "Customize options that affect submode selection")
(bury-buffer "Bury buffer")
(logview-refresh-buffer-as-needed "Append tail or revert the buffer, as needed")
(logview-prepare-for-new-contents "Prepare the buffer to inspect only newly added contents")
(logview-append-log-file-tail "Append log file tail to the buffer")
(logview-revert-buffer "Revert the buffer preserving active filters")
"Universal prefix commands are bound without modifiers: \\[universal-argument], \\[negative-argument], \\[digit-argument <0>]..\\[digit-argument <9>].")))
;;; Macros and inlined functions.
;; Lisp is sensitive to declaration order, so these are collected at
;; the beginning of the file.
(defmacro logview--std-altering (&rest body)
(declare (indent 0) (debug t))
`(save-excursion
(let ((logview--process-buffer-changes nil)
(inhibit-read-only t))
(with-silent-modifications
,@body))))
(defmacro logview--temporarily-widening (&rest body)
"Execute BODY with the current buffer fully widened.
Original point restrictions, if any, will not be possible to find
inside BODY. In most cases (also if not sure) you should use
macro `logview--std-temporarily-widening' instead."
(declare (indent 0) (debug t))
`(logview--do-temporarily-widening (lambda () ,@body)))
;; Used so that `(fboundp 'without-restriction)' is not evaluated at compilation time.
(defun logview--do-temporarily-widening (body)
(save-restriction
(if (fboundp 'without-restriction)
;; {LOCKED-NARROWING}
;; "Hurr-durr, mah security, you cannot unlock without knowing the tag." Try all
;; tags I could find in Emacs source code. Normally this should be enough, but
;; there is obviously no guarantee as macro `with-restriction' is part of public
;; Elisp interface now.
(without-restriction
:label 'long-line-optimizations-in-fontification-functions
(without-restriction
:label 'long-line-optimizations-in-command-hooks
(logview--do-widen)
(funcall body)))
(logview--do-widen)
(funcall body))))
(defun logview--do-widen ()
(widen)
;; If still not widened, then it is better to fail hard now than to face an arbitrary
;; and hard to predict failure later. In particular, an infinite loop in fontification
;; code can irreversibly freeze Emacs, but this is of course "not a bug":
;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=57804. They care about responsiveness
;; with long lines, but not here, right.
(unless (and (= (point-min) 1) (= (point-max) (1+ (buffer-size))))
(error "Logview is incompatible with locked narrowing; see https://github.com/doublep/logview#locked-narrowing")))
(defmacro logview--std-temporarily-widening (&rest body)
"Execute BODY with the current buffer fully widened.
Original point restrictions are available as return values of
functions `logview--point-min' and `logview--point-max'. This
macro can be nested, with inner calls not changing results of the
two functions (available since the first call) further."
(declare (indent 0) (debug t))
`(let ((logview--point-min (logview--point-min))
(logview--point-max (logview--point-max)))
(logview--temporarily-widening ,@body)))
(defmacro logview--locate-current-entry (entry start &rest body)
(declare (indent 2) (debug (symbolp symbolp body)))
(cond ((and entry start)
(let ((entry+start (make-symbol "$entry+start")))
`(let* ((,entry+start (logview--do-locate-current-entry))
(,entry (car ,entry+start))
(,start (cdr ,entry+start)))
,@body)))
(entry
`(let ((,entry (car (logview--do-locate-current-entry))))
,@body))
(start
`(let ((,start (cdr (logview--do-locate-current-entry))))
,@body))
(t
`(progn (logview--do-locate-current-entry)
,@body))))
(defsubst logview--point-min ()
(or logview--point-min (point-min)))
(defsubst logview--point-max ()
(or logview--point-max (point-max)))
;; Value of text property `logview-entry' is a vector with the following elements: