Google StackDriver Logging on Flask App - Difference between default Flask logger? - python

I'm trying to see the difference between default flask logger and stackdriver logger in GAE's sample application: https://cloud.google.com/python/getting-started/using-pub-sub
Code without StackDriver logger:
def create_app(config, debug=False, testing=False, config_overrides=None):
app = Flask(__name__)
app.config.from_object(config)
app.debug = debug
app.testing = testing
if config_overrides:
app.config.update(config_overrides)
# Configure logging
if not app.testing:
logging.basicConfig(level=logging.INFO)
Code with StackDriver logger:
def create_app(config, debug=False, testing=False, config_overrides=None):
app = Flask(__name__)
app.config.from_object(config)
app.debug = debug
app.testing = testing
if config_overrides:
app.config.update(config_overrides)
# [START setup_logging]
if not app.testing:
client = google.cloud.logging.Client(app.config['PROJECT_ID'])
# Attaches a Google Stackdriver logging handler to the root logger
client.setup_logging(logging.INFO)
There's some difference with the StackDriver code where a logger was imported from google cloud. However, the output of the logs seems similar:
Output Log without StackDriver:
Output Log with StackDriver:
These logs does not look that different with or without a StackDriver.
When I go to the StackDriver logs I get redirected to the default logs in GAE. Is there anything special with StackDriver loggers that the normal flask logger cannot do?

Taking a look into the the two function that you are using for logger configuration: Basicconfig and Setup.logging, your loggers have similar settings so it make sense to me to have similar log output.
I didn't understand what you expected to see in Stackdriver Logging Viewer since the two pictures that you attached looks right to me since them are normal Log entry for Stackdriver Logging. Notice that, by default, App Engine logging is provided by Stackdriver Logging as explained in this document
The advantage of Stackdriver Logging is to have a better management of the logs and the possibility to analyze them. You can have a look in this tutorial in order to have an idea about it.

Related

How can I make the python logging library print logs in console while running Flask?

This is the configuration I have right now
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.DEBUG)
logger = logging.getLogger(__name__)
logger.addHandler(AzureLogHandler(
connection_string='InstrumentationKey='+app_insights_id)
)
#app.route('/', methods = ['GET','POST'])
def main_page():
logger.debug('Hello Flask, on Azure App Service for Linux!') <--- want this to be printed on console
return 'Hello Flask, on Azure App Service for Linux!'
I am pretty sure this is possible since it was working for me until a couple of days back and after some change I cannot trace back, all logs stopped printing to console
I have tried setting the environment variable FLASK_ENV to development. That didn't work either.
According to this answer, we need to get logger from werkzeug, is there any way apart from this to get logs in console after starting flask?

Python Flask Logging Problems

I am about to write a flask app for something very trivial .. and I hit a roadblock in my logging practices.
This is my simple flask app, I wrote this to explain the problem I ran into and have been stuck at for sometime trying to figure out what is happening with python-logging & flask.
# standard
from flask import Flask
from flask_restful import Api
import logging
import json
# logging config
log_fmt = "%(asctime)s %(levelname)s %(process)d %(filename)s %(funcName)s %(message)s"
logging.basicConfig(
filename="test.log",
filemode="w",
format=log_fmt,
level=logging.DEBUG
)
# create an object of flask (flask is a web framework)
app = Flask(__name__)
api = Api(app)
# health check /
#app.route("/", methods=['GET'])
def default():
logging.debug("/ request received")
out_dict = {
"hello": "world"
}
logging.debug("/ response" + str(out_dict))
return json.dumps(out_dict)
# main function, entry point
if __name__ == "__main__":
# invokes src and runs the application
logging.debug("starting")
# COMMENTING below - gets me the log file! Whats happening with flask & logging?
app.run(host="0.0.0.0", port=7001, debug=True)
logging.debug("stopping")
Now this is the pattern I generally adopt when I need logging. But when I apply this pattern of logging along with app.run(..) the log-file never gets created. I am unable to figure out why this happens.
But on the contrary .. if I comment the app.run(..), the log file gets created with corresponding debug logs I have in place.
I have been struggling to understand this - and I did land on the flask inbuilt log handler, but looking at its implementation it attaches to logging module itself. So the whole thing is still not making sense. Any help or direction here will be appreciated.
Using logging.basicConfig makes a number of assumptions which upon calling app.run(...) may have undone, as Flask does also make use of the logging module to set up logging output as you noted. However, if you manually set up a file handler and attach it to the root logger like so (i.e. replace the # logging config section with):
# logging config
log_fmt = "%(asctime)s %(levelname)s %(process)d %(filename)s %(funcName)s %(message)s"
handler = logging.FileHandler('test.log')
handler.setFormatter(logging.Formatter(log_fmt))
root_logger = logging.getLogger()
root_logger.addHandler(handler)
root_logger.setLevel(logging.DEBUG)
This sets up the logging handler with the formatter set to the log_fmt you had specified, then attaching that handler to the root logger returned by logging.getLogger(). Running the application, give it some requests and quitting, you should see appropriate entries showing up inside test.log in the current working directory, while some of the typical logging output produced by flask will also be shown.

Google Stackdriver not showing struct entry log as expected from Google Kubernetes Engine

I am using Stackdriver Logging for Python and a Python logger at the same time. I am using the function google.cloud.logging.logger.log_struct (https://gcloud-python.readthedocs.io/en/latest/logging/logger.html) to log a JSON to the Stackdriver.
I am able to view the logs as expected in the log viewer with the selected resource Global when I am running my script using a Google Compute Engine VM instance. The struct I am logging is recorded properly in jsonPayload.
However, when the logging is coming from a Google Kubernetes Engine, the logger view does not show the structs I passed, but rather what is printed on stdout. How do I make sure I observe the same behaviour from the Google Compute Engine VM instance and a Google Kubernetes Engine?
This is a snippet showing how I am doing the logging:
import google.cloud.logging
import logging
logging_client = google.cloud.logging.Client()
# connects the logger to the root logging handler
cloud_logger = logging_client.logger('email_adaptor')
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(message)s - %(lineno)d - %(filename)s')
# get the logger a name
logger = logging.getLogger('email_adaptor')
# set a level for the logger
logger.setLevel(logging.DEBUG)
# make the logger write on stdout
stdout_alarm_log = logging.StreamHandler(sys.stdout)
stdout_alarm_log.setFormatter(formatter)
logger.addHandler(stdout_alarm_log)
struct = {'message':'Processed Alarm', 'valid': True}
cloud_logger.log_struct(struct, severity='INFO')
logger.info(str(struct))
This is an example of what I get on the STDOUT on both the VM instance and the Kubernetes Engine:
2018-10-26 12:30:20,008 - INFO - Processed Alarm {'valid': True} - 79 - log.py
INFO:email_adaptor:Processed Alarm {'valid': True}
This is what I see under the resource Global in the Google Log Viewer (the logs are ones from my VM instance, and do not correspond to the example I gave in the snippet code):
This is what I see under the resource Google Kubernetes Engine: The struct do not show, instead I see what is printed on STDOUT:
The Stackdriver library calls in the code write against “global” and the structured log entries from your GKE containers will show under “Global” resource.
The “GKE Container” resource will show logging written to stdout and stderr, which are ingested by default.
To write structured logs to stdout/stderr and access them in Stackdriver, the only structured format that the logging agent will accept is JSON. You must configure your logger to output JSON for it to be picked up as a structured log entry. More information at https://cloud.google.com/logging/docs/agent/configuration#process-payload .

Logging to separate files in Python

I'm using python's logging module. I've initialized it as:
import logging
logger = logging.getLogger(__name__)
in every of my modules. Then, in the main file:
logging.basicConfig(level=logging.INFO,filename="log.txt")
Now, in the app I'm also using WSGIServer from gevent. The initializer takes a log argument where I can add a logger instance. Since this is an HTTP Server it's very verbose.
I would like to log all of my app's regular logs to "log.txt" and WSGIServer's logs to "http-log.txt".
I tried this:
logging.basicConfig(level=logging.INFO,filename="log.txt")
logger = logging.getLogger(__name__)
httpLogger = logging.getLogger("HTTP")
httpLogger.addHandler(logging.FileHandler("http-log.txt"))
httpLogger.addFilter(logging.Filter("HTTP"))
http_server = WSGIServer(('0.0.0.0', int(config['ApiPort'])), app, log=httpLogger)
This logs all HTTP messages into http-log.txt, but also to the main logger.
How can I send all but HTTP messages to the default logger (log.txt), and HTTP messages only to http-log.txt?
EDIT: Since people are quickly jumping to point that this Logging to two files with different settings has an answer, plese read the linked answer and you'll see they don't use basicConfig but rather initialize each logger separately. This is not how I'm using the logging module.
Add the following line to disable propagation:
httpLogger.propagate = False
Then, it will no longer propagate messages to its ancestors' handlers which includes the root logger for which you have set up the general log file.

Formatting Flask app logs in json

I'm working with a Python/Flask application and trying to get the logs to be formatted (by line) in json.
Using the python-json-logger package, I've modified the formatter for the app.logger as follows:
from pythonjsonlogger import jsonlogger
formatter = jsonlogger.JsonFormatter(
'%(asctime) %(levelname) %(module) %(funcName) %(lineno) %(message)')
app.logger.handlers[0].setFormatter(formatter)
This works as expected. Any messages passed to app.logger are correctly formatted in json.
However the application is also automatically logging all requests. This information shows up in stdout as follows:
127.0.0.1 - - [19/Jun/2015 12:22:03] "GET /portal/ HTTP/1.1" 200 -
I want this information to be formatted in json as well. I've been looking for the logger/code that is responsible for creating this output with no success.
Where is this output generated?
Are the mechanisms to change the formatting of this logged information?
When you use the app.logger attribute for the first time, Flask sets up some log handlers:
a debug logger that is set to log level DEBUG and that filters on app.debug being true.
a production logger that is set to ERROR.
You can remove those handlers again yourself, by running:
app.logger.handlers[:] = []
However, the log line you see is not logged by Flask, but by the WSGI server. Both the built-in Werkzeug server (used when you use app.run()) and various other WSGI servers do this. Gunicorn for example uses the default Python logger to record access.
The built-in WSGI server should never be used in production (it won't scale well, and is not battle-hardened against malicious attackers).
For Gunicorn, you can disable log propagation to keep the logging separate:
logging.getLogger('gunicorn').propagate = False
Flask internally uses werkzeug server. Those logs are printed by it, not flask. You can access to werkzeug logger with logging.getLogger('werkzeug') and configure as you wish. For example:
werkzeug = logging.getLogger('werkzeug')
if len(werkzeug.handlers) == 1:
formatter = logging.Formatter('%(message)s', '%Y-%m-%d %H:%M:%S')
werkzeug.handlers[0].setFormatter(formatter)

Categories

Resources