python bottle always logs to console, no logging to file
Question:
In a python project with multiple threads my logging works well to write to a logger file. Basically based on Logging, StreamHandler and standard streams
Part of my project is a bottle web server which runs well also. But every bottle call writes a log to the console like this:
192.168.178.20 - - [26/Jun/2015 20:22:17] "GET /edit?addJob HTTP/1.1" 200 48028
How to handle this the same way as with the other code, so the bottle logs go also to the logger file?
Answers:
You running builtin server right ? Then you can make a simple plugin :
from bottle import request, response, route, install, run
from datetime import datetime
def logger(func):
def wrapper(*args, **kwargs):
log = open('log.txt', 'a')
log.write('%s %s %s %s %s n' % (request.remote_addr, datetime.now().strftime('%H:%M'),
request.method, request.url, response.status))
log.close()
req = func(*args, **kwargs)
return req
return wrapper
install(logger)
@route('/')
def index():
return 'Hello, World'
run(quiet=True)
Or try this one
If you’re rolling your own solution, you should write a simple Bottle plugin that emits log lines to a logging
logger. Here’s an example that sets up a basic logger, defines the logging plugin, and creates a Bottle app with that plugin installed on all routes.
from bottle import Bottle, request, response
from datetime import datetime
from functools import wraps
import logging
logger = logging.getLogger('myapp')
# set up the logger
logger.setLevel(logging.INFO)
file_handler = logging.FileHandler('myapp.log')
formatter = logging.Formatter('%(msg)s')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
def log_to_logger(fn):
'''
Wrap a Bottle request so that a log line is emitted after it's handled.
(This decorator can be extended to take the desired logger as a param.)
'''
@wraps(fn)
def _log_to_logger(*args, **kwargs):
request_time = datetime.now()
actual_response = fn(*args, **kwargs)
# modify this to log exactly what you need:
logger.info('%s %s %s %s %s' % (request.remote_addr,
request_time,
request.method,
request.url,
response.status))
return actual_response
return _log_to_logger
app = Bottle()
app.install(log_to_logger)
@app.route('/')
def home():
return ['hello, world']
app.run(host='0.0.0.0', port='8080', quiet=True)
Running that code yields what you want:
% python myapp.py &
% curl -v http://localhost:8080/
% tail myapp.log
127.0.0.1 2015-06-27 16:57:09.983249 GET http://localhost:8080/ 200 OK
I’m trying to use Ron’s solution with starting the bottle program on a thread:
tWeb = Thread(target=runWeb, args=('192.168.178.16', 5003)).start()
with
def runWeb(aserver, aport):
run(host=aserver, port=aport, debug=True)
but that fails. Any ‘print’ goes to the file, but not the ‘yield’ (see above), it goes to the console.
Also changing “debug=True” to “quiet=True” only changes to: there is no output on the console at all.
Ron’s solution logs only requests which were handled by the routes. If you want to log everything, including 404 file not found, you should consider a simple WSGI middleware like wsgi-request-logger.
#!/usr/bin/python3
import bottle
from bottle import route
from requestlogger import WSGILogger, ApacheFormatter
import waitress
import logging
import sys
@route('/hello')
def hello():
return 'Hello World'
waitress.serve(WSGILogger(
bottle.default_app(), [logging.StreamHandler(sys.stdout)],
ApacheFormatter(), propagate=False
))
The output looks the following way:
192.168.190.102 - - [09/Mar/2023:09:39:10 +0200] "GET /hello HTTP/1.1" 200 11 "" "curl/7.68.0" 0/228
192.168.190.102 - - [09/Mar/2023:09:39:14 +0200] "GET /zzz HTTP/1.1" 404 731 "" "curl/7.68.0" 0/9617
If you want to log to a file, you can add another logging
handler in the list or replace the existing one completely. Here is an example of a handler which logs to a file in a fashion similar to the Apache access logs:
#!/usr/bin/python3
import waitress
import bottle
from bottle import route
from requestlogger import WSGILogger, ApacheFormatter
import logging
import sys
from logging.handlers import TimedRotatingFileHandler
@route('/hello')
def hello():
return 'Hello World'
logging_handlers = [
logging.StreamHandler(sys.stdout),
TimedRotatingFileHandler('access.log', 'd', 7)
]
waitress.serve(WSGILogger(
bottle.default_app(), logging_handlers,
ApacheFormatter(), propagate=False
))
I don’t know what’s the performance impact of the middleware.
In a python project with multiple threads my logging works well to write to a logger file. Basically based on Logging, StreamHandler and standard streams
Part of my project is a bottle web server which runs well also. But every bottle call writes a log to the console like this:
192.168.178.20 - - [26/Jun/2015 20:22:17] "GET /edit?addJob HTTP/1.1" 200 48028
How to handle this the same way as with the other code, so the bottle logs go also to the logger file?
You running builtin server right ? Then you can make a simple plugin :
from bottle import request, response, route, install, run
from datetime import datetime
def logger(func):
def wrapper(*args, **kwargs):
log = open('log.txt', 'a')
log.write('%s %s %s %s %s n' % (request.remote_addr, datetime.now().strftime('%H:%M'),
request.method, request.url, response.status))
log.close()
req = func(*args, **kwargs)
return req
return wrapper
install(logger)
@route('/')
def index():
return 'Hello, World'
run(quiet=True)
Or try this one
If you’re rolling your own solution, you should write a simple Bottle plugin that emits log lines to a logging
logger. Here’s an example that sets up a basic logger, defines the logging plugin, and creates a Bottle app with that plugin installed on all routes.
from bottle import Bottle, request, response
from datetime import datetime
from functools import wraps
import logging
logger = logging.getLogger('myapp')
# set up the logger
logger.setLevel(logging.INFO)
file_handler = logging.FileHandler('myapp.log')
formatter = logging.Formatter('%(msg)s')
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
def log_to_logger(fn):
'''
Wrap a Bottle request so that a log line is emitted after it's handled.
(This decorator can be extended to take the desired logger as a param.)
'''
@wraps(fn)
def _log_to_logger(*args, **kwargs):
request_time = datetime.now()
actual_response = fn(*args, **kwargs)
# modify this to log exactly what you need:
logger.info('%s %s %s %s %s' % (request.remote_addr,
request_time,
request.method,
request.url,
response.status))
return actual_response
return _log_to_logger
app = Bottle()
app.install(log_to_logger)
@app.route('/')
def home():
return ['hello, world']
app.run(host='0.0.0.0', port='8080', quiet=True)
Running that code yields what you want:
% python myapp.py &
% curl -v http://localhost:8080/
% tail myapp.log
127.0.0.1 2015-06-27 16:57:09.983249 GET http://localhost:8080/ 200 OK
I’m trying to use Ron’s solution with starting the bottle program on a thread:
tWeb = Thread(target=runWeb, args=('192.168.178.16', 5003)).start()
with
def runWeb(aserver, aport):
run(host=aserver, port=aport, debug=True)
but that fails. Any ‘print’ goes to the file, but not the ‘yield’ (see above), it goes to the console.
Also changing “debug=True” to “quiet=True” only changes to: there is no output on the console at all.
Ron’s solution logs only requests which were handled by the routes. If you want to log everything, including 404 file not found, you should consider a simple WSGI middleware like wsgi-request-logger.
#!/usr/bin/python3
import bottle
from bottle import route
from requestlogger import WSGILogger, ApacheFormatter
import waitress
import logging
import sys
@route('/hello')
def hello():
return 'Hello World'
waitress.serve(WSGILogger(
bottle.default_app(), [logging.StreamHandler(sys.stdout)],
ApacheFormatter(), propagate=False
))
The output looks the following way:
192.168.190.102 - - [09/Mar/2023:09:39:10 +0200] "GET /hello HTTP/1.1" 200 11 "" "curl/7.68.0" 0/228
192.168.190.102 - - [09/Mar/2023:09:39:14 +0200] "GET /zzz HTTP/1.1" 404 731 "" "curl/7.68.0" 0/9617
If you want to log to a file, you can add another logging
handler in the list or replace the existing one completely. Here is an example of a handler which logs to a file in a fashion similar to the Apache access logs:
#!/usr/bin/python3
import waitress
import bottle
from bottle import route
from requestlogger import WSGILogger, ApacheFormatter
import logging
import sys
from logging.handlers import TimedRotatingFileHandler
@route('/hello')
def hello():
return 'Hello World'
logging_handlers = [
logging.StreamHandler(sys.stdout),
TimedRotatingFileHandler('access.log', 'd', 7)
]
waitress.serve(WSGILogger(
bottle.default_app(), logging_handlers,
ApacheFormatter(), propagate=False
))
I don’t know what’s the performance impact of the middleware.