Repository: python-telegram-bot/python-telegram-bot

Issue:
Log an INFO-level message when SIGINT received
Hi ;)

I'm recently experimenting with your nice bot. However, I stumbled upon this during my "learning-process":

I've created a bot, it worked normally. I just did `updater.start_polling` like in the examples, but without using `updater.idle()` (I thought first `start_polling` is blocking).  Exiting with CTRL+C worked immediately, but has thrown a `KeyboardInterrupt`.
After a while I found the `idle()` function, but I was actually a bit confused of CTRL+C's behaviour, because I've configured logging with a minimum level of `INFO`. So I've seen nothing when I pressed the keys. The bot actually needs a while to shutdown, and because nothing was logged I assumed the SIGINT signal wasn't received (/ assuming due to internal threading stuff not properly processed), so I pressed always CTRL+C two times causing a SIGTERM which worked immediately.
SIGINT notifications are only visible when enabling `DEBUG`-level logging. I would suggest to add an `INFO` message, that tells something like `Received SIGINT, shutting down...`, so nobody is worried ;)


Discussion:
@Makman2 To make it clear, this is all intended behavior. I kind of see your point, but it's probably not something that the library should spam into log files for users that collect them for later (if I'm making any sense). The issue is debatable, but since it's an event that clearly belongs to the "debugging" stage of application development, it makes sense to have it displayed only when debug logging is enabled, which every user should do at least once in his contact with ptb, since this offers many other clues to the internals of ptb that would fall into the same category of what the library does under the hood.
> it's an event that clearly belongs to the "debugging" stage of application development

This is the thing where I'm not so sure ;) It's kind of right that you usually keep your bot alive forever once it's in production state, however even then you sometimes have to shut it down (and I'm not sure if a production state bot should run in DEBUG mode). An indication might just help seeing that everything went fine.
Also a SIGTERM signal gets displayed as a `WARNING`, so not sure if it's better to display SIGINT for sake of consistency (but not as a warning).

> The issue is debatable

Indeed :) Personally I would like to receive an info, that's why I try to find arguments for it :)
Hi,

I tend to lean towards making this a `INFO`-level message. It's not really a spammy message (only occurs on the SIGINT signal) and alerts the user that the signal is received and the bot is closing down.
@jh0ker @tsnoam @bomjacob thoughts?
@Eldinnie I Agree
@jh0ker I agree too
You got my blessing. @Makman2 would you be willing to make a PR? :)
I'll try, currently Christmas and stuff, and Google Code-In still running on [my main open source project](https://github.com/coala/coala) :) But shouldn't be too hard I guess ;)

Resolving patch:
diff --git a/telegram/ext/updater.py b/telegram/ext/updater.py
--- a/telegram/ext/updater.py
+++ b/telegram/ext/updater.py
@@ -31,6 +31,7 @@
 from telegram import Bot, TelegramError
 from telegram.ext import Dispatcher, JobQueue
 from telegram.error import Unauthorized, InvalidToken, RetryAfter
+from telegram.utils.helpers import get_signal_name
 from telegram.utils.request import Request
 from telegram.utils.webhookhandler import (WebhookServer, WebhookHandler)
 
@@ -449,6 +450,8 @@ def _join_threads(self):
     def signal_handler(self, signum, frame):
         self.is_idle = False
         if self.running:
+            self.logger.info('Received signal {} ({}), stopping...'.format(
+                signum, get_signal_name(signum)))
             self.stop()
             if self.user_sig_handler:
                 self.user_sig_handler(signum, frame)
diff --git a/telegram/utils/helpers.py b/telegram/utils/helpers.py
--- a/telegram/utils/helpers.py
+++ b/telegram/utils/helpers.py
@@ -19,6 +19,7 @@
 """This module contains helper functions."""
 
 import re
+import signal
 from datetime import datetime
 
 try:
@@ -26,6 +27,18 @@
 except ImportError:
     from cgi import escape as escape_html  # noqa: F401
 
+
+# From https://stackoverflow.com/questions/2549939/get-signal-names-from-numbers-in-python
+_signames = {v: k
+             for k, v in reversed(sorted(vars(signal).items()))
+             if k.startswith('SIG') and not k.startswith('SIG_')}
+
+
+def get_signal_name(signum):
+    """Returns the signal name of the given signal number."""
+    return _signames[signum]
+
+
 # Not using future.backports.datetime here as datetime value might be an input from the user,
 # making every isinstace() call more delicate. So we just use our own compat layer.
 if hasattr(datetime, 'timestamp'):


Test patch:
diff --git a/tests/test_updater.py b/tests/test_updater.py
--- a/tests/test_updater.py
+++ b/tests/test_updater.py
@@ -16,9 +16,11 @@
 #
 # You should have received a copy of the GNU Lesser Public License
 # along with this program.  If not, see [http://www.gnu.org/licenses/].
+import logging
 import os
 import signal
 import sys
+from functools import partial
 from queue import Queue
 from random import randrange
 from threading import Thread
@@ -238,15 +240,25 @@ def _send_webhook_msg(self,
 
         return urlopen(req)
 
-    def signal_sender(self):
+    def signal_sender(self, updater):
         sleep(0.2)
+        while not updater.running:
+            sleep(0.2)
+
         os.kill(os.getpid(), signal.SIGTERM)
 
     @signalskip
-    def test_idle(self, updater):
+    def test_idle(self, updater, caplog):
         updater.start_polling(0.01)
-        Thread(target=self.signal_sender).start()
-        updater.idle()
+        Thread(target=partial(self.signal_sender, updater=updater)).start()
+
+        with caplog.at_level(logging.INFO):
+            updater.idle()
+
+        rec = caplog.records[-1]
+        assert rec.msg.startswith('Received signal {}'.format(signal.SIGTERM))
+        assert rec.levelname == 'INFO'
+
         # If we get this far, idle() ran through
         sleep(.5)
         assert updater.running is False
@@ -260,7 +272,7 @@ def user_signal_inc(signum, frame):
 
         updater.user_sig_handler = user_signal_inc
         updater.start_polling(0.01)
-        Thread(target=self.signal_sender).start()
+        Thread(target=partial(self.signal_sender, updater=updater)).start()
         updater.idle()
         # If we get this far, idle() ran through
         sleep(.5)