Skip to content

Commit 3e14384

Browse files
authored
Improve logging, and other minor refactoring (#62)
* Add stdout logger * Dockerfile formatting * Move CustomJSONEncoder to utils submodule * Add logged_route decorator for, er, logging routes * Tidy up route logging/printing/error handling * Set DEBUG mode to by default * Update test error message * Tweak CI config: do not run 3.9 tests if 3.8 fails * Reblacken * Revert error handling changes
1 parent 2624e30 commit 3e14384

File tree

15 files changed

+109
-85
lines changed

15 files changed

+109
-85
lines changed

.docker/server_dockerfile

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,12 @@ COPY ./pydatalab/ /app
1010
RUN pip install pipenv
1111
RUN pipenv install --deploy --dev
1212

13-
# Run flask server
14-
CMD ["pipenv", "run", "gunicorn", "-w", "2", "--error-logfile", "logs/pydatalab_error.log", "--access-logfile", "logs/pydatalab_access.log", "-b", "0.0.0.0:5001", "pydatalab.main:create_app()"]
13+
# Launch the server
14+
CMD ["pipenv", "run", \
15+
"gunicorn", \
16+
"-w", "2", \
17+
"--error-logfile", "logs/pydatalab_error.log", \
18+
"--access-logfile", "logs/pydatalab_access.log", \
19+
"-b", "0.0.0.0:5001", \
20+
"pydatalab.main:create_app()" \
21+
]

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ jobs:
3737
runs-on: ubuntu-latest
3838

3939
strategy:
40-
fail-fast: false
41-
max-parallel: 4
40+
fail-fast: true
41+
max-parallel: 1
4242
matrix:
4343
python-version: [3.8, 3.9]
4444

pydatalab/pydatalab/blocks/blocks.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,9 @@ def update_from_web(self, data):
122122
"""update the object with data received from the website. Only updates fields
123123
that are specified in the dictionary- other fields are left alone"""
124124
LOGGER.debug(
125-
"Updating block %s from web request with data %s", self.__class__.__name__, data
125+
"Updating block %s from web request with data %s",
126+
self.__class__.__name__,
127+
data,
126128
)
127129
self.data.update(data)
128130

@@ -151,26 +153,25 @@ def plot_functions(self):
151153

152154
def generate_xrd_plot(self):
153155
if "file_id" not in self.data:
154-
print("XRDBlock.generate_xrd_plot(): No file set in the DataBlock")
155-
return None
156+
LOGGER.warning("XRDBlock.generate_xrd_plot(): No file set in the DataBlock")
157+
return
156158
file_info = get_file_info_by_id(self.data["file_id"], update_if_live=True)
157159

158160
filename = file_info["name"]
159161
ext = os.path.splitext(filename)[-1].lower()
160162

161163
if ext not in [".xrdml", ".xy"]:
162-
print(
164+
LOGGER.warning(
163165
"XRDBlock.generate_xrd_plot(): Unsupported file extension (must be .xrdml or .xy)"
164166
)
165-
return None
167+
return
166168

167-
print(f"The XRD file to plot is found at: {file_info['location']}")
169+
LOGGER.debug(f"The XRD file to plot is found at: {file_info['location']}")
168170
if ext == ".xrdml":
169-
print("xrdml data received. converting to .xy")
170171
filename = xrd_utils.convertSinglePattern(
171172
file_info["location"]
172173
) # should give .xrdml.xy file
173-
print(f"the path is now: {filename}")
174+
LOGGER.debug("the path is now: %s", filename)
174175
else:
175176
filename = os.path.join(file_info["location"])
176177

pydatalab/pydatalab/blocks/echem_block.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pydatalab import bokeh_plots
1010
from pydatalab.blocks.blocks import DataBlock
1111
from pydatalab.file_utils import get_file_info_by_id
12+
from pydatalab.logger import LOGGER
1213
from pydatalab.simple_bokeh_plot import mytheme
1314
from pydatalab.utils import reduce_df_size
1415

@@ -184,15 +185,16 @@ def plot_cycle(self):
184185
)
185186

186187
if "file_id" not in self.data:
187-
print("No file_id given")
188+
LOGGER.warning("No file_id given")
188189
return
189190

190191
derivative_modes = (None, "dQ/dV", "dV/dQ")
191192

192193
if self.data["derivative_mode"] not in derivative_modes:
193-
print(
194-
f"Invalid derivative_mode provided: {self.data['derivative_mode']!r}. "
195-
f"Expected one of {derivative_modes}. Falling back to `None`."
194+
LOGGER.warning(
195+
"Invalid derivative_mode provided: %s. Expected one of %s. Falling back to `None`.",
196+
self.data["derivative_mode"],
197+
derivative_modes,
196198
)
197199
self.data["derivative_mode"] = None
198200

@@ -227,7 +229,9 @@ def plot_cycle(self):
227229
ext = os.path.splitext(filename)[-1].lower()
228230

229231
if ext not in self.accepted_file_extensions:
230-
print("Unrecognized filetype")
232+
LOGGER.warning(
233+
f"Unrecognized filetype {ext}, must be one of {self.accepted_file_extensions}"
234+
)
231235
return
232236

233237
if file_id in self.cache.get("parsed_file", {}):

pydatalab/pydatalab/config.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ class ServerConfig(BaseSettings):
88
SECRET_KEY: str = Field("dummy key", description="The secret key to use for Flask.")
99

1010
MONGO_URI: str = Field(
11-
"mongodb://localhost:27017/datalabvue", description="The URI for the underlying MongoDB."
11+
"mongodb://localhost:27017/datalabvue",
12+
description="The URI for the underlying MongoDB.",
1213
)
1314

1415
FILE_DIRECTORY: Union[str, Path] = Field(
1516
Path(__file__).parent.joinpath("../files").resolve(),
1617
description="The path under which to place stored files uploaded to the server.",
1718
)
1819

19-
DEBUG: bool = Field(False, description="Whether to enable debug-level logging in the server.")
20+
DEBUG: bool = Field(True, description="Whether to enable debug-level logging in the server.")
2021

2122
class Config:
2223
env_prefix = "pydatalab_"

pydatalab/pydatalab/file_utils.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import pydatalab.mongo
1010
from pydatalab.config import CONFIG
11+
from pydatalab.logger import LOGGER
1112
from pydatalab.models import File
1213
from pydatalab.resources import DIRECTORIES_DICT
1314

@@ -16,7 +17,7 @@
1617

1718
def get_file_info_by_id(file_id, update_if_live=True):
1819
"""file_id can be either the string representation or the ObjectId() object. Returns the file information dictionary"""
19-
print(f"getting file for file_id: {file_id}")
20+
LOGGER.debug("getting file for file_id: %s", file_id)
2021
file_collection = pydatalab.mongo.flask_mongo.db.files
2122
file_id = ObjectId(file_id)
2223
file_info = file_collection.find_one({"_id": file_id})
@@ -32,19 +33,20 @@ def get_file_info_by_id(file_id, update_if_live=True):
3233
try:
3334
stat_results = os.stat(full_remote_path)
3435
except FileNotFoundError:
35-
print(
36+
LOGGER.debug(
3637
"when trying to check if live file needs to be updated, could not access remote file"
3738
)
3839
file_info["live_update_error"] = "Could not reach remote server to update"
3940
return file_info
4041

4142
current_timestamp_on_server = datetime.datetime.fromtimestamp(stat_results.st_mtime)
42-
print(
43-
f"checking if update is necessary. Cached timestamp: {cached_timestamp}. Current timestamp: {current_timestamp_on_server}."
43+
LOGGER.debug(
44+
"checking if update is necessary. Cached timestamp: %s. Current timestamp: %s.",
45+
cached_timestamp,
46+
current_timestamp_on_server,
4447
)
45-
print(f"\tDifference: {current_timestamp_on_server - cached_timestamp} seconds")
48+
LOGGER.debug("\tDifference: %s seconds", current_timestamp_on_server - cached_timestamp)
4649
if current_timestamp_on_server > cached_timestamp:
47-
print("updating file")
4850
shutil.copy(full_remote_path, file_info.location)
4951
updated_file_info = file_collection.find_one_and_update(
5052
{"_id": file_id},

pydatalab/pydatalab/logger.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import logging
2+
from functools import wraps
3+
from typing import Callable
24

35

46
def setup_log():
7+
"""Creates a logger for pydatalab with a simple
8+
stdout output.
9+
10+
Verbosity is set in the config file via
11+
the DEBUG option.
12+
13+
"""
514
from pydatalab.config import CONFIG
615

716
logging.basicConfig()
@@ -13,4 +22,28 @@ def setup_log():
1322
return logger
1423

1524

25+
"""The main logging object to be imported from elsewhere in the package."""
1626
LOGGER = setup_log()
27+
28+
29+
def logged_route(fn: Callable):
30+
"""A decorator that enables logging of inputs and
31+
outputs when debug mode is enabled.
32+
33+
"""
34+
35+
@wraps(fn)
36+
def wrapped_logged_route(*args, **kwargs):
37+
from flask import request
38+
39+
LOGGER.debug(
40+
"Calling %s with request: %s, JSON payload: %s",
41+
fn.__name__,
42+
request,
43+
request.get_json(),
44+
)
45+
result = fn(*args, **kwargs)
46+
LOGGER.debug("%s returned %s", fn.__name__, result)
47+
return result
48+
49+
return wrapped_logged_route

pydatalab/pydatalab/main.py

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
import datetime
21
from typing import Any, Dict
32

4-
from bson import json_util
53
from flask import Flask
6-
from flask.json import JSONEncoder
74
from flask_cors import CORS
85

96
import pydatalab.mongo
107
from pydatalab.config import CONFIG
11-
12-
13-
class CustomJSONEncoder(JSONEncoder):
14-
"""Use a JSON encoder that can handle pymongo's bson."""
15-
16-
def default(self, obj):
17-
if isinstance(obj, datetime.datetime):
18-
return datetime.datetime.isoformat(obj)
19-
return json_util.default(obj)
8+
from pydatalab.logger import logged_route
9+
from pydatalab.utils import CustomJSONEncoder
2010

2111

2212
def create_app(config_override: Dict[str, Any] = None) -> Flask:
@@ -57,9 +47,9 @@ def register_endpoints(app):
5747
from pydatalab.routes import ENDPOINTS # pylint: disable=import-outside-toplevel
5848

5949
for rule, func in ENDPOINTS.items():
60-
app.add_url_rule(rule, func.__name__, func)
50+
app.add_url_rule(rule, func.__name__, logged_route(func))
6151

6252

6353
if __name__ == "__main__":
6454
app_ = create_app()
65-
app_.run(host="0.0.0.0", debug=True, port=5001)
55+
app_.run(host="0.0.0.0", debug=CONFIG.DEBUG, port=5001)

pydatalab/pydatalab/remote_filesystems.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import subprocess
55

66
import pydatalab.mongo
7+
from pydatalab.logger import LOGGER
78
from pydatalab.resources import DIRECTORIES
89

910
# from fs.smbfs import SMBFS
@@ -57,8 +58,7 @@ def save_directory_structure_to_db(directory_name, dir_structure):
5758
},
5859
upsert=True,
5960
)
60-
print("Result of saving directory structure to the db:")
61-
print(result.raw_result)
61+
LOGGER.debug("Result of saving directory structure to the db: %s", result.raw_result)
6262

6363

6464
def get_cached_directory_structure_from_db(directory_name):
@@ -68,12 +68,12 @@ def get_cached_directory_structure_from_db(directory_name):
6868
def get_all_directory_structures(directories=DIRECTORIES):
6969
all_directory_structures = []
7070
for directory in directories:
71-
print(f"Retrieving remote directory {directory['name']} at {directory['path']}")
71+
LOGGER.debug("Retrieving remote directory %s at %s", directory["name"], directory["path"])
7272
try:
7373
dir_structure = get_directory_structure_json(directory["path"])
7474
save_directory_structure_to_db(directory["name"], dir_structure)
75-
except (json.JSONDecodeError, KeyError):
76-
print("Error reading remote filetree json.")
75+
except (json.JSONDecodeError, KeyError) as exc:
76+
LOGGER.warning("Error reading remote filetree json: %s", exc)
7777
dir_structure = [{"type": "error", "name": "Could not reach remote server"}]
7878

7979
wrapped_dir_structure = {

pydatalab/pydatalab/routes/blocks.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def add_data_block():
1616
item_id = request_json["item_id"]
1717
insert_index = request_json["index"]
1818

19-
print(f"Adding a block of type: {block_type} to items: {item_id}")
2019
if block_type not in BLOCK_TYPES:
2120
return jsonify(status="error", message="Invalid block type"), 400
2221

@@ -42,19 +41,17 @@ def add_data_block():
4241
},
4342
)
4443

45-
print(result.raw_result)
4644
if result.modified_count < 1:
4745
return (
4846
jsonify(
4947
status="error",
50-
message="Update failed. The item_id probably incorrect: {}".format(item_id),
48+
message=f"Update failed. {item_id=} is probably incorrect.",
5149
),
5250
400,
5351
)
5452

5553
# get the new display_order:
5654
display_order_result = flask_mongo.db.items.find_one({"item_id": item_id}, {"display_order": 1})
57-
print("new document: {}".format(display_order_result))
5855

5956
return jsonify(
6057
status="success",
@@ -73,7 +70,6 @@ def update_block():
7370
plot
7471
"""
7572
request_json = request.get_json()
76-
print("update_block called with : " + str(request_json)[:1000])
7773
block_data = request_json["block_data"]
7874
blocktype = block_data["blocktype"]
7975

@@ -93,7 +89,6 @@ def delete_block():
9389
item_id = request_json["item_id"]
9490
block_id = request_json["block_id"]
9591

96-
# print(update)
9792
result = flask_mongo.db.items.update_one(
9893
{"item_id": item_id},
9994
{
@@ -105,10 +100,6 @@ def delete_block():
105100
},
106101
)
107102

108-
print("Removing block: {} , from sample: {}".format(block_id, item_id))
109-
print("result:")
110-
print(result.raw_result)
111-
112103
if result.modified_count < 1:
113104
return (
114105
jsonify(

0 commit comments

Comments
 (0)