Project: PALOS — PAN-OS Logs Scraper
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Install dependencies
pip install httpx[http2] beautifulsoup4 pandas lxml pyyaml
# Run scraper
python3 paloalto_scraper.py
# Dry run (preview without scraping)
# Set dry_run: true in paloalto_scraper_config.yaml, then run.Single-file async scraper (paloalto_scraper.py) with a YAML config. Scrapes PAN-OS syslog
field documentation from Palo Alto Networks docs and outputs CSV datasets.
- Config loads PAN-OS versions and per-log-type URLs from
paloalto_scraper_config.yaml PaloAltoLogScraper.run()creates onehttpx.AsyncClientand iterates versions sequentiallyscrape_version()fans out log types concurrently viaasyncio.gather+asyncio.Semaphore- For each log type page:
- Format string: comma-separated ordered field list (e.g.
FUTURE_USE, Receive Time, ...) - Field table: HTML table with
Field NameandDescriptioncolumns
- Format string: comma-separated ordered field list (e.g.
- Outputs per log type into
{version_name}/:{LogType}_format.csv: line 1 = original format string, line 2 = transformed variable names{LogType}_fields.csv: field table with addedField Name lookupandVariable Namecolumns
- After all per-type files:
consolidated/panos_syslog_fields.csv(position × log type matrix) andconsolidated/panos_consolidated_fields.csv(all unique variables with coverage + description)
get_page_content(client, url): async HTTP fetch with exponential backoff + 429/Retry-After handlingextract_format_string(soup, log_type_name)→(raw_string, list[str]): regex-extractsFormat:section, splits on commas, calls_apply_per_log_corrections, returns preserved raw string and corrected tokensextract_field_table(soup): finds HTML table with "field name" header; addsField Name lookup(text before() andVariable Namecolumns_apply_field_name_lookup_corrections(field_table, log_type_name): normalizesField Name lookupto match format tokens; global then per_log_type_lookup_variable_names(tokens, field_table): (1) DG Hierarchy regex →dg_hier_level_N; (2) exact lookup inField Name lookup— found + non-empty → return Variable Name; found + empty → write token back and pass through; (3) not found → pass through_apply_variable_name_corrections(tokens, field_table, log_type_name): global corrections (replace-all), then per-log-type (first-occurrence only on token list); both applied to field table Variable Name column_apply_per_log_corrections(tokens, log_type_name): called only fromextract_format_string;match:preferred overposition:; supportsnew:andsplit_into:_get_cell_text_with_formatting(): BS4 tree walk preserving block-element line breaks, collapsing source whitespace
| Key | Default | Effect |
|---|---|---|
base_delay |
1.0 |
Politeness sleep per slot after each page fetch |
retry_backoff |
2.0 |
Exponential backoff multiplier: base_delay × (backoff ^ attempt) + jitter |
max_retries |
3 |
Max retry attempts per URL |
concurrency |
5 |
asyncio.Semaphore size — max parallel log-type fetches per version |
inter_version_delay |
2.0 |
Sleep between versions |
force_rescrape |
true |
Re-fetch even if output already exists |
dry_run |
false |
Print plan without fetching |
output_dir |
"." |
Root output directory |
{version_name}/ # e.g. 11.1+/
{LogType}_format.csv # e.g. Traffic_format.csv (never Traffic_Log_format.csv)
{LogType}_fields.csv # columns: Field Name, Field Name lookup, Variable Name, Description
consolidated/
panos_syslog_fields.csv # position × log type matrix
panos_consolidated_fields.csv # all unique variables: field name, log type coverage, description
ecs/
panos_ecs_mapping.csv # manually curated ECS mapping; see FIELD_NAMING_NORMALIZATION.md
All new code must follow these conventions.
- Python 3.10+ — use
X | None, built-in generics,matchwhere appropriate. from __future__ import annotationsat the top of every module.- No
typingmodule — use built-in generics only:list[str],dict[str, str],tuple[str, ...],X | None. NeverList,Dict,Optional,Tuple.
- All function signatures must be fully annotated (parameters and return type).
- Return
Noneexplicitly when a function returns nothing meaningful. - Use
X | Nonefor optional values — neverOptional[X]. - Use
list[str],dict[str, int],tuple[str, str]— neverList,Dict,Tuple. - Annotate local variables when the type is not obvious from the right-hand side:
seen: set[str] = set(). - Type hints are not enforced at runtime — they exist for static checkers (mypy/pyright) and readability. Do not add
isinstanceguards based solely on a hint.
pathlib.Pathonly — neveros.path,os.makedirs,os.getcwd, oropen()with string paths. UsePath.read_text(),Path.write_text(),Path.open(),Path.mkdir(parents=True, exist_ok=True),Path.iterdir().
httpx.AsyncClientfor all HTTP — neverrequests.- One shared client per run, created in
run()as a context manager, passed to all callers. - All network functions are
async def. - Retry via
get_page_content(): exponential backoffbase_delay * (retry_backoff ** attempt) + jitter, 429/Retry-After handling. - Concurrency via
asyncio.Semaphore(self.concurrency)inscrape_version()— neverThreadPoolExecutor. Politeness sleep (base_delay) is inside the semaphore block.
logging.getLogger(__name__)in every module — neverprint().logging.basicConfigonly inmain(), never at module level.- Use
%-style or f-string formatting in log calls consistently.
- No
iterrows()— use boolean indexing,zipover Series,map, orto_dict('records'). - No defensive
.copy()unless an in-place mutation immediately follows.
@dataclassfor structured results — use when a dict has a fixed, known schema (e.g.FieldInfo). Prefer attribute access over string-keyed dicts; typos become parse-time errors instead of silentNone.- Docstrings: one short line only. No
Args:/Returns:blocks. - No comments that describe what — only why (hidden constraints, workarounds).
- Module-level constant for priority index:
_DESCRIPTION_PRIORITY_INDEX— never calllist.index()in a hot loop.
force_rescrapeis currentlytruein config — every run re-fetches all pages. Set tofalseto skip.field_name_lookup_corrections.global: only add when the key is NEVER the correct token for any log type. Useper_log_typefor log-specific overrides. Identity mapping"X": "X"suppresses a global rename.per_log_correctionswithmatch:replaces the FIRST occurrence only (list.index()).- asyncio is single-threaded:
_accumulate_consolidated_fieldsis safe without a lock since it contains noawait— it runs to completion atomically between coroutine switches.