15

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?

Community
  • 1
  • 1
gNeandr
  • 273
  • 1
  • 4
  • 14
  • You accepted ayb's answer, but please consider changing that. No need to accept mine (unless you think it's the best correct answer), but I'd rather not see future visitors to this question be misled by ayb's code, which should not be used except in the most lightweight of applications (and even then, it's a questionable practice). – ron rothman Jun 27 '15 at 21:07

3 Answers3

11

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
ron rothman
  • 14,574
  • 6
  • 34
  • 35
  • Tested, works! Going to add some statements so a normal 'print' will log also to the file. – gNeandr Jun 28 '15 at 08:55
  • Did you removed the 'print' I added? I'm trying to use your solution with starting the bottle program on a thread ... going to post it below – gNeandr Jun 28 '15 at 13:04
  • This solution may be used in threads with no issues. Not sure what you're asking about the "print," sorry. You added a "print" somewhere? – ron rothman Jun 28 '15 at 13:10
  • tried to add/edit in your listing "print = logger.info" etc ... but somehow it was rejected. – gNeandr Jun 28 '15 at 13:11
  • 1
    `print = logging.info` is a red flag. (Seriously, don't do it.) What are you trying to accomplish? – ron rothman Jun 28 '15 at 13:16
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/81783/discussion-between-gneandr-and-ron-rothman). – gNeandr Jun 28 '15 at 13:17
0

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.

gNeandr
  • 273
  • 1
  • 4
  • 14
  • @Ron Any idea what prevents outputting the 'yield' to the log file? – gNeandr Jun 28 '15 at 13:15
  • What yield are you talking about? I don't see a yield anywhere. – ron rothman Jun 28 '15 at 19:24
  • Upps, please see your comment "Running that code yields what you want" above ... part of it you posted "127.0.0.1 2015-06-27 16:57:09.983249 GET http://localhost:8080/ 200 OK" ... and that's what I'm missing when starting the bottle part in a thread .. see my answer above. – gNeandr Jun 28 '15 at 20:48
  • So if I understand correctly, you are using 'print' statements? Why aren't you using logging statements instead? Print is not a good way to log. – ron rothman Jun 28 '15 at 21:15
  • I found it helpful to enable the 'print' statements also going to the log file with just en/disabling "print = logger.info" – gNeandr Jun 28 '15 at 21:16
  • Okay, suit yourself. :) – ron rothman Jun 28 '15 at 21:18
-2

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

ayb
  • 56
  • 3
  • Yes, that works. But I see a conflict because the way this solution writes to a log would be writing to the same log file as with the main part of the project (see referenced link above). The bottle project is started on one of some threads and I think the "bottle logs" should be handled by the main project part also. – gNeandr Jun 27 '15 at 20:13
  • 4
    WARNING: do not do this, unless you REALLY don't care about performance. This code opens a file on *every* single request, which is a terrible idea. – ron rothman Jun 27 '15 at 20:41