python logging.critical() to raise exception and dump stacktrace and die - python-3.x

I'm porting some code from perl (log4perl) and java (slf4j). All is fine except for logging.critical() does not dump stacktrace and die like it does in the other frameworks, need to add a lot of extra code, logger.exception() also only writes error.
Today I do:
try:
errmsg = "--id={} not found on --host={}".format(args.siteid, args.host)
raise GX8Exception(errmsg)
except GX8Exception as e:
log.exception(e)
sys.exit(-1)
This produces:
2018-01-10 10:09:56,814 [ERROR ] root --id=7A4A7845-7559-4F89-B678-8ADFECF5F7C3 not found on --host=welfare-qa
Traceback (most recent call last):
File "./gx8-controller.py", line 85, in <module>
raise GX8Exception(errmsg)
GX8Exception: --id=7A4A7845-7559-4F89-B678-8ADFECF5F7C3 not found on --host=welfare-qa
Is there a way to config pythonmodule logger to do this, or any other framework to do the same:
log.critical("--id={} not found on --host={}".format(args.siteid, args.host))

One approach would be to create a custom Handler that does nothing but pass log messages on to its super and then exit if the log level is high enough:
import logging
class ExitOnExceptionHandler(logging.StreamHandler):
def emit(self, record):
super().emit(record)
if record.levelno in (logging.ERROR, logging.CRITICAL):
raise SystemExit(-1)
logging.basicConfig(handlers=[ExitOnExceptionHandler()], level=logging.DEBUG)
logger = logging.getLogger('MYTHING')
def causeAProblem():
try:
raise ValueError("Oh no!")
except Exception as e:
logger.exception(e)
logger.warning('Going to try something risky...')
causeAProblem()
print("This won't get printed")
Output:
rat#pandion:~$ python test.py
ERROR:root:Oh no!
Traceback (most recent call last):
File "test.py", line 14, in causeAProblem
raise ValueError("Oh no!")
ValueError: Oh no!
rat#pandion:~$ echo $?
255
However, this could cause unexpected behavior for users of your code. It would be much more straightfoward, if you want to log an exception and exit, to simply leave the exception uncaught. If you want to log a traceback and exit wherever the code is currently calling logging.critical, change it to raise an exception instead.

I inherited some code where I could not change the handler class. I resorted to run time patching of the handler which is a variation on the solution by #nathan-vērzemnieks:
import types
def patch_logging_handler(logger):
def custom_emit(self, record):
self.orig_emit(record)
if record.levelno == logging.FATAL:
raise SystemExit(-1)
handler = logger.handlers[0]
setattr(handler, 'orig_emit', handler.emit)
setattr(handler, 'emit', types.MethodType(custom_emit, handler))

Nathans anwser is great! Been looking for this for a long time,
will just add that you can also do:
if record.levelno >= logging.ERROR:
instead of
if record.levelno in (logging.ERROR, logging.CRITICAL):
to set the minimum level that would cause an exit.

Related

How to use bulk_load in apache airflow

I have apache airflow 2.1.4 and postgres database.
I need to insert multiple rows at a time. So I am going to use bulk_load method of PostgresHook but get error everytime.
data = pd.read_csv(open(filepath, 'rb'))
buffer = StringIO()
buffer.write(data.to_csv(index=None, header=None, sep='\t'))
buffer.seek(0)
schema_table = 'schema.table'
with PostgresHook(postgres_conn_id='my_pg_database'):
PostgresHook.bulk_load(table=schema_table, tmp_file=buffer)
The error I get:
Traceback (most recent call last):
File "/home/airflow/dags/my_python_file.py", line 76, in <module>
my_func(filepath=my_file, target_schema=schema, target_table=table)
File "/home/airflow/dags/my_python_file.py", line 39, in my_func
with PostgresHook(postgres_conn_id='my_pg_database'):
AttributeError: __enter__
I couldn't even find some examples of bulk_load usage. Would appriciate any clue. Thank you.
Postgres Hook (and any other hooks really) are not "context managers". You cannot use with: to use them.
Something like that should work:
postgres_hook = PostgresHook(postgres_conn_id='my_pg_database')
postgres_hook.bulk_load(...)

Strange issue with Python recursive function in Chalice framework

I have defined this SNS-triggered Lambda in Chalice:
#app.on_sns_message(topic='arn:aws:sns:us-west-1:XXXXXXXX:MyTopic')
def step1_photo_url_preload(event, retry = 3):
try:
js = json.loads(event.message)
... some logic here, event object is never modified ...
except:
if retry:
print("WARNING: failed, %d retries remaining" % retry)
return step1_photo_url_preload(event, retry-1)
else:
raise
When an exception is raised, the function should retry up to 3 times.
Instead, what I get is the exception below. Look closely at the trace: Line 56 shows the error occurs when attempting the recursive call:
[ERROR] TypeError: 'SNSEvent' object is not subscriptable
Traceback (most recent call last):
File "/var/task/chalice/app.py", line 1459, in __call__
return self.func(event_obj)
File "/var/task/app.py", line 56, in step1_photo_url_preload
return step1_photo_url_preload(event, retry-1)
File "/var/task/chalice/app.py", line 1458, in __call__
event_obj = self.event_class(event, context)
File "/var/task/chalice/app.py", line 1486, in __init__
self._extract_attributes(event_dict)
File "/var/task/chalice/app.py", line 1532, in _extract_attributes
first_record = event_dict['Records'][0]
Mysteriously, the function can't work with the event object that it received the first time.
What could cause this?
I suspect this might have something to do with the magic behind #app.on_sns_message, but I'm not sure where to look next.
The problem is the fact that the function is decorated, the failure is in the code the decorator is running. Pull the functionality you want to run recursively into a separate function and the problem should go away.

How to handle connection issues with kafka using the python kafka library?

I have a serverless function that's trying to send some data to kafka. Sometimes it works and sometimes the connection just drop and the data is lost.
The reason for this is that the library for kafka is not raising the exception but it's adding error logs instead. So i cannot add my piece of code in a try:except.
Here is the error that i am often getting in my logs:
<BrokerConnection node_id=11 host=... port=9092>: Error receiving network data closing socket
Traceback (most recent call last):
File "/var/task/kafka/conn.py", line 745, in _recv
data = self._sock.recv(SOCK_CHUNK_BYTES)
ConnectionResetError: [Errno 104] Connection reset by peer
and the function _recv mentioned above has definition as follows:
I am still looking for a solution but adding the code within try:except doesn't work.
def _recv(self):
responses = []
SOCK_CHUNK_BYTES = 4096
while True:
try:
data = self._sock.recv(SOCK_CHUNK_BYTES)
# We expect socket.recv to raise an exception if there is not
# enough data to read the full bytes_to_read
# but if the socket is disconnected, we will get empty data
# without an exception raised
if not data:
log.error('%s: socket disconnected', self)
self.close(error=Errors.ConnectionError('socket disconnected'))
break
else:
responses.extend(self.receive_bytes(data))
if len(data) < SOCK_CHUNK_BYTES:
break
except SSLWantReadError:
break
except ConnectionError as e:
if six.PY2 and e.errno == errno.EWOULDBLOCK:
break
log.exception('%s: Error receiving network data'
' closing socket', self)
self.close(error=Errors.ConnectionError(e))
break
except BlockingIOError:
if six.PY3:
break
raise
return responses
I expect when the connection is lost, an exception is raised and the application breaks but instead only there are only error logs and the application continues even if the kafka part fails.

Print traceback "ending" on the assert-line, in Python3 unittest addFailure

I have made a simple, custom TestResult class (not inheriting from anything). When my python unittest fails, addFailure(self, test, err) is called as expected.
err[2] contains a traceback
I now print the traceback with this command: traceback.print_tb(err[2])
The print out contains two more levels than expected/desired.
File "/usr/lib64/python3.4/unittest/case.py", line 58, in testPartExecutor
yield
File "/usr/lib64/python3.4/unittest/case.py", line 580, in run
testMethod()
File "/home/xplatformer/code/tools/python/exception_test/my_test.py", line 23, in test_my4
self.assertEqual(5,4)
File "/usr/lib64/python3.4/unittest/case.py", line 800, in assertEqual
assertion_func(first, second, msg=msg)
File "/usr/lib64/python3.4/unittest/case.py", line 793, in _baseAssertEqual
raise self.failureException(msg)
How can I get the traceback to "end" at the assertEqual (line 23 in my_test.py)
Similarly, when extracting the filename like this: err[2].tb_frame.f_code.co_filename, I get case.py and not my_test.py as expected/desired.
How can I get the filename where the assertion occurred?
from the log it is pretty clear that, there is self.assertEqual(5,4) which fails the test case on line 23 in the method test_my4 in file /home/xplatformer/code/tools/python/exception_test/my_test.py
change the self.assertEqual(5,5) will pass the test case.

Python 3 script using libnotify fails as cron job

I've got a Python 3 script that gets some JSON from a URL, processes it, and notifies me if there's any significant changes to the data I get. I've tried using notify2 and PyGObject's libnotify bindings (gi.repository.Notify) and get similar results with either method. This script works a-ok when I run it from a terminal, but chokes when cron tries to run it.
import notify2
from gi.repository import Notify
def notify_pygobject(new_stuff):
Notify.init('My App')
notify_str = '\n'.join(new_stuff)
print(notify_str)
popup = Notify.Notification.new('Hey! Listen!', notify_str,
'dialog-information')
popup.show()
def notify_notify2(new_stuff):
notify2.init('My App')
notify_str = '\n'.join(new_stuff)
print(notify_str)
popup = notify2.Notification('Hey! Listen!', notify_str,
'dialog-information')
popup.show()
Now, if I create a script that calls notify_pygobject with a list of strings, cron throws this error back at me via the mail spool:
Traceback (most recent call last):
File "/home/p0lar_bear/Documents/devel/notify-test/test1.py", line 3, in <module>
main()
File "/home/p0lar_bear/Documents/devel/notify-test/test1.py", line 4, in main
testlib.notify(notify_projects)
File "/home/p0lar_bear/Documents/devel/notify-test/testlib.py", line 8, in notify
popup.show()
File "/usr/lib/python3/dist-packages/gi/types.py", line 113, in function
return info.invoke(*args, **kwargs)
gi._glib.GError: Error spawning command line `dbus-launch --autolaunch=776643a88e264621544719c3519b8310 --binary-syntax --close-stderr': Child process exited with code 1
...and if I change it to call notify_notify2() instead:
Traceback (most recent call last):
File "/home/p0lar_bear/Documents/devel/notify-test/test2.py", line 3, in <module>
main()
File "/home/p0lar_bear/Documents/devel/notify-test/test2.py", line 4, in main
testlib.notify(notify_projects)
File "/home/p0lar_bear/Documents/devel/notify-test/testlib.py", line 13, in notify
notify2.init('My App')
File "/usr/lib/python3/dist-packages/notify2.py", line 93, in init
bus = dbus.SessionBus(mainloop=mainloop)
File "/usr/lib/python3/dist-packages/dbus/_dbus.py", line 211, in __new__
mainloop=mainloop)
File "/usr/lib/python3/dist-packages/dbus/_dbus.py", line 100, in __new__
bus = BusConnection.__new__(subclass, bus_type, mainloop=mainloop)
File "/usr/lib/python3/dist-packages/dbus/bus.py", line 122, in __new__
bus = cls._new_for_bus(address_or_type, mainloop=mainloop)
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.NotSupported: Unable to autolaunch a dbus-daemon without a $DISPLAY for X11
I did some research and saw suggestions to put a PATH= into my crontab, or to export $DISPLAY (I did this within the script by calling os.system('export DISPLAY=:0')) but neither resulted in any change...
You are in the right track. This behavior is because cron is run in a multiuser headless environment (think of it as running as root in a terminal without GUI, kinda), so he doesn't know to what display (X Window Server session) and user target to. If your application open, for example, windows or notification to some user desktop, then this problems is raised.
I suppose you edit your cron with crontab -e and the entry looks like this:
m h dom mon dow command
Something like:
0 5 * * 1 /usr/bin/python /home/foo/myscript.py
Note that I use full path to Python, is better if this kind of situation where PATH environment variable could be different.
Then just change to:
0 5 * * 1 export DISPLAY=:0 && /usr/bin/python /home/foo/myscript.py
If this still doesn't work you need to allow your user to control the X Windows server:
Add to your .bash_rc:
xhost +si:localuser:$(whoami)
If you want to set the DISPLAY from within python like you attempted with os.system('export DISPLAY=:0'), you can do something like this
import os
if not 'DISPLAY' in os.environ:
os.environ['DISPLAY'] = ':0'
This will respect any DISPLAY that users may have on a multi-seat box, and fall back to the main head :0.
If ur notify function, regardless of Python version or notify library, does not track the notify id [in a Python list] and deleting the oldest before the queue is completely full or on error, then depending on the dbus settings (in Ubuntu it's 21 notification max) dbus will throw an error, maximum notifications reached!
from gi.repository import Notify
from gi.repository.GLib import GError
# Normally implemented as class variables.
DBUS_NOTIFICATION_MAX = 21
lstNotify = []
def notify_show(strSummary, strBody, strIcon="dialog-information"):
try:
# full queue, delete oldest
if len(lstNotify)==DBUS_NOTIFICATION_MAX:
#Get oldest id
lngOldID = lstNotify.pop(0)
Notify.Notification.clear(lngOldID)
del lngOldID
if len(lstNotify)==0:
lngLastID = 0
else:
lngLastID = lstNotify[len(lstNotify) -1] + 1
lstNotify.append(lngLastID)
notify = Notify.Notification.new(strSummary, strBody, strIcon)
notify.set_property('id', lngLastID)
print("notify_show id %(id)d " % {'id': notify.props.id} )
#notify.set_urgency(Notify.URGENCY_LOW)
notify.show()
except GError as e:
# Most likely exceeded max notifications
print("notify_show error ", e )
finally:
if notify is not None:
del notify
Although it may somehow be possible to ask dbus what the notification queue max limit is. Maybe someone can help ... Improve this until perfection.
Plz
Cuz gi.repository complete answers are spare to come by.

Resources