Enhancement: Add debug logging around get_issue response parsing #14

Open
opened 2026-03-06 16:11:59 +00:00 by Bartender · 1 comment
Owner

Summary

Add targeted debug logging around the get_issue flow to make issue parsing failures easier to diagnose.

Context

A valid get_issue request currently fails with:

'NoneType' object is not iterable

The related bug is tracked in #13.

Goal

Improve observability so that future failures clearly show:

  • the upstream Gitea response status
  • which optional fields are present, null, or missing
  • the exact parsing step that failed
  • enough structured context to reproduce without leaking sensitive data

Suggested Logging

Add debug logs for:

  • request parameters (owner, repo, issue_number)
  • upstream response status code
  • top-level keys returned by the Gitea API
  • nullable fields before iteration or transformation
  • exception type and stack trace when parsing fails

Safety Considerations

  • Avoid logging tokens, authorization headers, or secrets
  • Avoid logging full issue bodies by default if not needed
  • Prefer structured logs over ad-hoc strings

Acceptance Criteria

  • get_issue emits useful debug logs when parsing starts
  • null / missing optional fields are logged safely
  • parse failures include exception type and stack trace
  • logs avoid secrets and unnecessary issue content
  • logs are sufficient to identify which field caused the failure

Nice to Have

  • Add a debug log toggle or log level guard if not already present
  • Add a small regression test that asserts failure paths produce actionable diagnostics

Related: #13

## Summary Add targeted debug logging around the `get_issue` flow to make issue parsing failures easier to diagnose. ## Context A valid `get_issue` request currently fails with: ``` 'NoneType' object is not iterable ``` The related bug is tracked in #13. ## Goal Improve observability so that future failures clearly show: - the upstream Gitea response status - which optional fields are present, null, or missing - the exact parsing step that failed - enough structured context to reproduce without leaking sensitive data ## Suggested Logging Add debug logs for: - request parameters (`owner`, `repo`, `issue_number`) - upstream response status code - top-level keys returned by the Gitea API - nullable fields before iteration or transformation - exception type and stack trace when parsing fails ## Safety Considerations - Avoid logging tokens, authorization headers, or secrets - Avoid logging full issue bodies by default if not needed - Prefer structured logs over ad-hoc strings ## Acceptance Criteria - [ ] `get_issue` emits useful debug logs when parsing starts - [ ] null / missing optional fields are logged safely - [ ] parse failures include exception type and stack trace - [ ] logs avoid secrets and unnecessary issue content - [ ] logs are sufficient to identify which field caused the failure ## Nice to Have - Add a debug log toggle or log level guard if not already present - Add a small regression test that asserts failure paths produce actionable diagnostics Related: #13
Author
Owner

Implementation suggestion: introduce a small reusable logging helper pattern instead of adding ad-hoc debug logging inline.

Proposed Helper Pattern

Create a small module for consistent structured logging across MCP endpoints.

Suggested helpers:

def sanitize_context(context: dict) -> dict:
    sensitive_keys = {"token", "authorization", "cookie", "password", "secret"}
    return {
        k: ("***" if k.lower() in sensitive_keys else v)
        for k, v in context.items()
    }


def log_event(logger, level, event: str, **context):
    logger.log(level, event, extra={"context": sanitize_context(context)})


def log_exception(logger, event: str, exc: Exception, **context):
    log_event(
        logger,
        logging.ERROR,
        event,
        exception_type=type(exc).__name__,
        exception_message=str(exc),
        **context,
    )
    logger.exception(exc)


def log_nullable_field(logger, event: str, field_name: str, value):
    log_event(
        logger,
        logging.DEBUG,
        event,
        field=field_name,
        is_none=value is None,
        value_type=(type(value).__name__ if value is not None else None),
    )

Example Usage in get_issue

log_event(logger, logging.DEBUG, "get_issue.start", owner=owner, repo=repo, issue_number=issue_number)

response = client.get(...)
log_event(logger, logging.DEBUG, "get_issue.http_response", status_code=response.status_code, owner=owner, repo=repo, issue_number=issue_number)

issue_data = response.json()
log_event(logger, logging.DEBUG, "get_issue.payload_shape", top_level_keys=list(issue_data.keys()) if issue_data else None)

labels = issue_data.get("labels")
log_nullable_field(logger, "get_issue.field_check", "labels", labels)

Why this helps

  • Keeps logging consistent across all endpoints
  • Makes None / nullable field failures obvious
  • Prevents accidental logging of secrets
  • Makes future debugging and observability easier to scale

Suggested Scope

This can start as part of #14 for get_issue, then be reused later for:

  • get_pull_request
  • list_issues
  • get_commit_diff
  • other parsing-heavy endpoints
Implementation suggestion: introduce a small reusable logging helper pattern instead of adding ad-hoc debug logging inline. ## Proposed Helper Pattern Create a small module for consistent structured logging across MCP endpoints. Suggested helpers: ```python def sanitize_context(context: dict) -> dict: sensitive_keys = {"token", "authorization", "cookie", "password", "secret"} return { k: ("***" if k.lower() in sensitive_keys else v) for k, v in context.items() } def log_event(logger, level, event: str, **context): logger.log(level, event, extra={"context": sanitize_context(context)}) def log_exception(logger, event: str, exc: Exception, **context): log_event( logger, logging.ERROR, event, exception_type=type(exc).__name__, exception_message=str(exc), **context, ) logger.exception(exc) def log_nullable_field(logger, event: str, field_name: str, value): log_event( logger, logging.DEBUG, event, field=field_name, is_none=value is None, value_type=(type(value).__name__ if value is not None else None), ) ``` ## Example Usage in `get_issue` ```python log_event(logger, logging.DEBUG, "get_issue.start", owner=owner, repo=repo, issue_number=issue_number) response = client.get(...) log_event(logger, logging.DEBUG, "get_issue.http_response", status_code=response.status_code, owner=owner, repo=repo, issue_number=issue_number) issue_data = response.json() log_event(logger, logging.DEBUG, "get_issue.payload_shape", top_level_keys=list(issue_data.keys()) if issue_data else None) labels = issue_data.get("labels") log_nullable_field(logger, "get_issue.field_check", "labels", labels) ``` ## Why this helps - Keeps logging consistent across all endpoints - Makes `None` / nullable field failures obvious - Prevents accidental logging of secrets - Makes future debugging and observability easier to scale ## Suggested Scope This can start as part of #14 for `get_issue`, then be reused later for: - `get_pull_request` - `list_issues` - `get_commit_diff` - other parsing-heavy endpoints
Sign in to join this conversation.