diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..91041ed
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+name: "CodeQL"
+
+on:
+  push:
+    branches: [master]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [master]
+  schedule:
+    - cron: '0 5 * * 6'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        # Override automatic language detection by changing the below list
+        # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
+        language: ['python']
+        # Learn more...
+        # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+      with:
+        # We must fetch at least the immediate parents so that if this is
+        # a pull request then we can checkout the head.
+        fetch-depth: 2
+
+    # If this run was triggered by a pull request event, then checkout
+    # the head of the pull request instead of the merge commit.
+    - run: git checkout HEAD^2
+      if: ${{ github.event_name == 'pull_request' }}
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file. 
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v1
+
+    # ℹī¸ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1
diff --git a/.travis.yml b/.travis.yml
index f3f043a..ea1ef12 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,6 +5,7 @@ python:
   - "3.6"
   - "3.7"
   - "3.8"
+  - "3.9"
 matrix:
   include:
   - python: "3.5"
@@ -15,6 +16,8 @@ matrix:
     env: PYTHONASYNCIODEBUG=x
   - python: "3.8"
     env: PYTHONASYNCIODEBUG=x
+  - python: "3.9"
+    env: PYTHONASYNCIODEBUG=x
 
 before_install:
   - pip install poetry more-itertools
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4432a3b..19fea8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -142,7 +142,22 @@
 
 - Don't include tests and changelog in distribution
 
-## [v1.10.0] 2019-12-7
+## [v1.10.0] 2019-12-07
 ### Changed
 
 - Allow sync decorator call from async function
+
+## [v1.11.0] 2021-07-12
+### Changed
+
+- Configurable logging levels for backoff and giveup events
+- Minor documentation fixes
+
+### NOTE
+
+THIS WILL BE THE FINAL PYTHON 2.7 COMPATIBLE RELEASE.
+
+## [v1.11.1] 2021-07-14
+### Changed
+
+- Update __version__ in backoff module
diff --git a/Makefile b/Makefile
index 9daaf0b..6357044 100644
--- a/Makefile
+++ b/Makefile
@@ -14,9 +14,9 @@ all:
 
 flake8:
 ifeq ($(PY_GTE_35),1)
-	@flake8 backoff tests
+	@flake8 --ignore=E741,W503,W504 backoff tests
 else
-	@flake8 --exclude tests/python35,backoff/_async.py backoff tests
+	@flake8 --ignore=E741,W503,W504 --exclude tests/python35,backoff/_async.py backoff tests
 endif
 
 clean:
diff --git a/README.rst b/README.rst
index 30ddc32..cfd382d 100644
--- a/README.rst
+++ b/README.rst
@@ -229,7 +229,7 @@ implemented like so:
 .. code-block:: python
 
     def backoff_hdlr(details):
-        print ("Backing off {wait:0.1f} seconds afters {tries} tries "
+        print ("Backing off {wait:0.1f} seconds after {tries} tries "
                "calling function {target} with args {args} and kwargs "
                "{kwargs}".format(**details))
 
@@ -280,7 +280,7 @@ asynchronous HTTP client/server library.
 
     @backoff.on_exception(backoff.expo, aiohttp.ClientError, max_time=60)
     async def get_url(url):
-        async with aiohttp.ClientSession() as session:
+        async with aiohttp.ClientSession(raise_for_status=True) as session:
             async with session.get(url) as response:
                 return await response.text()
 
@@ -312,7 +312,7 @@ looked up by name.
 .. code-block:: python
 
    @backoff.on_exception(backoff.expo,
-                         requests.exception.RequestException,
+                         requests.exceptions.RequestException,
 			 logger='my_logger')
    # ...
 
@@ -323,12 +323,12 @@ directly.
 
     my_logger = logging.getLogger('my_logger')
     my_handler = logging.StreamHandler()
-    my_logger.add_handler(my_handler)
+    my_logger.addHandler(my_handler)
     my_logger.setLevel(logging.ERROR)
 
     @backoff.on_exception(backoff.expo,
-                         requests.exception.RequestException,
-			 logger=my_logger)
+                          requests.exceptions.RequestException,
+			  logger=my_logger)
     # ...
 
 Default logging can be disabled all together by specifying
diff --git a/backoff/__init__.py b/backoff/__init__.py
index fc00001..52a6287 100644
--- a/backoff/__init__.py
+++ b/backoff/__init__.py
@@ -12,6 +12,9 @@ polling resources for externally generated content.
 For examples and full documentation see the README at
 https://github.com/litl/backoff
 """
+import sys
+import warnings
+
 from backoff._decorator import on_predicate, on_exception
 from backoff._jitter import full_jitter, random_jitter
 from backoff._wait_gen import constant, expo, fibo
@@ -23,7 +26,15 @@ __all__ = [
     'expo',
     'fibo',
     'full_jitter',
-    'random_jitter'
+    'random_jitter',
 ]
 
-__version__ = '1.10.0'
+__version__ = '1.11.1'
+
+
+if sys.version_info[0] < 3:
+    warnings.warn(
+        "Python 2.7 support is deprecated and will be dropped "
+        "in the next release",
+        DeprecationWarning,
+    )  # pragma: no cover
diff --git a/backoff/_common.py b/backoff/_common.py
index efd13f1..b76e579 100644
--- a/backoff/_common.py
+++ b/backoff/_common.py
@@ -7,6 +7,13 @@ import traceback
 import warnings
 
 
+# python 2.7 -> 3.x compatibility for str and unicode
+try:
+    basestring
+except NameError:  # pragma: python=3.5
+    basestring = str
+
+
 # Use module-specific logger with a default null handler.
 _logger = logging.getLogger('backoff')
 _logger.addHandler(logging.NullHandler())  # pragma: no cover
@@ -41,20 +48,31 @@ def _next_wait(wait, jitter, elapsed, max_time):
 
         seconds = value + jitter()
 
-    # don't sleep longer than remaining alloted max_time
+    # don't sleep longer than remaining allotted max_time
     if max_time is not None:
         seconds = min(seconds, max_time - elapsed)
 
     return seconds
 
 
+def _prepare_logger(logger):
+    if isinstance(logger, basestring):
+        logger = logging.getLogger(logger)
+    return logger
+
+
 # Configure handler list with user specified handler and optionally
 # with a default handler bound to the specified logger.
-def _config_handlers(user_handlers, default_handler=None, logger=None):
+def _config_handlers(
+    user_handlers, default_handler=None, logger=None, log_level=None
+):
     handlers = []
     if logger is not None:
+        assert log_level is not None, "Log level is not specified"
         # bind the specified logger to the default log handler
-        log_handler = functools.partial(default_handler, logger=logger)
+        log_handler = functools.partial(
+            default_handler, logger=logger, log_level=log_level
+        )
         handlers.append(log_handler)
 
     if user_handlers is None:
@@ -73,7 +91,7 @@ def _config_handlers(user_handlers, default_handler=None, logger=None):
 
 
 # Default backoff handler
-def _log_backoff(details, logger):
+def _log_backoff(details, logger, log_level):
     msg = "Backing off %s(...) for %.1fs (%s)"
     log_args = [details['target'].__name__, details['wait']]
 
@@ -83,11 +101,11 @@ def _log_backoff(details, logger):
         log_args.append(exc_fmt.rstrip("\n"))
     else:
         log_args.append(details['value'])
-    logger.info(msg, *log_args)
+    logger.log(log_level, msg, *log_args)
 
 
 # Default giveup handler
-def _log_giveup(details, logger):
+def _log_giveup(details, logger, log_level):
     msg = "Giving up %s(...) after %d tries (%s)"
     log_args = [details['target'].__name__, details['tries']]
 
@@ -98,4 +116,4 @@ def _log_giveup(details, logger):
     else:
         log_args.append(details['value'])
 
-    logger.error(msg, *log_args)
+    logger.log(log_level, msg, *log_args)
diff --git a/backoff/_decorator.py b/backoff/_decorator.py
index e541904..bdfb173 100644
--- a/backoff/_decorator.py
+++ b/backoff/_decorator.py
@@ -5,18 +5,16 @@ import logging
 import operator
 import sys
 
-from backoff._common import (_config_handlers, _log_backoff, _log_giveup)
+from backoff._common import (
+    _prepare_logger,
+    _config_handlers,
+    _log_backoff,
+    _log_giveup
+)
 from backoff._jitter import full_jitter
 from backoff import _sync
 
 
-# python 2.7 -> 3.x compatibility for str and unicode
-try:
-    basestring
-except NameError:  # pragma: python=3.5
-    basestring = str
-
-
 def on_predicate(wait_gen,
                  predicate=operator.not_,
                  max_tries=None,
@@ -26,6 +24,8 @@ def on_predicate(wait_gen,
                  on_backoff=None,
                  on_giveup=None,
                  logger='backoff',
+                 backoff_log_level=logging.INFO,
+                 giveup_log_level=logging.ERROR,
                  **wait_gen_kwargs):
     """Returns decorator for backoff and retry triggered by predicate.
 
@@ -63,6 +63,8 @@ def on_predicate(wait_gen,
             about the invocation.
         logger: Name of logger or Logger object to log to. Defaults to
             'backoff'.
+        backoff_log_level: log level for the backoff event. Defaults to "INFO"
+        giveup_log_level: log level for the give up event. Defaults to "ERROR"
         **wait_gen_kwargs: Any additional keyword args specified will be
             passed to wait_gen when it is initialized.  Any callable
             args will first be evaluated and their return values passed.
@@ -70,12 +72,15 @@ def on_predicate(wait_gen,
     """
     def decorate(target):
         # change names because python 2.x doesn't have nonlocal
-        logger_ = logger
-        if isinstance(logger_, basestring):
-            logger_ = logging.getLogger(logger_)
+        logger_ = _prepare_logger(logger)
+
         on_success_ = _config_handlers(on_success)
-        on_backoff_ = _config_handlers(on_backoff, _log_backoff, logger_)
-        on_giveup_ = _config_handlers(on_giveup, _log_giveup, logger_)
+        on_backoff_ = _config_handlers(
+            on_backoff, _log_backoff, logger_, backoff_log_level
+        )
+        on_giveup_ = _config_handlers(
+            on_giveup, _log_giveup, logger_, giveup_log_level
+        )
 
         retry = None
         if sys.version_info >= (3, 5):  # pragma: python=3.5
@@ -107,6 +112,8 @@ def on_exception(wait_gen,
                  on_backoff=None,
                  on_giveup=None,
                  logger='backoff',
+                 backoff_log_level=logging.INFO,
+                 giveup_log_level=logging.ERROR,
                  **wait_gen_kwargs):
     """Returns decorator for backoff and retry triggered by exception.
 
@@ -117,7 +124,7 @@ def on_exception(wait_gen,
             backoff.
         max_tries: The maximum number of attempts to make before giving
             up. Once exhausted, the exception will be allowed to escape.
-            The default value of None means their is no limit to the
+            The default value of None means there is no limit to the
             number of tries. If a callable is passed, it will be
             evaluated at runtime and its return value used.
         max_time: The maximum total amount of time to try for before
@@ -144,6 +151,8 @@ def on_exception(wait_gen,
             is exceeded.  The parameter is a dict containing details
             about the invocation.
         logger: Name or Logger object to log to. Defaults to 'backoff'.
+        backoff_log_level: log level for the backoff event. Defaults to "INFO"
+        giveup_log_level: log level for the give up event. Defaults to "ERROR"
         **wait_gen_kwargs: Any additional keyword args specified will be
             passed to wait_gen when it is initialized.  Any callable
             args will first be evaluated and their return values passed.
@@ -151,12 +160,15 @@ def on_exception(wait_gen,
     """
     def decorate(target):
         # change names because python 2.x doesn't have nonlocal
-        logger_ = logger
-        if isinstance(logger_, basestring):
-            logger_ = logging.getLogger(logger_)
+        logger_ = _prepare_logger(logger)
+
         on_success_ = _config_handlers(on_success)
-        on_backoff_ = _config_handlers(on_backoff, _log_backoff, logger_)
-        on_giveup_ = _config_handlers(on_giveup, _log_giveup, logger_)
+        on_backoff_ = _config_handlers(
+            on_backoff, _log_backoff, logger_, backoff_log_level
+        )
+        on_giveup_ = _config_handlers(
+            on_giveup, _log_giveup, logger_, giveup_log_level
+        )
 
         retry = None
         if sys.version_info[:2] >= (3, 5):   # pragma: python=3.5
diff --git a/backoff/_wait_gen.py b/backoff/_wait_gen.py
index 49dbbca..3cb0e11 100644
--- a/backoff/_wait_gen.py
+++ b/backoff/_wait_gen.py
@@ -8,7 +8,7 @@ def expo(base=2, factor=1, max_value=None):
 
     Args:
         base: The mathematical base of the exponentiation operation
-        factor: Factor to multiply the exponentation by.
+        factor: Factor to multiply the exponentiation by.
         max_value: The maximum value to yield. Once the value in the
              true exponential sequence exceeds this, the value
              of max_value will forever after be yielded.
diff --git a/debian/changelog b/debian/changelog
index 6ce55a3..25e0b41 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+backoff (1.11.1-0kali1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Kali Janitor <janitor@kali.org>  Thu, 26 Aug 2021 02:13:37 -0000
+
 backoff (1.10.0-0kali1) kali-dev; urgency=medium
 
   * Initial release
diff --git a/debian/patches/Add-missing-setup.py.patch b/debian/patches/Add-missing-setup.py.patch
index 6d9f89c..0d67892 100644
--- a/debian/patches/Add-missing-setup.py.patch
+++ b/debian/patches/Add-missing-setup.py.patch
@@ -11,11 +11,10 @@ tarball)
  1 file changed, 24 insertions(+)
  create mode 100644 setup.py
 
-diff --git a/setup.py b/setup.py
-new file mode 100644
-index 0000000..6e4f961
+Index: backoff/setup.py
+===================================================================
 --- /dev/null
-+++ b/setup.py
++++ backoff/setup.py
 @@ -0,0 +1,24 @@
 +# -*- coding: utf-8 -*-
 +from distutils.core import setup
diff --git a/pyproject.toml b/pyproject.toml
index 3469bf2..dc5b120 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,8 +1,8 @@
 [tool.poetry]
 name = "backoff"
-version = "1.10.0"
+version = "1.11.1"
 description = "Function decoration for backoff and retry"
-authors = ["Bob Green <rgreen@goscoutgo.com>"]
+authors = ["Bob Green <rgreen@aquent.com>"]
 readme = "README.rst"
 repository = "https://github.com/litl/backoff"
 license = "MIT"
@@ -21,7 +21,8 @@ classifiers = ['Development Status :: 5 - Production/Stable',
                'Programming Language :: Python :: 3.6',
                'Programming Language :: Python :: 3.7',
                'Programming Language :: Python :: 3.8',
-               'Topic :: Internet :: WWW/HTTP',
+               'Programming Language :: Python :: 3.9',
+                'Topic :: Internet :: WWW/HTTP',
                'Topic :: Software Development :: Libraries :: Python Modules',
                'Topic :: Utilities']
 packages = [
@@ -36,7 +37,8 @@ flake8 = "^3.6"
 pytest = "^4.0"
 pytest-cov = "^2.6"
 pytest-asyncio = {version = "^0.10.0",python = "^3.5"}
+autopep8 = "^1.5.7"
 
 [build-system]
-requires = ["poetry>=0.12"]
-build-backend = "poetry.masonry.api"
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/tests/test_backoff.py b/tests/test_backoff.py
index 20734f5..0af6cfc 100644
--- a/tests/test_backoff.py
+++ b/tests/test_backoff.py
@@ -1,9 +1,12 @@
 # coding:utf-8
 import datetime
+import itertools
 import logging
 import random
+import re
 import sys
 import threading
+import unittest.mock
 
 import pytest
 
@@ -733,3 +736,82 @@ def test_on_exception_logger_user_str(monkeypatch, caplog):
     assert len(caplog.records) == 3  # 2 backoffs and 1 giveup
     for record in caplog.records:
         assert record.name == 'my-logger'
+
+
+def _on_exception_factory(
+    backoff_log_level, giveup_log_level, max_tries
+):
+    @backoff.on_exception(
+        backoff.expo,
+        ValueError,
+        max_tries=max_tries,
+        backoff_log_level=backoff_log_level,
+        giveup_log_level=giveup_log_level,
+    )
+    def value_error():
+        raise ValueError
+
+    def func():
+        with pytest.raises(ValueError):
+            value_error()
+
+    return func
+
+
+def _on_predicate_factory(
+    backoff_log_level, giveup_log_level, max_tries
+):
+    @backoff.on_predicate(
+        backoff.expo,
+        max_tries=max_tries,
+        backoff_log_level=backoff_log_level,
+        giveup_log_level=giveup_log_level,
+    )
+    def func():
+        return False
+
+    return func
+
+
+@pytest.mark.parametrize(
+    ("func_factory", "backoff_log_level", "giveup_log_level"),
+    (
+        (factory, backoff_log_level, giveup_log_level)
+        for backoff_log_level, giveup_log_level in itertools.product(
+            (
+                logging.DEBUG,
+                logging.INFO,
+                logging.WARNING,
+                logging.ERROR,
+                logging.CRITICAL,
+            ),
+            repeat=2,
+        )
+        for factory in (_on_predicate_factory, _on_exception_factory)
+    )
+)
+def test_event_log_levels(
+    caplog, func_factory, backoff_log_level, giveup_log_level
+):
+    max_tries = 3
+    func = func_factory(backoff_log_level, giveup_log_level, max_tries)
+
+    with unittest.mock.patch('time.sleep', return_value=None):
+        with caplog.at_level(
+            min(backoff_log_level, giveup_log_level), logger="backoff"
+        ):
+            func()
+
+    backoff_re = re.compile("backing off", re.IGNORECASE)
+    giveup_re = re.compile("giving up", re.IGNORECASE)
+
+    backoff_log_count = 0
+    giveup_log_count = 0
+    for logger_name, level, message in caplog.record_tuples:
+        if level == backoff_log_level and backoff_re.match(message):
+            backoff_log_count += 1
+        elif level == giveup_log_level and giveup_re.match(message):
+            giveup_log_count += 1
+
+    assert backoff_log_count == max_tries - 1
+    assert giveup_log_count == 1