Skip to content

Commit

Permalink
Merge pull request #10 from kiraum/kiraum/issues_5
Browse files Browse the repository at this point in the history
 feat: add option to enable debugging logs (Improve logger instance)
  • Loading branch information
kiraum authored Dec 23, 2024
2 parents 95fbee1 + c12ad5f commit 4980288
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 7 deletions.
8 changes: 8 additions & 0 deletions irrexplorer_cli/helpers.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
"""Helper functions for the CLI."""

import ipaddress
import logging
import re
from typing import Any, Dict, List

from irrexplorer_cli.models import PrefixInfo, PrefixResult

logger = logging.getLogger(__name__)


def validate_prefix_format(prefix_input: str) -> bool:
"""Validate IPv4 or IPv6 prefix format."""
logger.debug("Validating prefix format: %s", prefix_input)
try:
ipaddress.ip_network(prefix_input)
logger.debug("Prefix validation successful")
return True
except ValueError:
logger.debug("Invalid prefix format")
return False


Expand Down Expand Up @@ -116,12 +122,14 @@ def format_as_sets(as_number: str, sets_data: Dict[str, Dict[str, List[str]]]) -

async def find_least_specific_prefix(direct_overlaps: List[PrefixInfo]) -> str | None:
"""Find the least specific prefix from the overlaps."""
logger.debug("Finding least specific prefix from %d overlaps", len(direct_overlaps))
least_specific = None
for info in direct_overlaps:
if "/" in info.prefix:
_, mask = info.prefix.split("/")
if least_specific is None or int(mask) < int(least_specific.split("/")[1]):
least_specific = info.prefix
logger.debug("New least specific prefix found: %s", least_specific)
return least_specific


Expand Down
14 changes: 9 additions & 5 deletions irrexplorer_cli/irrexplorer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Core functionality for IRR Explorer CLI."""

import logging
from typing import Any, Dict, List, Optional, cast

import backoff
Expand All @@ -14,6 +15,8 @@

from .models import PrefixInfo

logger = logging.getLogger(__name__)


class IrrExplorer:
"""IRR Explorer API client for prefix information retrieval."""
Expand All @@ -28,18 +31,17 @@ def __init__(self, base_url: str = "https://irrexplorer.nlnog.net") -> None:
@backoff.on_exception(backoff.expo, (httpx.HTTPError, httpx.RequestError), max_tries=3, max_time=300)
async def fetch_prefix_info(self, prefix: str) -> List[PrefixInfo]:
"""Fetch prefix information from IRR Explorer API."""
logger.debug("Fetching prefix info for: %s", prefix)
try:
url = f"{self.base_url}/api/prefixes/prefix/{prefix}"
logger.debug("Making API request to: %s", url)
response = await self.client.get(url)
response.raise_for_status()
data = response.json()
if not data:
return []
logger.debug("Received response data: %s", data)
return [PrefixInfo(**item) for item in data]
except httpx.TimeoutException:
self.console.print(
f"[yellow]Request timed out while fetching info for {prefix}. The server might be busy.[/yellow]"
)
logger.error("Request timeout for prefix: %s", prefix)
return []

@backoff.on_exception(backoff.expo, (httpx.HTTPError, httpx.RequestError), max_tries=3, max_time=300)
Expand Down Expand Up @@ -171,7 +173,9 @@ async def get_status_style(self, category: str) -> str:

async def display_prefix_info(self, direct_overlaps: List[PrefixInfo]) -> None:
"""Display prefix information in Rich panels."""
logger.debug("Displaying prefix info for %d overlaps", len(direct_overlaps))
if not direct_overlaps:
logger.debug("No prefix information found")
self.console.print("[yellow]No prefix information found[/yellow]")
return

Expand Down
15 changes: 15 additions & 0 deletions irrexplorer_cli/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Command-line interface for IRR Explorer queries."""

import asyncio
import logging
from importlib.metadata import version
from typing import Optional

Expand All @@ -19,6 +20,17 @@
context_settings={"help_option_names": ["-h", "--help"]},
)
console = Console()
logger = logging.getLogger(__name__)


def setup_logging(debug: bool) -> None:
"""Configure logging based on debug flag."""
log_level = logging.DEBUG if debug else logging.INFO
logging.basicConfig(level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")

# set httpx logger level based on debug flag
httpx_logger = logging.getLogger("httpx")
httpx_logger.setLevel(logging.DEBUG if debug else logging.WARNING)


def version_display(display_version: bool) -> None:
Expand All @@ -33,10 +45,13 @@ def callback(
ctx: typer.Context,
_: bool = typer.Option(None, "--version", "-v", callback=version_display, is_eager=True),
base_url: Optional[str] = typer.Option(None, "--url", "-u", help="Base URL for IRR Explorer API"),
debug: bool = typer.Option(False, "--debug", "-d", help="Enable debug logging"),
) -> None:
"""Query IRR Explorer for prefix information."""
ctx.ensure_object(dict)
ctx.obj["base_url"] = base_url
setup_logging(debug)
logger.debug("CLI initialized with base_url: %s", base_url)


@app.command(no_args_is_help=True)
Expand Down
21 changes: 19 additions & 2 deletions irrexplorer_cli/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Data models for IRR Explorer API responses."""

from typing import Dict, List, Optional
import logging
from typing import Any, Dict, List, Optional

from pydantic import BaseModel
from pydantic import BaseModel, model_validator

logger = logging.getLogger(__name__)


class BaseRoute(BaseModel):
Expand Down Expand Up @@ -43,6 +46,20 @@ class PrefixInfo(BaseModel):
prefixSortKey: str
goodnessOverall: int

def __init__(self, **data: Any) -> None:
logger.debug("Initializing PrefixInfo with data: %s", data)
super().__init__(**data)
logger.debug("PrefixInfo initialized successfully for prefix: %s", self.prefix)

@model_validator(mode="after")
def validate_prefix_info(self) -> "PrefixInfo":
"""Validate the prefix information after model creation."""
logger.debug("Validating PrefixInfo for prefix: %s", self.prefix)
logger.debug(
"Category: %s, RIR: %s, BGP Origins count: %d", self.categoryOverall, self.rir, len(self.bgpOrigins)
)
return self


class AsResponse(BaseModel):
"""Response model for AS queries."""
Expand Down
8 changes: 8 additions & 0 deletions irrexplorer_cli/queries.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Query functions for the CLI tool."""

import json
import logging
from typing import Optional

import httpx
Expand All @@ -15,6 +16,8 @@
)
from irrexplorer_cli.irrexplorer import IrrDisplay, IrrExplorer

logger = logging.getLogger(__name__)


async def process_overlaps(explorer: IrrExplorer, least_specific: str) -> None:
"""Process and print overlapping prefixes."""
Expand All @@ -28,12 +31,17 @@ async def process_overlaps(explorer: IrrExplorer, least_specific: str) -> None:

async def async_prefix_query(pfx: str, output_format: Optional[str] = None, base_url: Optional[str] = None) -> None:
"""Execute asynchronous prefix query and display results."""
logger.debug("Starting prefix query for: %s", pfx)
logger.debug("Output format: %s, Base URL: %s", output_format, base_url)
explorer = IrrExplorer(base_url=base_url) if base_url else IrrExplorer()
display = IrrDisplay()

try:
direct_overlaps = await explorer.fetch_prefix_info(pfx)
logger.debug("Received %d direct overlaps", len(direct_overlaps))

if output_format == "json":
logger.debug("Formatting output as JSON")
json_data = [result.model_dump() for result in direct_overlaps]
print(json.dumps(json_data, indent=2))
elif output_format == "csv":
Expand Down

0 comments on commit 4980288

Please sign in to comment.