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

Ayr 1297/audit app logs #646

Merged
merged 20 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,20 +599,31 @@ This config includes:
`setup_logging` is called during the initialization of the Flask app.

### Usage
We use two loggers in this application:

We can utilise the Flask logger by accessing Flask's `app.logger`. Since we define our routes with blueprints rather than the app directly, we can call access app through
- app_logger for application-related logs
- audit_logger for audit-specific logs

Both are attached to the Flask app instance for easy access across the app. Since we define routes using blueprints instead of directly on the app, we access these loggers through Flask’s current_app.

To use these loggers in your code, import current_app from Flask:

```python
from flask import current_app
```

for example:
Then, call the appropriate logger as follows:

```python
current_app.logger.info('Some info message')
current_app.logger.debug('Some debug message')
current_app.logger.warning('Some warning message')
current_app.logger.error('Some error message')
current_app.app_logger.info('Some info message')
current_app.app_logger.debug('Some debug message')
current_app.app_logger.warning('Some warning message')
current_app.app_logger.error('Some error message')

current_app.audit_logger.info('Some info message')
current_app.audit_logger.debug('Some debug message')
current_app.audit_logger.warning('Some warning message')
current_app.audit_logger.error('Some error message')
```

### Output
Expand Down
55 changes: 51 additions & 4 deletions app/logger_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import logging
import os

import boto3
from botocore.exceptions import ClientError
from flask import has_request_context, request
from flask.logging import default_handler


class RequestFormatter(logging.Formatter):
Expand All @@ -12,15 +14,60 @@ def format(self, record):
else:
record.url = None
record.remote_addr = None

return super().format(record)


class CloudWatchHandler(logging.Handler):
colinbowen marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, log_group, stream_name):
super().__init__()
self.client = boto3.client("logs")
self.log_group = log_group
self.stream_name = stream_name

def emit(self, record):
try:
log_entry = self.format(record)
self.client.put_log_events(
logGroupName=self.log_group,
logStreamName=self.stream_name,
logEvents=[
{
"timestamp": int(record.created * 1000),
"message": log_entry,
}
],
)
except ClientError as e:
print(f"Error sending log to CloudWatch: {e}")


def setup_logging(app):
formatter = RequestFormatter(
"[%(asctime)s] %(remote_addr)s requested %(url)s\n"
"%(levelname)s in %(module)s: %(message)s"
)
default_handler.setFormatter(formatter)

app.logger.setLevel(logging.INFO)
app_logger = logging.getLogger("app_logger")
app_logger.setLevel(logging.INFO)
audit_logger = logging.getLogger("audit_logger")
audit_logger.setLevel(logging.INFO)

if os.getenv("ENV") == "aws":
colinbowen marked this conversation as resolved.
Show resolved Hide resolved
app_handler = CloudWatchHandler(
log_group="app-logs", stream_name="app-log-stream"
)
audit_handler = CloudWatchHandler(
log_group="audit-logs", stream_name="audit-log-stream"
)
else:
anthonyhashemi marked this conversation as resolved.
Show resolved Hide resolved
app_handler = logging.StreamHandler()
audit_handler = logging.StreamHandler()

app_handler.setFormatter(formatter)
audit_handler.setFormatter(formatter)

app_logger.addHandler(app_handler)
audit_logger.addHandler(audit_handler)

app.audit_logger = audit_logger
app.app_logger = app_logger
8 changes: 4 additions & 4 deletions app/main/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ def record(record_id: uuid.UUID):
try:
presigned_url = create_presigned_url(file)
except Exception as e:
current_app.logger.info(
current_app.app_logger.info(
f"Failed to create presigned url for document render non-javascript fallback {e}"
)

Expand Down Expand Up @@ -726,7 +726,7 @@ def download_record(record_id: uuid.UUID):
try:
s3_file_object = s3.get_object(Bucket=bucket, Key=key)
except Exception as e:
current_app.logger.error(f"Failed to get object from S3: {e}")
current_app.app_logger.error(f"Failed to get object from S3: {e}")
abort(404)

download_filename = file.FileName
Expand All @@ -741,7 +741,7 @@ def download_record(record_id: uuid.UUID):
file_content = s3_file_object["Body"].read()
file_type = download_filename.split(".")[-1].lower()
except Exception as e:
current_app.logger.error(f"Error reading S3 file content: {e}")
current_app.app_logger.error(f"Error reading S3 file content: {e}")
abort(500)

content_type = s3_file_object.get("ContentType", "application/octet-stream")
Expand All @@ -760,7 +760,7 @@ def download_record(record_id: uuid.UUID):
as_attachment=True,
download_name=download_filename,
)
current_app.logger.info(
current_app.app_logger.info(
colinbowen marked this conversation as resolved.
Show resolved Hide resolved
json.dumps({"user_id": session["user_id"], "file": key})
)
return response
Expand Down
6 changes: 3 additions & 3 deletions app/main/util/render_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def get_download_filename(file):
def create_presigned_url(file):
file_extension = file.FileName.split(".")[-1].lower()
if file_extension not in current_app.config["SUPPORTED_RENDER_EXTENSIONS"]:
current_app.logger.warning(
current_app.app_logger.warning(
f"Rendering file format '{file_extension}' is not currently supported by AYR."
)
return None
Expand Down Expand Up @@ -88,7 +88,7 @@ def generate_pdf_manifest(record_id):
try:
presigned_url = create_presigned_url(file)
except Exception as e:
current_app.logger.info(
current_app.app_logger.info(
f"Failed to create presigned url for document render non-javascript fallback {e}"
)

Expand Down Expand Up @@ -166,7 +166,7 @@ def generate_image_manifest(s3_file_object, record_id):
try:
presigned_url = create_presigned_url(file)
except Exception as e:
current_app.logger.info(
current_app.app_logger.info(
f"Failed to create presigned url for document render non-javascript fallback {e}"
)

Expand Down
Loading