I am building an applicatrion with Flask and Celery and I am trying to send my application logs to Papertrail. This works fine for my regular (synchronous) application logs. The configuration looks like this:
import logging
from logging.handlers import SysLogHandler
import socket
class ContextFilter(logging.Filter):
hostname = socket.gethostname()
def filter(self, record):
record.hostname = ContextFilter.hostname
return True
f = ContextFilter()
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addFilter(f)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
syslog = SysLogHandler(address=('<myapp>.papertrailapp.com', <port>))
syslog.setFormatter(formatter)
logger.addHandler(syslog)
I have tried adding this logger to Celery tasks but all I see is output in sdout and nothing in Papertrail. Does Celery do something to get around the normal logging flow?
I realize Celery has a task-specific logger but I cannot find any documentation on how this could be configured with Celery.
If I read this correctly, the secret is to call the function redirect_stdouts_to_logger to send stdout to your SysLogHandler instance. Celery's docs have more.
Related
I'm currently managing my API using Celery tasks and a kubernetes cluster on Google Cloud Platform.
Celery is automatically logging input and output of each task. This is something I want but I would like to use the possibility of google-cloud-logging to log input and output as jsonPayload.
I use for all other log the following:
from google.cloud.logging.handlers import CloudLoggingHandler
from google.cloud.logging_v2.handlers import setup_logging
# Imports the Cloud Logging client library
import google.cloud.logging
# Instantiates a client
client = google.cloud.logging.Client()
handler = CloudLoggingHandler(client)
setup_logging(handler)
import logging
logger = logging.getLogger(__name__)
data_dict = {"my": "data"}
logger.info("this is an example", extra={"json_fields": data_dict})
And I use Celery with the following template:
app = Celery(**my_params)
#app.task
def task_test(data):
# Update dictonary with new data
data["key1"] = "value1"
return data
...
detection_task = celery.signature('tasks.task_test', args=([[{"hello": "world"}]]))
r = detection_task.apply_async()
data = r.get()
Here's an example of log I receive from Celery:
The blurred part correspond to the dict/json I would like to have in a jsonPayload instead of a textPayload.
(Also note that this log is marked as error on GCP but INFO from celery)
Any idea how I could connect python built-in logging, celery logger and gcp logger ?
To connect your Python logger with GCP Logger:
import logging
import google.cloud.logging
from google.cloud.logging.handlers import CloudLoggingHandler, setup_logging
client = google.cloud.logging.Client()
handler = CloudLoggingHandler(client, name="your_log_name")
cloud_logger = logging.getLogger('cloudLogger')
# configure cloud_logger
cloud_logger.addHandler(handler)
To connect this logger to Celery's logger:
def initialize_log(logger=None,loglevel=logging.DEBUG, **kwargs):
logger = logging.getLogger('celery')
handler = # use GCP handler defined above
handler.setLevel(loglevel)
logger.addHandler(handler)
return logger
from celery.signals import after_setup_task_logger
after_setup_task_logger.connect(initialize_log)
from celery.signals import after_setup_logger
after_setup_logger.connect(initialize_log)
I recently set up logging in my Flask app such that it would log to files and stdout. I was inserting logging statements like
current_app.logger.info('Logging message')
into routes so that I could see logging messages when I ran pytest tests on the routes. This worked (logged to the files and console) for a little while, but at some point it stopped logging to the console while continuing to log to the log files.
I'm not sure what could have caused logging to stdout to stop. The only thing that comes to mind is that I attempted to add a logging statement to the test module at one point. This caused an error, which may have had something to do with the logger not being configured in the app defined by my application factory pytest fixture. However, I've since deleted this logging statement from the test module, and I'm still not getting the console logs that I got previously.
Can anyone see any reason why logging to the console would have stopped?
My logging configuration:
__init__.py:
def create_app(test_config=None):
app = Flask('project_name', instance_relative_config=True)
...
if not app.debug:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/trivia.log', maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
stream_handler = StreamHandler(sys.stdout)
stream_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
stream_handler.setLevel(logging.INFO)
app.logger.addHandler(stream_handler)
app.logger.setLevel(logging.INFO)
routes.py
#bp.route('/add', methods=['POST'])
def add_question():
current_app.logger.info("Received request to /add")
...
This happened because logging messages only get displayed in the console when a pytest test fails. My tests were all passing for a while, so I wasn't seeing the logging messages.
I am logging to a log file in a Flask application under gunicorn and nginx using the following setup:
def setup_logging():
stream_handler = logging.StreamHandler()
formatter = logging.Formatter('[%(asctime)s][PID:%(process)d][%(levelname)s][%(name)s.%(funcName)s()] %(message)s')
stream_handler.setFormatter(formatter)
stream_handler.setLevel("DEBUG")
logging.getLogger().addHandler(stream_handler)
file_handler = RotatingFileHandler("log.txt", maxBytes=100000, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel("DEBUG")
logging.getLogger().addHandler(file_handler)
logging.getLogger().setLevel("DEBUG")
Then inializing logging prior to creating the app:
setup_logging()
def create_app(config_name):
app = Flask(__name__)
Then in modules I am logging using:
import logging
logger = logging.getLogger(__name__)
x = 2
logger.debug('x: {0}' x)
Logging works OK on my local machine - both to stdout and log.txt
However when I run the application on a remote server nothing gets written to log.txt. I have deployed as a user with read and write permission on log.txt on the remote system.
I have tried initializing the app on the remote server with DEBUG = True, still nothing written to log file. The only way I can get view any logs is by viewing /var/log/supervisor/app-stdout---supervisor-nnn.log files but these dont show all logging output
Using the answer from HolgerShurig here Flask logging - Cannot get it to write to a file on the server log file I get only named logger output (ie no output from module level logging)
2017-10-21 00:32:45,125 - file - DEBUG - Debug FILE
running same code lo local machine I get
2017-10-21 08:35:39,046 - file - DEBUG - Debug FILE
2017-10-21 08:35:42,340 - werkzeug - INFO - * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) ie
2017-10-21 08:38:46,236 [MainThread ] [INFO ] 127.0.0.1 - - [21/Oct/2017 08:38:46] "[37mGET /blah/blah HTTP/1.1[0m" 200 -
I then changed the logging config to:
def setup_logging(app):
stream_handler = logging.StreamHandler()
formatter = logging.Formatter('[%(asctime)s][PID:%(process)d][%(levelname)s][%(lineno)s][%(name)s.%(funcName)s()] %(message)s')
stream_handler.setFormatter(formatter)
stream_handler.setLevel(Config.LOG_LEVEL)
app.logger.addHandler(stream_handler)
file_handler = RotatingFileHandler(Config.LOGGING_FILE, maxBytes=Config.LOGGING_MAX_BYTES, backupCount=Config.LOGGING_BACKUP_COUNT)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(Config.LOG_LEVEL)
loggers = [app.logger]
for logger in loggers:
logger.addHandler(file_handler)
app.logger.setLevel(Config.LOG_LEVEL)
app.logger.debug('this message should be recorded in the log file')
and set to us this just after creating the Flask app:
setup_logging(app)
I each module I am still using
import logging
logger = logging.getLogger(__name__)
#for example
def example():
logger.debug('debug')
logger.info('info')
logger.warn('warn')
When I run the application on ther server with
gunicorn manage:app
The only thing printed in the log.txt file is
2017-10-21 02:48:32,982 DEBUG: this message should be recorded in the log file [in /../../__init__.py:82]
But locally MainThread processeses are shown as well
Any ideas?
If your configuration is working on your local machine and not working on your remote server, then your problem is about permission, regarding the file or the directory where the logfile resides.
Here's something that could help you.
Besides that, here's a Gist which could give another perspective regarding logging configuration for Flask applications.
I'm building a python application with also a web interface, with Flask web framework.
It runs on Flask internal server in debug/dev mode and in production mode it runs on tornado as wsgi container.
This is how i've set up my logger:
log_formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
file_handler = logging.handlers.RotatingFileHandler(LOG_FILE, maxBytes=5 * 1024 * 1024, backupCount=10)
file_handler.setFormatter(log_formatter)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setFormatter(log_formatter)
log = logging.getLogger('myAppLogger')
log.addHandler(file_handler)
log.addHandler(console_handler)
To add my logger to the Flask app i tried this:
app = Flask('system.web.server')
app.logger_name = 'myAppLogger'
But the log still going to the Flask default log handler, and in addition, I didn't found how to customize the log handlers also for the Tornado web server.
Any help is much appreciated,
thanks in advance
AFAIK, you can't change the default logger in Flask. You can, however, add your handlers to the default logger:
app = Flask('system.web.server')
app.logger.addHandler(file_handler)
app.logger.addHandler(console_handler)
Regarding my comment above - "Why would you want to run Flask in tornado ...", ignore that. If you are not seeing any performance hit, then clearly there's no need to change your setup.
If, however, in future you'd like to migrate to a multithreaded container, you can look into uwsgi or gunicorn.
I managed to do it, multiple handler, each doing their thing, so that ERROR log will not show on the INFO log as well and end up with duplicate info grrr:
app.py
import logging
from logging.handlers import RotatingFileHandler
app = Flask(__name__)
# Set format that both loggers will use:
formatter = logging.Formatter("[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s")
# Set logger A for known errors
log_handler = RotatingFileHandler('errors.log', maxBytes=10000, backupCount=1)
log_handler.setFormatter(formatter)
log_handler.setLevel(logging.INFO)
a = logging.getLogger('errors')
a.addHandler(log_handler)
# Set logger B for bad exceptions
exceptions_handler = RotatingFileHandler('exceptions.log', maxBytes=10000, backupCount=1)
exceptions_handler.setFormatter(formatter)
exceptions_handler.setLevel(logging.ERROR)
b = logging.getLogger('exceptions')
b.addHandler(exceptions_handler)
...
whatever_file_where_you_want_to_log.py
import logging
import traceback
# Will output known error messages to 'errors.log'
logging.getLogger('errors').error("Cannot connect to database, timeout")
# Will output the full stack trace to 'exceptions.log', when trouble hits the fan
logging.getLogger('exceptions').error(traceback.format_exc())
In my Celery application I am getting 2 types of logs on the console i.e celery application logs and task level logs (inside task I am using logger.INFO(str) syntax for logging)
I wanted to send both of them to a custom handler (in my case python-logstash handler )
For django logs I was successfull, by setting handler and logger in settings.py but I am helpless with celery
def initialize_logstash(logger=None,loglevel=logging.DEBUG, **kwargs):
# logger = logging.getLogger('celery')
handler = logstash.TCPLogstashHandler('localhost', 5959,tags=['worker'])
handler.setLevel(loglevel)
logger.addHandler(handler)
# logger.setLevel(logging.DEBUG)
return logger
from celery.signals import after_setup_task_logger
after_setup_task_logger.connect(initialize_logstash)
from celery.signals import after_setup_logger
after_setup_logger.connect(initialize_logstash)
using both after_setup_task_logger and after_setup_logger signals solved the problem
Celery provides a after_setup_logger signal that is triggered after Celery has set up the logger. Among other few arguments, the signal passes the logger object which you can add your custom logging handlers to.
from celery import signals
import logstash
import logging
#signals.after_setup_logger.connect
def setup_logstash_logger(logger, *args, **kwargs):
handler = logstash.TCPLogstashHandler('localhost', 5959)
# More logger/handler configuration
# handler.setLevel(logging.ERROR)
# ...
logger.addHandler(handler)
After fine-tuning Celery's logger you can simply rely on it to send the app messages to Logstash, even if you need to send your own messages:
logger = logging.getLogger(__name__)
logger.info('My message') # This message will also be sent to Logstash