-
Notifications
You must be signed in to change notification settings - Fork 77
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
Structured logging for DSR requests #4594
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 1 Ignored Deployment
|
Passing run #6270 ↗︎
Details:
Review all test suite changes for PR #4594 ↗︎ |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #4594 +/- ##
=======================================
Coverage 86.82% 86.83%
=======================================
Files 331 332 +1
Lines 19885 19975 +90
Branches 2553 2569 +16
=======================================
+ Hits 17265 17345 +80
- Misses 2152 2157 +5
- Partials 468 473 +5 ☔ View full report in Codecov by Sentry. |
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.
@galvana i haven't done UAT testing yet since this is still in draft, but i think the approach here looks great! a couple minor comments for now but, assuming you've confirmed this is giving us good output that's in line with the expectations in the ticket, i think this is a good path to continue down and i'd be happy to do some proper UAT testing if/when you feel that's ready.
have we confirmed SaaS request execution logging as a sufficient scope for this increment? if so, then this looks pretty much feature-complete, i think. that being said, my comments are mainly around future-proofing and how to ensure consistency as we'll look to build on this. it may not be time to act on those concerns quite yet, but at least wanted to share some of my thoughts as i was going through!
"""Log SaaS request and response in dev mode only""" | ||
if CONFIG.dev_mode: | ||
logger.info( | ||
"\n\n-----------SAAS REQUEST-----------" |
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.
lol, this is nice to get rid of :)
def exception_details(exception: Exception, url: str) -> Dict[str, Any]: | ||
"""Maps select connection exceptions to user-friendly error details.""" | ||
|
||
details = {"error_group": ErrorGroup.network_error.value} |
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.
this confuses me a bit, when considered alongside the function signature. the function seems to apply to any Exception
generally, but here we effectively assume that it's a network_error
. that may be the only context in which this helper function is called currently, but seems like something that one could easily be tripped up on when trying to extend these utilities to other use cases.
so, either i'm misunderstanding something, or i'd recommend adjusting the function name or logic (or at least docstring)
if connector.current_collection_name: | ||
details["collection"] = connector.current_collection_name | ||
if connector.current_privacy_request: | ||
details["privacy_request_id"] = connector.current_privacy_request.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.
nit: looking ahead to a broader logging capability, can you think of any (simple) ways to make more generic/extensible the pieces that add the generic privacy request details to the log context/dictionary?
i.e., we'll probably want the privacy_request_id
put on the logger context even when we don't have a SaaSconnector
available to us (on non-SaaS DSRs). it'd be nice to ensure consistency/DRY code for adding that piece of context in all use cases.
all that being said, that may be an over-abstraction for now, if we're just looking to get the SaaS connector support, so totally fine deferring on this till later. just wanted to mention and keep eyes on what's ahead!
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.
one very rudimentary thing i can think of, at least for now, is just defining the privacy_request_id
key as a module-level constant rather than a string defined inline. that's at least one measure we can take to help ensure consistency across contexts. we also could do the same for other keys that we anticipate will be reused across different context. 👍
the reason i key (pun intended) in on this is because consistency across logs is super important for being able to sift through the noise! so the closer we can get to a formal "schema" for the context we're adding (across the entire app), the better!
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.
nice to pull these utils out into their own module 👍
could we go a step further and abstract out the (small amount of) boiler plate that's still required to get the dict that's output by these functions into the logger context? maybe not, but something to consider. basically - how can we make it as simple as possible for someone who wants to come in and add a similar type of logging context from a totally different point in the app, with as consistent a schema as possible to what we've started here. having the functions defined in a single place here helps with that goal, for sure - but i wonder if there's anything else we can do to help guide consistency.
take that with a grain of salt though - i'm aware that we can't over-optimize, so it may be nothing to act on for now!
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.
Here's an alternate approach #4609, let me know your thoughts!
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.
oooooh, this looks really nice!
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.
latest changes look good to me, and i really like the way the changes in #4609 are looking - that approach feels very extensible while remaining relatively simple!
i think you'll wanna merge that into this PR and finalize things, right? so i'll hold off on a final approval until then, just to see how things shape up here. but everything else looks good! nice work on all of this 👍
class LoggerContextKeys(Enum): | ||
privacy_request_id = "privacy_request_id" | ||
error_group = "error_group" | ||
error_details = "error_details" | ||
|
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.
nice to declare this as an enum to ensure consistency! 👍
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.
oooooh, this looks really nice!
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.
@galvana awesome work here, i really like how this has turned out! a really elegant paradigm you've set up.
i've got one nit that you could look address, but probably not necessary. i haven't done any testing yet, but i'll approve preemptively anyway, and i'll see if i can follow up with some manual testing.
@wraps(func) | ||
def wrapper(*args: Any, **kwargs: Any) -> Any: | ||
context = dict(additional_context) | ||
for arg in args: |
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.
nit: may we also want to look at the kwargs
here for Contextualizable
instances? could just make the decorator a bit more robust i think? admittedly i'm a bit fuzzy on how args/kwargs come through to the decorator wrapper here so i could be wrong about this! in any case, not a blocker, but may be something quick to make this a bit more defensive, if my understanding is correct!
@@ -161,6 +162,9 @@ def debug(self) -> Dict[str, Any]: | |||
"to": {k: set(v) for k, v in to.items()}, | |||
} | |||
|
|||
def get_log_context(self) -> Dict[LoggerContextKeys, Any]: | |||
return {LoggerContextKeys.collection: self.node.collection.name} |
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.
nice, i think this ends up being quite elegant since these additional pieces of context are composed when passed to a function that's decorated. i really like it! 👏
status_code=response.status_code, errors_to_ignore=ignore_errors | ||
) | ||
context_logger = logger.bind( | ||
**request_details(prepared_request, response, ignore_error) |
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.
at some point, it'd be nice if we could figure out a not-too-crazy way to add/override third party classes (e.g. PreparedRequest
and Response
) and "annotate" them with get_log_context
methods, i.e. make them extend Contextualizable
... and then some of this could be further abstracted out into those methods, rather than the standalone utility functions, which stick out a bit now.
anyway, not something to worry about in this initial iteration, just thought i'd note it!
Closes PROD-1602
Description Of Changes
Uses a combination of a
Contextualizable
mixin and alog_context
decorator. The decorator iterates through the arguments of the decorated function and callsget_log_context
on every argument that implementsContextualizable
. The log_context decorator accepts optional keyword arguments to add additional context outside of the contextualizable arguments.Adding four user-friendly error groups for SaaS connector logs.
All HTTP request logs for SaaS connectors will also include the following metadata
This logging will apply to access, erasure, and consent requests. We can expand this contextualized logging to database connectors or other parts of the app after we receive initial feedback.
Code Changes
logger_context_utils.py
with aContextualizable
base class andlog_context
decoratorlogger.contextualize
in key parts of the access, erasure, and consent request functionsSteps to Confirm
test_privacy_request_logging.py
test or any of the SaaS connector testsPre-Merge Checklist
CHANGELOG.md