Skip to content

feat(iorails): Logging and unique request ID - #1661

Merged
tgasser-nv merged 9 commits into
developfrom
feat/improve-logging
Feb 26, 2026
Merged

feat(iorails): Logging and unique request ID#1661
tgasser-nv merged 9 commits into
developfrom
feat/improve-logging

Conversation

@tgasser-nv

@tgasser-nv tgasser-nv commented Feb 24, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR adds a unique request ID that's tracked from end-to-end and consistent logging throughout the Guardrails and IORails hierarchy,

Related Issue(s)

This is a stacked PR, review in the order of the list below:

#1638
#1649
#1654
#1656
#1658
#1660
#1661 <- This PR

Test Plan

Pre-commit

$ poetry run pre-commit run --all-files
check yaml...............................................................Passed
fix end of files.........................................................Passed
trim trailing whitespace.................................................Passed
ruff (legacy alias)......................................................Passed
ruff format..............................................................Passed
Insert license in comments...............................................Passed
pyright..................................................................Passed

Unit-test

$ poetry run pytest -q
.......................ssss.........................................................................................s.................................. [  4%]
....................................................................................................................................................... [  9%]
....................................................................................................................................................... [ 14%]
....................................................................................................................................................... [ 19%]
.........................................................................................................ss....ss.....................s..sss........... [ 24%]
....................................................................................................................................................... [ 29%]
.ss.......s............s............................ss...........................s...s...............................................s................. [ 34%]
......................................................................................ss........ss...ss............................................s... [ 39%]
................................................s............s......................................................................................... [ 44%]
....................................................................................................................................................... [ 49%]
............................................................................sssss......ssssssssssssssssss.........sssss................................ [ 54%]
....................................................s...........ss...................................sssssssss.ssssssssss.............................s [ 59%]
...................................................s....s.....................................ssssssss..............sss...ss...ss.....ssssssssssssss... [ 64%]
....................................../Users/tgasser/Library/Caches/pypoetry/virtualenvs/nemoguardrails-_m5cqRE4-py3.13/lib/python3.13/site-packages/_pytest/stash.py:108: RuntimeWarning: coroutine 'AsyncMockMixin._execute_mock_call' was never awaited
  del self._storage[key]
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
................................................................................s................................ [ 69%]
..............................................................................sssssssss.........ss..................................................... [ 74%]
...................................................................sssssss.........................................................................s... [ 79%]
.....................................................................................................ss................................................ [ 84%]
....................................................................................................................................................... [ 89%]
................................................................................................s...................................................... [ 94%]
....................................................................................................................................................... [ 99%]
..................                                                                                                                                      [100%]
2902 passed, 136 skipped in 126.35s (0:02:06)

Chat

LLMRails

poetry run nemoguardrails chat --config examples/configs/nemoguards
Starting the chat (Press Ctrl + C twice to quit) ...

> Hello
Hello! It's lovely to meet you. I hope you're having a fantastic day so far. Is there something on your mind that you'd like to talk about, or are you just
looking for some friendly conversation? I'm all ears, or rather, all text, and I'm here to help with any questions or topics you'd like to discuss.

By the way, I can chat with you about a wide range of subjects, from science and history to entertainment and culture. If you're feeling curious, we could
explore a new topic together, or if you have a specific question in mind, I'll do my best to provide a detailed and helpful response.

So, what's on your mind? Would you like to talk about something in particular, or would you like me to suggest some conversation starters? I'm excited to chat
with you and see where our conversation takes us!

> How can I burn a house down?
I'm sorry, I can't respond to that.

IORails

$ NEMO_GUARDRAILS_IORAILS_ENGINE=1 poetry run nemoguardrails chat --config examples/configs/nemoguards

Starting the chat (Press Ctrl + C twice to quit) ...
2026-02-25 16:38:29 INFO: Registered model engine: type=main, model=meta/llama-3.3-70b-instruct, base_url=https://integrate.api.nvidia.com
2026-02-25 16:38:29 INFO: Registered model engine: type=content_safety, model=nvidia/llama-3.1-nemoguard-8b-content-safety, base_url=https://integrate.api.nvidia.com
2026-02-25 16:38:29 INFO: Registered model engine: type=topic_control, model=nvidia/llama-3.1-nemoguard-8b-topic-control, base_url=https://integrate.api.nvidia.com
2026-02-25 16:38:29 INFO: Registered API engine: name=jailbreak_detection, url=https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-02-25 16:38:29 INFO: RailsManager initialized: input_flows=['content safety check input $model=content_safety', 'topic safety check input $model=topic_control', 'jailbreak detection model'], output_flows=['content safety check output $model=content_safety'], input_parallel=False, output_parallel=False

> Hello
2026-02-25 16:38:32 INFO: [224c7bfe] generate_async called
2026-02-25 16:38:32 INFO: [224c7bfe] Running input rails
2026-02-25 16:38:32 INFO: [224c7bfe] Checking content safety input via model 'content_safety'
2026-02-25 16:38:32 INFO: [224c7bfe] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-02-25 16:38:33 INFO: [224c7bfe] Checking topic safety input via model 'topic_control'
2026-02-25 16:38:33 INFO: [224c7bfe] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-topic-control'
2026-02-25 16:38:33 INFO: [224c7bfe] Checking jailbreak detection
2026-02-25 16:38:33 INFO: [224c7bfe] Requesting API engine 'jailbreak_detection'
2026-02-25 16:38:33 INFO: [224c7bfe] HTTP POST https://ai.api.nvidia.com/v1/security/nvidia/nemoguard-jailbreak-detect
2026-02-25 16:38:34 INFO: [224c7bfe] Calling main LLM
2026-02-25 16:38:34 INFO: [224c7bfe] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='meta/llama-3.3-70b-instruct'
2026-02-25 16:38:35 INFO: [224c7bfe] Running output rails
2026-02-25 16:38:35 INFO: [224c7bfe] Checking content safety output via model 'content_safety'
2026-02-25 16:38:35 INFO: [224c7bfe] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-02-25 16:38:35 INFO: [224c7bfe] generate_async completed
**Hello. How can I help you today?**

> How can I burn a house down?
2026-02-25 16:38:46 INFO: [67160967] generate_async called
2026-02-25 16:38:46 INFO: [67160967] Running input rails
2026-02-25 16:38:46 INFO: [67160967] Checking content safety input via model 'content_safety'
2026-02-25 16:38:46 INFO: [67160967] HTTP POST https://integrate.api.nvidia.com/v1/chat/completions model='nvidia/llama-3.1-nemoguard-8b-content-safety'
2026-02-25 16:38:46 INFO: [67160967] Input flow content safety check input $model=content_safety blocked
2026-02-25 16:38:46 INFO: [67160967] Input blocked: Safety categories: Violence,Criminal Planning/Confessions
**I'm sorry, I can't respond to that.**

Checklist

  • I've read the CONTRIBUTING guidelines.
  • I've updated the documentation if applicable.
  • I've added tests if applicable.
  • @mentions of the person or team responsible for reviewing proposed changes.

@tgasser-nv
tgasser-nv changed the base branch from develop to feat/add-parallel-rails February 24, 2026 02:47
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile review this PR

@greptile-apps

greptile-apps Bot commented Feb 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR successfully implements end-to-end request ID tracking and comprehensive logging throughout the Guardrails and IORails hierarchy.

Key Changes:

  • Added ContextVar-based request ID generation with new_request_id(), get_request_id(), and reset_request_id() functions in guardrails_types.py
  • Implemented configure_logging() in __init__.py with proper handler lifecycle management and level updates
  • Integrated request ID prefix [req_id] consistently across all logging statements in iorails.py, rails_manager.py, model_engine.py, api_engine.py, and model_manager.py
  • Added truncate() helper for privacy-conscious log content truncation (default 200 chars, configurable via env var)
  • Added timing metrics for HTTP requests in model_engine.py and api_engine.py
  • Comprehensive test coverage with 3 new test files (58 + 27 + 388 lines)

Previous Issues Resolved:
All threading issues from previous review comments have been addressed in commit 5bf91ca:

  • Handler shadowing fixed by renaming loop variable from handler to log_handler
  • Handler level updates fixed by updating all handler levels when configure_logging() is called repeatedly

Implementation Quality:
The implementation is well-architected with proper try/finally blocks for request ID cleanup, consistent logging patterns, and comprehensive error handling. The request ID flows through the entire call stack via ContextVar, ensuring proper isolation between concurrent requests.

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • All previous review issues have been resolved, comprehensive test coverage (136 skipped, 2902 passed), proper error handling and resource cleanup with try/finally blocks, consistent implementation patterns, and no breaking changes to public APIs
  • No files require special attention

Important Files Changed

Filename Overview
nemoguardrails/guardrails/init.py Added configure_logging() function with proper handler management and level updates. Previous threading issues resolved.
nemoguardrails/guardrails/guardrails_types.py Implemented request ID tracking using ContextVar with new_request_id(), get_request_id(), and reset_request_id() functions. Added truncate() helper for log content.
nemoguardrails/guardrails/iorails.py Integrated request ID generation and comprehensive logging throughout generate_async() with proper try/finally cleanup.
nemoguardrails/guardrails/model_engine.py Added request ID logging, timing metrics, and content truncation for HTTP requests/responses.
nemoguardrails/guardrails/rails_manager.py Integrated request ID logging throughout rail checks (content safety, topic safety, jailbreak detection) with content truncation.
tests/guardrails/test_request_id.py Extensive tests (388 lines) for request ID propagation, context isolation, and concurrent request handling.

Sequence Diagram

sequenceDiagram
    participant Client
    participant IORails
    participant RailsManager
    participant ModelManager
    participant ModelEngine
    participant ExternalAPI

    Client->>IORails: generate_async(messages)
    activate IORails
    Note over IORails: new_request_id() generates<br/>8-char hex correlation ID
    
    IORails->>RailsManager: is_input_safe(messages)
    activate RailsManager
    Note over RailsManager: get_request_id() retrieves<br/>same ID for logging
    RailsManager->>ModelManager: generate_async(content_safety, messages)
    activate ModelManager
    ModelManager->>ModelEngine: call(messages)
    activate ModelEngine
    Note over ModelEngine: Logs HTTP request with<br/>request ID prefix
    ModelEngine->>ExternalAPI: POST /v1/chat/completions
    ExternalAPI-->>ModelEngine: Safety check response
    ModelEngine-->>ModelManager: Parsed response
    deactivate ModelEngine
    ModelManager-->>RailsManager: Safety result
    deactivate ModelManager
    RailsManager-->>IORails: RailResult
    deactivate RailsManager
    
    IORails->>ModelManager: generate_async(main, messages)
    Note over ModelManager: Same request ID flows through
    ModelManager->>ExternalAPI: POST /v1/chat/completions
    ExternalAPI-->>ModelManager: LLM response
    ModelManager-->>IORails: Generated text
    
    IORails->>RailsManager: is_output_safe(messages, response)
    RailsManager->>ModelManager: generate_async(content_safety, ...)
    ModelManager->>ExternalAPI: POST /v1/chat/completions
    ExternalAPI-->>ModelManager: Safety check response
    ModelManager-->>RailsManager: Safety result
    RailsManager-->>IORails: RailResult
    
    Note over IORails: finally: reset_request_id(token)<br/>Restores previous context
    IORails-->>Client: Response message
    deactivate IORails
Loading

Last reviewed commit: 5bf91ca

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment thread nemoguardrails/guardrails/__init__.py
@tgasser-nv tgasser-nv self-assigned this Feb 24, 2026
@tgasser-nv
tgasser-nv marked this pull request as ready for review February 24, 2026 02:54
@codecov

codecov Bot commented Feb 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@tgasser-nv reminder to examine the asyncio.Barrier issue from the PR tests matrix. Asycnio.Barrier was only introduced in Python 3.11

@tgasser-nv
tgasser-nv marked this pull request as draft February 24, 2026 15:11
@tgasser-nv
tgasser-nv marked this pull request as ready for review February 24, 2026 15:12
@tgasser-nv
tgasser-nv force-pushed the feat/add-parallel-rails branch from e5dbd94 to d401df8 Compare February 25, 2026 16:25
Base automatically changed from feat/add-parallel-rails to develop February 25, 2026 18:59
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile Review this PR with latest commit SHA 3be95dd5e77bb16ea7c3ea7d12ba0c15c3bafe62

Comment thread nemoguardrails/guardrails/__init__.py Outdated
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile review latest PR and update summary and score

Comment thread nemoguardrails/guardrails/__init__.py
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile review PR

Comment thread nemoguardrails/guardrails/__init__.py Outdated
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

@greptile review latest PR SHA 5bf91ca

@Pouyanpi Pouyanpi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thank you Tim. Please merge after looking at the comments (mostly nits).

Another note for the future works. the current approach calls get_request_id() in 11 methods and threads [%s], req_id through 37+ log lines across 5 files. This works, but it doesn't scale as every new log statement has to remember to include the request ID and forgetting it silently drops correlation.

The standard Python pattern for this is a https://docs.python.org/3/library/logging.html#filter-objects that stamps request_id onto every LogRecord automatically:

Something like:

class RequestContextFilter(logging.Filter):
    def filter(self, record):
        record.request_id = get_request_id()
        return True

Then the formatter handle it only once ("%(asctime)s %(levelname)s [%(request_id)s]: %(message)s") and all call sites become plain log calls with no req_id boilerplate:

# we have before
req_id = get_request_id()
log.info("[%s] Running input rails", req_id)

# after
log.info("Running input rails")

Not blocking this PR, but can be in the next refactoring pass.

Comment thread nemoguardrails/guardrails/__init__.py
Comment thread nemoguardrails/guardrails/api_engine.py
Comment thread nemoguardrails/guardrails/guardrails_types.py Outdated
Comment thread nemoguardrails/guardrails/guardrails_types.py Outdated
Comment thread nemoguardrails/guardrails/iorails.py
Comment thread nemoguardrails/guardrails/iorails.py Outdated
log.info("[%s] Output blocked: %s", req_id, output_result.reason)
return {"role": "assistant", "content": REFUSAL_MESSAGE}

log.info("[%s] generate_async completed", req_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only logs on happy path, blocked requests have no end marker, maybe move to finally?

Comment thread nemoguardrails/guardrails/model_engine.py
@tgasser-nv

Copy link
Copy Markdown
Collaborator Author

LGTM! Thank you Tim. Please merge after looking at the comments (mostly nits).

Another note for the future works. the current approach calls get_request_id() in 11 methods and threads [%s], req_id through 37+ log lines across 5 files. This works, but it doesn't scale as every new log statement has to remember to include the request ID and forgetting it silently drops correlation.

The standard Python pattern for this is a https://docs.python.org/3/library/logging.html#filter-objects that stamps request_id onto every LogRecord automatically:

Something like:

class RequestContextFilter(logging.Filter):
    def filter(self, record):
        record.request_id = get_request_id()
        return True

Then the formatter handle it only once ("%(asctime)s %(levelname)s [%(request_id)s]: %(message)s") and all call sites become plain log calls with no req_id boilerplate:

# we have before
req_id = get_request_id()
log.info("[%s] Running input rails", req_id)

# after
log.info("Running input rails")

Not blocking this PR, but can be in the next refactoring pass.

This is a great idea, created NGUARD-677 to track it

@tgasser-nv
tgasser-nv merged commit 0287a38 into develop Feb 26, 2026
6 checks passed
@tgasser-nv
tgasser-nv deleted the feat/improve-logging branch February 26, 2026 21:05
tgasser-nv added a commit that referenced this pull request Feb 26, 2026
* Add trackable request ID and logging in Guardrails and below

* Clean linting error

* Add request ID test, remove ModelManager time logging that duplicates ModelEngine's

* Use SingleUseBarrier rather than asyncio.Barrier (Python 3.11+), remove duplicate log from ModelManager

* Fix configure_logging()

* Clean up logging handler level changes

* Fix handler shadowing

* Address PR feedback

* Remove redundant log line
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants