The Flask 1.0 release changed the semantics of
`@app.errorhandler(Exception)`.
Previously, this feature would catch application internal exceptions
only. Since the Flask 1.0 release, this now catches all exceptions,
including werkzeug's HTTPException which is used internally by Flask for
control flow, e.g. 404 not found, 403 forbidden, etc. The side-effect of
this change is that the AppInsights exception handler now translated
these control flow exceptions into uncaught exceptions, i.e. HTTP 500
errors.
Currently the Application Insights client SDK already has some great
resiliency built-in, e.g. retrying to send telemetry if there are
intermittent connectivity issues or service interruptions. However,
there still is a risk to lose telemetry if the app crashes before
sending telemetry since the telemetry queue is in-memory only.
This change adds an option to persist the queue to disk via the
[persist-queue](https://pypi.org/project/persist-queue/) module which
will protect customers against potential data loss, at the cost of a
slight loss in performance.
Currently the NullSender crashes when used in conjunction with
AsynchronousQueue since the AsynchronousQueue relies on some methods
that are not part of the SenderBase interface.
This limitation leads to unexpected behavior when for example using the
NullSender to avoid sending telemetry in an application when the
instrumentation key isn't configured in the environment.
This change makes the NullSender work with the AsynchronousQueue so that
the class can be used with both queues included in the SDK.
It's a very common use-case to set up AppInsights telemetry in a project
and customize the telemetry channel to be asynchronous; for example to
transparently integrate AppInsights into an existing daemon, worker
thread or webserver that already uses the logging module. It's currently
tedious if the user always has to write the same code over and over
again to import the AsynchronousQueue, AsynchronousSender,
TelemetryChannel, write them all up, before being able to pass them into
the enable function. As such, this change introduces a new shortcut that
lets the user specify async mode as a high-level concept on the enable
function.
This change simplifies the following code:
```py
from applicationinsights.logging import enable
from applicationinsights.channel import AsynchronousQueue
from applicationinsights.channel import AsynchronousSender
from applicationinsights.channel import TelemetryChannel
sender = AsynchronousSender()
queue = AsynchronousQueue(sender)
channel = TelemetryChannel(queue=queue)
enable('my-instrumentation-key', telemetry_channel=channel)
```
The new code is much nicer for the user:
```py
from applicationinsights.logging import enable
enable('my-instrumentation-key', async_=True)
```
As a companion to this new functionality, this change also introduces an
argument that lets the user customize the endpoint to which the
telemetry is being sent:
```py
from applicationinsights.logging import enable
enable('my-instrumentation-key', async_=True, endpoint='http://custom')
```
Together, the two new arguments raise the enable function to feature
parity with the other high level integrations (e.g. Django or Flask) in
terms of ease of developer use and high-level API.
The `logging.enable` function caches references to the LoggingHandler
instances that it creates in a dictionary private to the module.
This means that references to the handler objects survive even if they
were removed from all loggers that referenced them, causing two
undesirable side-effects:
1) The LoggingHandler instances created by `logging.enable` can never be
reclaimed by the garbage collector.
2) Removing all references to the LoggingHandler from the logger doesn't
mark the instrumentation key as "no longer used".
This change fixes these issues by replacing the cache with a
WeakValueDictionary: as soon as the last reference to the LoggingHandler
is removed (e.g. when `some_logger.removeHandler(...)` is called), the
WeakValueDictionary will also evict its cached entry.
This removes an assumption that the log level should always be INFO by
enabling users to call the function like so:
```py
import logging
from applicationinsights.logging import enable
enable('my-instrumentation-key', level=logging.DEBUG)
```
The classifiers list in `setup.py` mentions support for Python 3.4 and
yet the CI on Travis doesn't run the tests on that version which means
that we can't be certain that the package works correctly on the
declared version.
To fix this discrepancy, this change makes the Python versions used
during CI match up with the Python versions declared in the classifiers
list.
This change brings three main benefits:
1) Clients can now programmatically access the value of the default
sender endpoint, for example to forward requests to it. This benefit
is demonstrated by the changes to the test code.
2) Avoid copy/paste of the endpoint URL between async and sync senders.
3) Defaulting the service_endpoint_url to None instead of the string
value and checking against null later to look up the default endpoint
url means that the class now supports use-cases like
`Sender(os.getenv('SERVICE_URL'))` which simplifies client code that
may want to optionally override the service url. This benefit is
demonstrated by the changes to the Django and Flask integrations.