Skip to content
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

Add logging to API #2051

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft

Add logging to API #2051

wants to merge 18 commits into from

Conversation

anth-volk
Copy link
Collaborator

@anth-volk anth-volk commented Dec 9, 2024

Fixes #1947.

This PR adds logging to both the API and attached workers, as well as testing to the base Logger class (but not yet WorkerLogger). I'm opening this in draft because there are still a few drawbacks (below) that I'm stuck on, and I'd love any assistance the requested reviewers might have. This is very much a work in progress, and as I've delved into some of the complexities of logging, I realize that there are a lot of considerations this code may not be handling, even at a design level. Any and all feedback is welcomed.

Drawbacks:

  • When running Flask in debug mode, Flask starts a server process, triggering the start of the logger. However, it then uses this process to start a child process also running the API, but with Werkzeug debugging tools, creating yet another log file. It's this child process that will ultimately be in use. I've tried checking for particular env variables and only launching when the right combo is present, but this feels hackish and has negative ramifications for the worker. I've also tried using the Flask before_request feature, but that's not a solution.
  • I was unable to properly launch memory usage logging. My preferred implementation merely copied what I had done previously in Add logging to worker #1948, but this either crashed (if I used a weak ref to store the logger) or improperly logged memory stats for all workers (if using a strong one). I am unsure how to proceed on this.
  • The reform impact service is typically only run alongside a job, which is itself run on a worker. However, the service interfaces with portions of the code that are not isolated to the job itself, creating challenges when choosing a logging file to write to. If logging with the more basic Logger class, the service will actually create a new log file, perhaps because it is run within the worker thread, not the main API one. However, if logging with the WorkerLogger class, the service has no access to the proper worker ID (at least, not that I found), so it will create a file with the wrong name and log to a separate file from the worker itself.

@anth-volk anth-volk marked this pull request as draft December 9, 2024 19:12
@anth-volk anth-volk requested a review from mikesmit December 9, 2024 19:12
Copy link
Collaborator

@mikesmit mikesmit left a comment

Choose a reason for hiding this comment

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

Pausing here because I think the main comment about "why aren't we doing this the usual log4x way" is the most relevant and impacts the rest of the review.

import os


class Logger:
Copy link
Collaborator

Choose a reason for hiding this comment

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

discussion, blocking With the qualifier I am looking at this as a fairly standard Log4X-style logger, but there may be aspects /conventions in python that make this different...

Generally I would expect you to just use logging.getLogger in your classes like this

And configure your logs globally at the root logger level rather than doing it on child loggers, generally with something like logging.config.fileConfig or command line arguments or whatever).

Where you might have different settings for the main app thread vs. the worker thread and you could tune specific loggers

suggestion I think this is worth a discussion. You may have gone this way for specific reasons that I'm not aware of.


# Format message with context if provided
if context:
context_str = " ".join(f"{k}={v}" for k, v in context.items())
Copy link
Collaborator

Choose a reason for hiding this comment

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

This makes more sense to me in terms of standardizing your message string. you don't necessarily need to wrap logger to do this though, you could just write a 'format_context' function or whatever:

logger.info(format_context(message, context))

self.cloud_client: cloud_logging.Client = None

# Prevent duplicate handlers
if not self.logger.handlers:
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe if we configure as discussed above you don't have to worry about this.

self.logger.addHandler(console_handler)

# Google Cloud Logging handler; don't log to GCP if in debug
if log_to_cloud and os.environ.get("FLASK_DEBUG") != "1":
Copy link
Collaborator

Choose a reason for hiding this comment

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

Similarly you could configure this at the application level rather than in code at each logger declaration. Maybe there are reasons to only cloud log some things, but I'd generally expect it to be on/off for the whole app.

Comment on lines +27 to +29
monitor_memory (bool): Whether to monitor memory usage (defaults to True)
memory_threshold (int): Memory usage threshold to trigger warnings (default: 75%)
memory_check_interval (int): How often to check memory in seconds (default: 5)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nitt, documentation Arguments in documentation not in the constructor. suggestion remove them

Comment on lines +37 to +43
self.logger = logging.getLogger(self.name)
self.logger.setLevel(logging.INFO)

# Create log directory if it doesn't exist
try:
self.dir.mkdir(parents=True, exist_ok=True)
except Exception as e:
Copy link
Collaborator

Choose a reason for hiding this comment

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

question, blocking - who/what is this for? Google app engine looks at either stdout/stderr or the cloud logging API.

For that reason I'm surprised we write this by default even in the cloud.

suggestion I would not do this by default I would only do it in dev mode. In the context of the suggestion above I would pass a different config in dev than in prod.

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.

Add logging to worker
2 participants