Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions tail_jsonl/_private/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,16 @@ def from_line(cls, data: dict, config: Config) -> Record: # type: ignore[type-a
def print_record(line: str, console: Console, config: Config) -> None:
"""Format and print the record."""
try:
record = Record.from_line(json.loads(line), config=config)
except Exception:
data = json.loads(line)
record = Record.from_line(data, config=config)
if config.debug:
console.print(f'[dim]DEBUG: Parsed keys - timestamp={record.timestamp!r}, '
f'level={record.level!r}, message={record.message!r}[/dim]',
markup=True, highlight=False)
except Exception as exc:
if config.debug:
console.print(f'[dim red]DEBUG: Failed to parse line as JSON: {exc.__class__.__name__}: {exc}[/dim red]',
markup=True, highlight=False)
console.print(line.rstrip(), markup=False, highlight=False) # Print the unmodified line
return

Expand All @@ -77,6 +85,9 @@ def print_record(line: str, console: Console, config: Config) -> None:
if '.' not in dotted_key:
continue
if value := dotted.get(record.data, dotted_key):
if config.debug:
console.print(f'[dim]DEBUG: Promoting dotted key {dotted_key!r} to own line[/dim]',
markup=True, highlight=False)
record.data[dotted_key] = value if isinstance(value, str) else str(value)
dotted.remove(record.data, dotted_key)

Expand Down
2 changes: 2 additions & 0 deletions tail_jsonl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ class Config:

styles: Styles = field(default_factory=Styles)
keys: Keys = field(default_factory=Keys)
debug: bool = False

@classmethod
def from_dict(cls, data: dict) -> Config: # type: ignore[type-arg]
"""Return Self instance."""
return cls(
styles=styles_from_dict(data.get('styles', {})),
keys=Keys.from_dict(data.get('keys', {})),
debug=data.get('debug', False),
)
16 changes: 11 additions & 5 deletions tail_jsonl/scripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,35 @@
from .config import Config


def _load_config(config_path: str | None) -> Config:
def _load_config(config_path: str | None, *, debug: bool = False) -> Config:
"""Return loaded specified configuration file."""
user_config: dict = {} # type: ignore[type-arg]
if config_path:
pth = Path(config_path).expanduser()
user_config = tomllib.loads(pth.read_text(encoding='utf-8'))
return Config.from_dict(user_config)
config = Config.from_dict(user_config)
# CLI debug flag overrides config file
if debug:
config.debug = True
return config


def start() -> None: # pragma: no cover
"""CLI Entrypoint."""
# PLANNED: Add a flag (--debug & store_true) to print debugging information

parser = argparse.ArgumentParser(description='Pipe JSONL Logs for pretty printing')
parser.add_argument(
'-v', '--version', action='version',
version=f'%(prog)s {__version__}', help="Show program's version number and exit.",
)
parser.add_argument('--config-path', help='Path to a configuration file')
parser.add_argument(
'--debug', action='store_true',
help='Enable debug mode to show parsing details and error information',
)
options = parser.parse_args(sys.argv[1:])
sys.argv = sys.argv[:1] # Remove CLI before calling fileinput

config = _load_config(options.config_path)
config = _load_config(options.config_path, debug=options.debug)
console = Console()
with fileinput.input() as _f:
for line in _f:
Expand Down
5 changes: 4 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,7 @@ def test_create_default_config():

config = _load_config(config_path=str(example_config))

assert tomllib.loads(example_config.read_text(encoding='utf-8')) == asdict(config)
# Exclude 'debug' field as it's a runtime flag, not part of config file
config_dict = asdict(config)
config_dict.pop('debug', None)
assert tomllib.loads(example_config.read_text(encoding='utf-8')) == config_dict