Skip to content

Commit 84d9e45

Browse files
committed
Resolve lack of logging.
1 parent c8d652e commit 84d9e45

File tree

12 files changed

+405
-170
lines changed

12 files changed

+405
-170
lines changed

.flake8

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[flake8]
2+
select = E9,F63,F7,F82
3+
exclude = .git,.github,__pycache__,.pytest_cache,.venv,logs,creds
4+
max-line-length = 120

config.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Flask configuration variables."""
2+
23
from os import environ, path
34

45
from dotenv import load_dotenv
@@ -11,6 +12,7 @@ class Config:
1112
"""Set Flask configuration from .env file."""
1213

1314
# General Config
15+
APPLICATION_NAME = "flaskassets"
1416
ENVIRONMENT = environ.get("ENVIRONMENT")
1517

1618
# Flask Config
@@ -27,3 +29,6 @@ class Config:
2729
STATIC_FOLDER = "static"
2830
TEMPLATES_FOLDER = "templates"
2931
COMPRESSOR_DEBUG = True
32+
33+
34+
settings = Config()

flask_assets_tutorial/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Initialize Flask app."""
2+
23
from flask import Flask
34
from flask_assets import Environment
45

flask_assets_tutorial/admin/routes.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"""Routes for logged-in account pages."""
2+
23
from flask import Blueprint, render_template
4+
from log import LOGGER
35

46
admin_blueprint = Blueprint("admin_blueprint", __name__, template_folder="templates", static_folder="static")
57

68

79
@admin_blueprint.route("/dashboard", methods=["GET"])
810
def dashboard():
911
"""Admin dashboard route."""
12+
LOGGER.info("Admin dashboard rendered by user.")
1013
return render_template(
1114
"dashboard.jinja2",
1215
title="Admin Dashboard",

flask_assets_tutorial/assets.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Compile static assets."""
2+
23
from flask import current_app as app
34
from flask_assets import Bundle, Environment
45

flask_assets_tutorial/main/routes.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"""Routes for main pages."""
2+
23
from flask import Blueprint, render_template
4+
from log import LOGGER
35

46
main_blueprint = Blueprint("main_blueprint", __name__, template_folder="templates", static_folder="static")
57

68

79
@main_blueprint.route("/", methods=["GET"])
810
def home():
911
"""Home page route."""
12+
LOGGER.info("Home page rendered by user.")
1013
return render_template(
1114
"index.jinja2",
1215
title="Home",
@@ -19,6 +22,7 @@ def home():
1922
@main_blueprint.route("/about", methods=["GET"])
2023
def about():
2124
"""About page route."""
25+
LOGGER.info("About page rendered by user.")
2226
return render_template(
2327
"index.jinja2",
2428
title="About",
@@ -31,6 +35,7 @@ def about():
3135
@main_blueprint.route("/etc", methods=["GET"])
3236
def etc():
3337
"""Etc page route."""
38+
LOGGER.info("Etc. page rendered by user.")
3439
return render_template(
3540
"index.jinja2",
3641
title="Etc.",

gunicorn.conf.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Gunicorn configuration file."""
2+
23
import socket
34
from os import environ, path
45

@@ -23,7 +24,7 @@
2324

2425
if ENVIRONMENT == "production":
2526
daemon = True
26-
accesslog = "/var/log/flaskassets/access.log"
27+
accesslog = "/var/log/flaskassets/info.log"
2728
errorlog = "/var/log/flaskassets/error.log"
2829
loglevel = "trace"
29-
dogstatsd_tags = "env:prod,service:flaskassets,language:python"
30+
dogstatsd_tags = "env:prod,service:flaskassets,language:python,type:tutorial"

log.py

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""Custom logger."""
2+
3+
import json
4+
from sys import stdout
5+
6+
from loguru import logger
7+
8+
from config import settings
9+
10+
11+
def json_formatter(record: dict) -> str:
12+
"""
13+
Pass raw log to be serialized.
14+
15+
:param dict record: Dictionary containing logged message with metadata.
16+
17+
:returns: str
18+
"""
19+
20+
def serialize(log: dict) -> str:
21+
"""
22+
Parse log message into Datadog JSON format.
23+
24+
:param dict log: Dictionary containing logged message with metadata.
25+
26+
:returns: str
27+
"""
28+
subset = {
29+
"time": log["time"].strftime("%m/%d/%Y, %H:%M:%S"),
30+
"message": log["message"],
31+
"level": log["level"].name,
32+
"function": log.get("function"),
33+
"module": log.get("name"),
34+
}
35+
if log.get("exception", None):
36+
subset.update({"exception": log["exception"]})
37+
return json.dumps(subset)
38+
39+
record["extra"]["serialized"] = serialize(record)
40+
return "{extra[serialized]},\n"
41+
42+
43+
def log_formatter(record: dict) -> str:
44+
"""
45+
Formatter for .log records
46+
47+
:param dict record: Key/value object containing log message & metadata.
48+
49+
:returns: str
50+
"""
51+
if record["level"].name == "TRACE":
52+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #d2eaff>{level}</fg #d2eaff>: <light-white>{message}</light-white>\n"
53+
if record["level"].name == "INFO":
54+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #98bedf>{level}</fg #98bedf>: <light-white>{message}</light-white>\n"
55+
if record["level"].name == "WARNING":
56+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #b09057>{level}</fg #b09057>: <light-white>{message}</light-white>\n"
57+
if record["level"].name == "SUCCESS":
58+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #6dac77>{level}</fg #6dac77>: <light-white>{message}</light-white>\n"
59+
if record["level"].name == "ERROR":
60+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #a35252>{level}</fg #a35252>: <light-white>{message}</light-white>\n"
61+
if record["level"].name == "CRITICAL":
62+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #521010>{level}</fg #521010>: <light-white>{message}</light-white>\n"
63+
return "<fg #5278a3>{time:MM-DD-YYYY HH:mm:ss}</fg #5278a3> | <fg #98bedf>{level}</fg #98bedf>: <light-white>{message}</light-white>\n"
64+
65+
66+
def create_logger() -> logger:
67+
"""
68+
Configure custom logger.
69+
70+
:returns: logger
71+
"""
72+
logger.remove()
73+
logger.add(
74+
stdout,
75+
colorize=True,
76+
catch=True,
77+
level="TRACE",
78+
format=log_formatter,
79+
)
80+
if settings.ENVIRONMENT == "production":
81+
# Datadog JSON logs
82+
logger.add(
83+
f"/var/log/{settings.APPLICATION_NAME}/info.json",
84+
format=json_formatter,
85+
rotation="200 MB",
86+
level="TRACE",
87+
compression="zip",
88+
)
89+
# Readable logs
90+
logger.add(
91+
f"/var/log/{settings.APPLICATION_NAME}/info.log",
92+
colorize=True,
93+
catch=True,
94+
level="TRACE",
95+
format=log_formatter,
96+
rotation="200 MB",
97+
compression="zip",
98+
)
99+
else:
100+
logger.add(
101+
"./logs/info.log",
102+
colorize=True,
103+
catch=True,
104+
format=log_formatter,
105+
rotation="200 MB",
106+
compression="zip",
107+
level="INFO",
108+
)
109+
logger.add(
110+
"./logs/error.log",
111+
colorize=True,
112+
catch=True,
113+
format=log_formatter,
114+
rotation="200 MB",
115+
compression="zip",
116+
level="ERROR",
117+
)
118+
return logger
119+
120+
121+
# Custom logger
122+
LOGGER = create_logger()

0 commit comments

Comments
 (0)