-
Notifications
You must be signed in to change notification settings - Fork 13
feat: Introduce logging context to Tangle #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
45fca00 to
c08c8f2
Compare
|
Thank you for this PR. I'm reviewing the changes and I think we might want to design this slightly differently. On the other hand, API requests might benefit from trace IDs so that all logging messages that are generated when processing a single API call can be filtered and grouped together. |
This sounds good to me. I will make some adjustments. Thanks! |
c08c8f2 to
e824f64
Compare
6245345 to
2bfcc23
Compare
**Changes:**
* Adds logging context helpers
* Add request middleware to generate unique request id and set it in the logging context around API requests
* Sets the x-tangle-request-id on the response for client consumption
2bfcc23 to
4e7b1a3
Compare
The work has been re-designed. Here is a summary: Logging Context ImprovementsSummaryEnhanced the Tangle orchestrator logging to automatically include execution context (execution_id, container_execution_id) in all log messages, making it easier to trace and filter logs for specific executions. Changes Made1. Added Logging Context to Orchestrator (
|
| with logging_context.logging_context( | ||
| execution_id=queued_execution.id | ||
| ): | ||
| _logger.info("Before processing queued execution") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will the execution ID information still be present in non-JSON logs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be, yes. This is achieved through the following change:
class ContextAwareFormatter(logging.Formatter):
"""Formatter that dynamically includes context fields only when they're set."""
def format(self, record: logging.LogRecord) -> str:
"""Format log record with dynamic context fields."""
# Base format
base_format = "%(asctime)s [%(levelname)s] %(name)s"
# Collect context fields that are present
context_parts = []
context_metadata = get_all_context_metadata()
for key, value in context_metadata.items():
if value is not None and hasattr(record, key):
context_parts.append(f"{key}={value}")
# Add context to format if any exists
if context_parts:
base_format += " [" + " ".join(context_parts) + "]"
base_format += ": %(message)s"
# Create formatter with the dynamic format
formatter = logging.Formatter(base_format)
return formatter.format(record)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. I wonder how _logger.exception messages look like.
| import logging.config | ||
| from cloud_pipelines_backend.instrumentation.logging_context import get_all_context_metadata | ||
|
|
||
| class LoggingContextFilter(logging.Filter): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move these two classes to a dedicated, well-named file/module rather than being coupled with an entrypoint script.
| execution_id = execution_nodes[0].id if execution_nodes else None | ||
|
|
||
| with logging_context.logging_context( | ||
| execution_id=execution_id, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call it: execution_node_id
| # There must be at least one SUCCEEDED/RUNNING/PENDING since non_purged_candidates is non-empty. | ||
| old_execution = non_purged_candidates[-1] | ||
| _logger.info( | ||
| f"Execution {execution.id=} will reuse the {old_execution.id=} with " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's either restore the = or rephrase: Reusing cached execution node {old_execution.id} with
|
|
||
| # region: Logging configuration | ||
| import logging.config | ||
| from cloud_pipelines_backend.instrumentation.logging_context import get_all_context_metadata |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Let's only import modules, not classes/function. https://google.github.io/styleguide/pyguide.html#22-imports
import foo
from foo import bar
If the module name is ambiguous/conflicting, tehn it can be renamed:
from launchers import interfaces as launcher_interfaces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, module names should be plural (e.g. "contexts") or gerund ("logging").
Singular nouns clash with local variables. For example, Kubernetes library has from kubernetes import client and it's a pain.
In this case it's harder to find a good name.
Maybe contextual_logging.py?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, api_tracing.py?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make sure to use Black formatting on all modified files.
| key: The metadata key (e.g., 'execution_id', 'request_id', 'user_id') | ||
| value: The value to set | ||
| """ | ||
| metadata = _context_metadata.get().copy() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the main purpose of .copy() here?
| ) | ||
|
|
||
|
|
||
| def set_context_metadata(key: str, value: Any) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to delete/unset a key?
Similar to a dict.
Ark-kun
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you. This looks pretty good.
I've left some comments and approved it.
Issue
#67
Changes:
extrasparameterextrasparameter, or creating a new one if None)Test pipeline notes:
ytest,starlette,httpx(e.g.pip3 install ...)