Skip to content

Commit 906dafb

Browse files
authored
Merge pull request #44 from Textualize/long-lines
protect against very long lines
2 parents e564702 + 80977f9 commit 906dafb

File tree

8 files changed

+39
-10
lines changed

8 files changed

+39
-10
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "toolong"
3-
version = "1.3.0"
3+
version = "1.4.0"
44
description = "A terminal log file viewer / tailer / analyzer"
55
authors = ["Will McGugan <[email protected]>"]
66
license = "MIT"

src/toolong/format_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ def __init__(self) -> None:
111111

112112
def parse(self, line: str) -> ParseResult:
113113
"""Parse a line."""
114+
if len(line) > 10_000:
115+
line = line[:10_000]
114116
for index, format in enumerate(self._formats):
115117
parse_result = format.parse(line)
116118
if parse_result is not None:

src/toolong/highlighter.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from rich.highlighter import RegexHighlighter
2+
from rich.text import Text
23

34

45
def _combine_regex(*regexes: str) -> str:
@@ -28,3 +29,17 @@ class LogHighlighter(RegexHighlighter):
2829
r"(?P<path>\[.*?\])",
2930
),
3031
]
32+
33+
def highlight(self, text: Text) -> None:
34+
"""Highlight :class:`rich.text.Text` using regular expressions.
35+
36+
Args:
37+
text (~Text): Text to highlighted.
38+
39+
"""
40+
if len(text) >= 10_000:
41+
return
42+
43+
highlight_regex = text.highlight_regex
44+
for re_highlight in self.highlights:
45+
highlight_regex(re_highlight, style_prefix=self.base_style)

src/toolong/line_panel.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ def __init__(self, line: str, text: Text, timestamp: datetime | None) -> None:
3636

3737
def compose(self) -> ComposeResult:
3838
try:
39-
json.loads(self.line)
39+
json_data = json.loads(self.line)
4040
except Exception:
41-
yield Label(self.text)
41+
pass
4242
else:
43-
yield Static(JSON(self.line), expand=True, classes="json")
43+
yield Static(JSON.from_data(json_data), expand=True, classes="json")
44+
return
45+
yield Label(self.text)
4446

4547

4648
class LinePanel(ScrollableContainer):

src/toolong/log_file.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ def scan_line_breaks(
187187
monotonic = time.monotonic
188188
break_time = monotonic()
189189

190+
if log_mmap[-1] != "\n":
191+
batch.append(position)
192+
190193
while (position := rfind(b"\n", 0, position)) != -1:
191194
append(position)
192195
if get_length() % 1000 == 0 and monotonic() - break_time > batch_time:

src/toolong/log_lines.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646

4747
SPLIT_REGEX = r"[\s/\[\]\(\)\"\/]"
4848

49+
MAX_LINE_LENGTH = 1000
50+
4951

5052
@dataclass
5153
class LineRead(Message):
@@ -108,9 +110,6 @@ def run(self) -> None:
108110
)
109111

110112

111-
MAX_LINE_LENGTH = 1000
112-
113-
114113
class SearchSuggester(Suggester):
115114
def __init__(self, search_index: Mapping[str, str]) -> None:
116115
self.search_index = search_index
@@ -520,6 +519,7 @@ def get_text(
520519
line_index: int,
521520
abbreviate: bool = False,
522521
block: bool = False,
522+
max_line_length=MAX_LINE_LENGTH,
523523
) -> tuple[str, Text, datetime | None]:
524524
log_file, start, end = self.index_to_span(line_index)
525525
cache_key = (log_file, start, end, abbreviate)
@@ -535,8 +535,8 @@ def get_text(
535535
return "", Text(""), None
536536
line = new_line
537537
timestamp, line, text = log_file.parse(line)
538-
if abbreviate and len(text) > MAX_LINE_LENGTH:
539-
text = text[:MAX_LINE_LENGTH] + "…"
538+
if abbreviate and len(text) > max_line_length:
539+
text = text[:max_line_length] + "…"
540540
self._text_cache[cache_key] = (line, text, timestamp)
541541
return line, text.copy(), timestamp
542542

src/toolong/log_view.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535

3636
SPLIT_REGEX = r"[\s/\[\]]"
3737

38+
MAX_DETAIL_LINE_LENGTH = 100_000
39+
3840

3941
class InfoOverlay(Widget):
4042
"""Displays text under the lines widget when there are new lines."""
@@ -363,7 +365,10 @@ async def update_panel(self) -> None:
363365
pointer_line = self.query_one(LogLines).pointer_line
364366
if pointer_line is not None:
365367
line, text, timestamp = self.query_one(LogLines).get_text(
366-
pointer_line, block=True
368+
pointer_line,
369+
block=True,
370+
abbreviate=True,
371+
max_line_length=MAX_DETAIL_LINE_LENGTH,
367372
)
368373
await self.query_one(LinePanel).update(line, text, timestamp)
369374

src/toolong/timestamps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ def scan(self, line: str) -> datetime | None:
123123
Returns:
124124
A datetime or `None` if no timestamp was found.
125125
"""
126+
if len(line) > 10_000:
127+
line = line[:10000]
126128
for index, timestamp_format in enumerate(self._timestamp_formats):
127129
regex, parse_callable = timestamp_format
128130
if (match := re.search(regex, line)) is not None:

0 commit comments

Comments
 (0)