Use a logger rather than print statements.

Unfortunately, bottle.py logs to stderr/stdout on its own.
This commit is contained in:
s3lph 2019-09-01 23:13:24 +02:00
parent 2ceb141864
commit dec4e98a0c
5 changed files with 48 additions and 27 deletions

View file

@ -1,8 +1,7 @@
from typing import List from typing import List
import json import json
from urllib.error import HTTPError import logging
import traceback
import bottle import bottle
@ -26,7 +25,7 @@ def prometheus_api():
'error': str(e) 'error': str(e)
} }
bottle.response.status = 400 bottle.response.status = 400
traceback.print_exc() logging.exception('Cannot parse PromQL query')
bottle.response.add_header('Content-Type', 'application/json') bottle.response.add_header('Content-Type', 'application/json')
return json.dumps(response) return json.dumps(response)
@ -42,14 +41,6 @@ def prometheus_api():
'result': [e.serialize() for e in events] 'result': [e.serialize() for e in events]
} }
} }
except HTTPError as e:
response = {
'status': 'error',
'errorType': 'internal',
'error': str(e)
}
bottle.response.status = 500
traceback.print_exc()
except BaseException: except BaseException:
response = { response = {
'status': 'error', 'status': 'error',
@ -57,7 +48,7 @@ def prometheus_api():
'error': 'An internal error occurred.' 'error': 'An internal error occurred.'
} }
bottle.response.status = 500 bottle.response.status = 500
traceback.print_exc() logging.exception('An internal error occurred')
bottle.response.add_header('Content-Type', 'application/json') bottle.response.add_header('Content-Type', 'application/json')
return json.dumps(response) return json.dumps(response)

View file

@ -2,6 +2,7 @@ from typing import Dict, List, Iterable
import sys import sys
import urllib.request import urllib.request
import logging
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
from threading import Lock, Timer from threading import Lock, Timer
@ -54,8 +55,12 @@ def _scrape_calendar(name: str, config: CalendarConfig, start: datetime, end: da
events = [] events = []
opener: urllib.request.OpenerDirector = config.get_url_opener() opener: urllib.request.OpenerDirector = config.get_url_opener()
try:
with opener.open(config.url) as response: with opener.open(config.url) as response:
data = response.read().decode('utf-8') data = response.read().decode('utf-8')
except BaseException:
logging.exception(f'An error occurred while scraping the calendar endpoint "{name}" ({config.url})')
return
calendar = cal.Calendar.from_ical(data) calendar = cal.Calendar.from_ical(data)
for element in calendar.walk(): for element in calendar.walk():

View file

@ -6,6 +6,7 @@ from datetime import timedelta
import ssl import ssl
import urllib.request import urllib.request
import sys import sys
import logging
import pytz import pytz
import jinja2 import jinja2
@ -155,7 +156,7 @@ def _keycheck(key: str,
raise KeyError(f'Expected member "{key}" not found at path {path}') raise KeyError(f'Expected member "{key}" not found at path {path}')
value: Any = config[key] value: Any = config[key]
if not isinstance(value, typ): if not isinstance(value, typ):
raise TypeError(f'Expected {typ}, not {type(value).__name__} for path {path}.{key}') raise TypeError(f'Expected {typ.__name__}, not {type(value).__name__} for path {path}.{key}')
if valid_values is not None: if valid_values is not None:
if value not in valid_values: if value not in valid_values:
raise ValueError(f'Expected one of {", ".join(valid_values)} ({typ}), not {value} for path {path}.{key}') raise ValueError(f'Expected one of {", ".join(valid_values)} ({typ}), not {value} for path {path}.{key}')
@ -216,10 +217,17 @@ def get_jenv() -> jinja2.Environment:
def load_config(filename: str): def load_config(filename: str):
global CONFIG, JENV global CONFIG, JENV
try:
with open(filename, 'r') as f: with open(filename, 'r') as f:
json_config = json.loads(f.read()) json_config = json.loads(f.read())
CONFIG = Config(json_config) CONFIG = Config(json_config)
JENV = jinja2.Environment() JENV = jinja2.Environment()
except json.JSONDecodeError as e:
logging.exception('Cannot parse config JSON')
raise e
except Exception as e:
logging.error(e)
raise e
def load_default_config(): def load_default_config():

View file

@ -1,4 +1,5 @@
import sys import sys
import logging
import bottle import bottle
@ -11,17 +12,32 @@ from icalendar_timeseries_server.api import prometheus_api
def main(): def main():
# Set up logger
log_handler = logging.StreamHandler()
log_handler.setFormatter(logging.Formatter(
'%(asctime)s %(filename)s:%(lineno)d(%(funcName)s) [%(levelname)s]: %(message)s'))
logging.getLogger().addHandler(log_handler)
# Load configuration
config = get_config()
try:
if len(sys.argv) == 1: if len(sys.argv) == 1:
load_default_config() load_default_config()
elif len(sys.argv) == 2: elif len(sys.argv) == 2:
load_config(sys.argv[1]) load_config(sys.argv[1])
else: else:
print(f'Can only read one config file, got "{" ".join(sys.argv[1:])}"') logging.log(logging.FATAL, f'Can only read one config file, got "{" ".join(sys.argv[1:])}"')
exit(1) exit(1)
# Re-fetch config after parsing
config = get_config() config = get_config()
except:
logging.fatal('Could not parse configuration file')
exit(1)
# Schedule calendar scraping in the background # Schedule calendar scraping in the background
for calname in config.calendars.keys(): for calname in config.calendars.keys():
start_scrape_calendar(calname, config.calendars[calname]) start_scrape_calendar(calname, config.calendars[calname])
# Start the Bottle HTTP server # Start the Bottle HTTP server
bottle.run(host=config.addr, port=get_config().port) bottle.run(host=config.addr, port=get_config().port)

View file

@ -1,6 +1,7 @@
from typing import Dict from typing import Dict
import re import re
import logging
LABEL_MATCH_OPERATORS = [ LABEL_MATCH_OPERATORS = [
'=', '=',
@ -64,7 +65,7 @@ class MetricQuery:
self.__parse(q) self.__parse(q)
def __parse(self, q: str): def __parse(self, q: str):
print(q) logging.debug(f'Parsing PromQL query string: {q}')
# globalstate: # globalstate:
# 0 = parsing metric name # 0 = parsing metric name
# 1 = parsing filters # 1 = parsing filters