Add opt-in API call logging for LANforge JSON requests - #320
Add opt-in API call logging for LANforge JSON requests#320Sidartha-CT wants to merge 18 commits into
Conversation
|
Here is the log csv that generated out of this when we ran lf_webpage.py : |
| _logging_paused = False | ||
| if not _logging_enabled: | ||
| return | ||
| _log_filename = log_filename or os.path.join(os.path.expanduser('~'), 'lf_api_calls.csv') |
There was a problem hiding this comment.
Might we consider:
from pathlib import Path
home_dir = Path.home()
| Returns: | ||
| None. | ||
| """ | ||
| global _logging_enabled, _log_filename, _logging_paused |
There was a problem hiding this comment.
I would appreciate an alternative to using globals.
| CSV_FIELDS = ['api_timestamp', 'api_method', 'api_url', 'api_status', 'api_response_code', | ||
| 'api_payload', 'api_error', 'api_diagnostics'] | ||
|
|
||
| _logging_enabled = False |
There was a problem hiding this comment.
I would appreciate an alternative to using globals. Can we turn these into class members?
| _log_filename = None | ||
|
|
||
|
|
||
| def configure_api_call_logging(enabled: bool, log_filename: Optional[str] = None) -> None: |
There was a problem hiding this comment.
Is there any reason we cannot design these methods into an ApiLogger class?
There was a problem hiding this comment.
It looks like all of these changes will be lost on the next API generation. These changes need to live in btbits/client/candela/lanforge/json_api.py
There was a problem hiding this comment.
Thanks for pointing that out. Is this something that needs to be updated on the btbits side first? I don't have access to that repository, so I'm not very familiar with how the API generation flow works. If these changes need to be made there before they're generated into this repo, please let me know.
| optional.add_argument("--test_priority", default="", help="dut model for kpi.csv, test-priority is arbitrary number") | ||
| optional.add_argument("--test_id", default="lf_webpage", help="test-id for kpi.csv, script or test name") | ||
| optional.add_argument('--csv_outfile', help="--csv_outfile <Output file for csv data>", default="") | ||
| optional.add_argument('--save_api', help="save json_get/json_post/json_put/json_delete calls to a lightweight csv log file", |
There was a problem hiding this comment.
"save_api" is a misleading term, please consider "enable_api_logging"
| status = "UNKNOWN" | ||
| try: | ||
| with open(_log_filename, 'a', newline='') as csv_file: | ||
| csv.writer(csv_file).writerow([ |
There was a problem hiding this comment.
This is not the time the message is sent, it refers to the time that the csv is recording this data
… request Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
…call logging Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
…default Verified CLI : python3 lf_webpage.py --ap_name "Cisco" --mgr 192.168.207.75 --ssid Cisco-5g --security wpa2 --passwd sharedsecret --upstream_port eth1 --duration 20s --bands 5G --client_type Real --file_size 2MB --save_api Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
…stamps record_api_call() previously stamped datetime.now() at CSV-write time, which is after the request/response cycle completes. Accept an optional sent_at param and use it instead when the caller provides it. Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
json_post() and get() now record sent_at right before the request is issued and pass it through to api_logger.record_api_call() on every success/error path, instead of letting it default to log-write time. Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Adds a last_sent_at attribute, set immediately before urlopen() in json_post() and get(), so callers can log the actual send time rather than the time the call happened to finish. Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
_log_api_call() now accepts sent_at and forwards it to api_logger.record_api_call(); json_post/json_put/json_get/json_delete pass lf_r.last_sent_at so the CSV timestamp reflects send time. Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
record_api_call() now accepts an optional elapsed_ms param, measured by the caller with time.perf_counter() around just the request itself (excludes diagnostics/logging overhead), and writes it to a new api_elapsed_ms CSV column so slow calls can be spotted at a glance. Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
json_post() and get_as_json() now wrap the urlopen()/read() call with time.perf_counter() and pass the resulting elapsed_ms through to api_logger.record_api_call() on every success/error path, so the CSV log reflects the actual network call time rather than a timestamp delta that includes diagnostics overhead. Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Match the terser placeholder style rather than the verbose "No payload" string in the api_payload column. Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
…ration Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
…logger Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
c8096b3 to
11a1c8d
Compare
Why global (module-level) state instead of a constructor parameter?The logging enable flag is intentionally kept as process-wide module state in
By calling For now, this approach achieves the required functionality with minimal changes. Once this PR is approved, I'll revisit the implementation and, if feasible, migrate it to a class-based design while preserving the same behavior. |
Signed-off-by: Sidartha-CT <neelapu.sidartha@candelatech.com>
Add opt-in API call logging for LANforge JSON requests
Summary
Adds a lightweight, opt-in CSV logger for every
json_get/json_post/json_put/json_deletecall made through either of LANforge's two client paths:py-json/LANforge/lfcli_base.py→Realmlanforge_client/lanforge_api.py→LFJsonCommand/LFJsonQueryWhen enabled via:
each call's method, URL, payload, HTTP status, and error/diagnostics get appended to a CSV (default
~/lf_api_calls.csv), which is copied into the run's report folder.Why a new
lanforge_client/api_logger.pymoduleWe want to record specific fields per call (method, url, payload, status, error) and post-process them later as CSV, so the recording logic needed to live inside
json_get()/json_post()/json_put()/json_delete()themselves rather than being bolted on from outside.There are two independent call paths that implement those four methods:
LFCliBase(py-json, used byRealmand most scripts)BaseLFJsonRequest(lanforge_client/lanforge_api.py, not currently used by interop)Rather than duplicating logging logic in both, the common pieces (CSV schema, enable/disable state, the actual
record()call) live in one new module inlanforge_client/, and each call path just calls into it.lanforge_clientis the natural home since it's already a shared dependency of both.Why global (module-level) state instead of a constructor parameter
The enable flag is deliberately process-wide global state in
api_logger.py, not a parameter threaded throughRealm/LFCliBase/ profile constructors.Reason:
Realmgets instantiated directly all over thepy-scriptstree, and profile classes build their own nested instances.For example:
lf_webpage.pyholds aRealml4_cxprofile, which itself instantiates its ownRealmTo pass an "enable logging" flag through the constructor, it would need to be threaded through every one of those call sites, and there's no guarantee the import/instantiation chain doesn't go a level or two deeper in other scripts.
A module-level
configure()called once near the top of a script'smain()avoids that — everyLFCliBase/RealmorLFJsonCommand/LFJsonQuerycreated afterward in the same process just picks it up.What's covered / not covered
Covered
LFRequest.pynow captureslast_response_codeand a one-linelast_diagnosticssummary (reason + X-Error-* headers) on every request, win or fail, so callers have something worth logging.lfcli_base.py(LFCliBase, used byRealm) andlanforge_api.py(BaseLFJsonRequest) both callapi_logger.record(...)after every GET / POST / PUT / DELETE, using those diagnostics.Not covered
json_get()/ etc. at all — those aren't covered yet and will be migrated separately.Pause / resume
monitor_for_runtime_csv()inlf_webpage.pypolls in a tight loop and would otherwise flood the CSV with repetitive, rarely-useful entries.Added:
api_logger.pause()api_logger.resume()so state (enabled + filename) is preserved but recording is suppressed during that loop by default.
--log_monitor_api_callsopts back in if someone actually wants those calls logged too.Usage
lf_webpage.py ... \ --save_api \ [--api_log_file_name /path/to/log.csv] \ [--log_monitor_api_calls] (this is somthing change wrt script )Commits
LFRequest.py— capture response code + diagnostics summary per requestapi_logger.py— new process-wide CSV logger (configure/record/is_enabled)lfcli_base.py— wire_log_api_callintojson_get/json_post/json_put/json_deletelanforge_api.py— same, for thelanforge_clientrequest pathlf_webpage.py— add--save_api/--api_log_file_nameflags, configure logger, copy CSV into report dirapi_logger.py— addpause()/resume()lf_webpage.py— pause logging during the runtime-CSV monitor loop by default, add--log_monitor_api_callsoverrideFuture Scope
The current changes improve the existing API logging by capturing additional request information and storing it in a structured format for easier debugging. Potential future enhancements include:
Extend API call capture to scripts and code paths that do not currently use the common
json_get(),json_post(),json_put(), andjson_delete()interfaces.Record REST request latency alongside the existing response code and diagnostics to help identify slow responses.
Capture additional system state when communication issues are detected, such as:
/ports/list/resources/response statusAdd configurable troubleshooting indicators that detect missed report intervals and automatically record additional diagnostic information.
Include high-level runtime summaries, such as:
Improve the generated API logs with optional summaries or filtering to simplify correlating API activity with existing LANforge logs and test failures.