Logging handler for Application Insights by BogdanBe · Pull Request #2 · microsoft/ApplicationInsights-Python · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions DESCRIPTION.rst
66 changes: 53 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![PyPI version](https://badge.fury.io/py/applicationinsights.svg)](http://badge.fury.io/py/applicationinsights)

>Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python's elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms.
>Python is an easy to learn, powerful programming language. It has efficient high-level data structures and a simple but effective approach to object-oriented programming. Python's elegant syntax and dynamic typing, together with its interpreted nature, make it an ideal language for scripting and rapid application development in many areas on most platforms.
> -- <cite>[The Python Tutorial - Introduction](https://docs.python.org/3/tutorial/)</cite>

This project extends the Application Insights API surface to support Python. [Application Insights](http://azure.microsoft.com/en-us/services/application-insights/) is a service that allows developers to keep their application available, performing and succeeding. This Python module will allow you to send telemetry of various kinds (event, trace, exception, etc.) to the Application Insights service where they can be visualized in the Azure Portal.
Expand Down Expand Up @@ -45,23 +45,23 @@ tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
tc.track_event('Test event', { 'foo': 'bar' }, { 'baz': 42 })
```

**Sending a trace telemetry item with custom properties**
**Sending a trace telemetry item with custom properties**
```python
from applicationinsights import TelemetryClient
tc = TelemetryClient()
tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
tc.track_trace('Test trace', { 'foo': 'bar' })
```

**Sending a metric telemetry item**
**Sending a metric telemetry item**
```python
from applicationinsights import TelemetryClient
tc = TelemetryClient()
tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
tc.track_metric('My Metric', 42)
```

**Sending an exception telemetry item with custom properties and measurements**
**Sending an exception telemetry item with custom properties and measurements**
```python
import sys
from applicationinsights import TelemetryClient
Expand All @@ -78,22 +78,22 @@ except:
tc.track_exception(*sys.exc_info(), properties={ 'foo': 'bar' }, measurements={ 'x': 42 })
```

**Configuring context for a telemetry client instance**
**Configuring context for a telemetry client instance**
```python
from applicationinsights import TelemetryClient
tc = TelemetryClient()
tc.context.instrumentation_key = '<YOUR INSTRUMENTATION KEY GOES HERE>'
tc.context.application.id = 'My application'
tc.context.application.ver = '1.2.3'
tc.context.device.id = 'My current device'
tc.context.device.oem_name = 'Asus'
tc.context.device.model = 'X31A'
tc.context.device.type = "Other"
tc.context.user.id = 'santa@northpole.net'
tc.context.application.id = 'My application'
tc.context.application.ver = '1.2.3'
tc.context.device.id = 'My current device'
tc.context.device.oem_name = 'Asus'
tc.context.device.model = 'X31A'
tc.context.device.type = "Other"
tc.context.user.id = 'santa@northpole.net'
tc.track_trace('My trace with context')
```

**Configuring channel related properties**
**Configuring channel related properties**
```python
from applicationinsights import TelemetryClient
tc = TelemetryClient()
Expand All @@ -103,5 +103,45 @@ tc.channel.sender.send_interval_in_milliseconds = 30 * 1000
tc.channel.sender.max_queue_item_count = 10
```

**Basic logging configuration**
```python
import logging
from applicationinsights.logging import ApplicationInsightsHandler

# set up logging
handler = ApplicationInsightsHandler('<YOUR INSTRUMENTATION KEY GOES HERE>')
logging.basicConfig(handlers=[ handler ], format='%(levelname)s: %(message)s', level=logging.DEBUG)

# log something (this will be sent to the Application Insights service as a trace)
logging.debug('This is a message')

try:
raise Exception('Some exception')
except:
# this will send an exception to the Application Insights service
logging.exception('Code went boom!')

# don't forget to flush send all un-sent telemetry
handler.flush()
```

**Advanced logging configuration**
```python
import logging
from applicationinsights.logging import ApplicationInsightsHandler

# set up logging
handler = ApplicationInsightsHandler('<YOUR INSTRUMENTATION KEY GOES HERE>')
handler.setLevel(logging.DEBUG)
handler.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
my_logger = logging.getLogger('simple_logger')
my_logger.setLevel(logging.DEBUG)
my_logger.addHandler(handler)

# log something (this will be sent to the Application Insights service as a trace)
my_logger.debug('This is a message')

# don't forget to flush to send all un-sent telemetry
handler.flush()
```

3 changes: 2 additions & 1 deletion applicationinsights/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .TelemetryClient import TelemetryClient
from . import channel
from . import channel
from . import logging
55 changes: 55 additions & 0 deletions applicationinsights/logging/ApplicationInsightsHandler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import logging
import applicationinsights

class ApplicationInsightsHandler(logging.Handler):
"""This class represents an integration point between Python's logging framework and the Application Insights
service.

Logging records are sent to the service either as simple Trace telemetry or as Exception telemetry (in the case
of exception information being available).
"""
def __init__(self, instrumentation_key, *args, **kwargs):
"""
Initialize a new instance of the class.

Args:
instrumentation_key (str). the instrumentation key to use while sending telemetry to the service.
"""
if not instrumentation_key:
raise Exception('Instrumentation key was required but not provided')
self.client = applicationinsights.TelemetryClient()
self.client.context.instrumentation_key = instrumentation_key
logging.Handler.__init__(self, *args, **kwargs)

def flush(self):
"""Flushes the queued up telemetry to the service.
"""
self.client.flush()
return super().flush()

def emit(self, record):
"""Emit a record.

If a formatter is specified, it is used to format the record. If exception information is present, an Exception
telemetry object is sent instead of a Trace telemetry object.

Args:
record (:class:`logging.LogRecord`). the record to format and send.
"""
# the set of properties that will ride with the record
properties = {
'process': record.processName,
'module': record.module,
'fileName': record.filename,
'lineNumber': record.lineno,
'level': record.levelname,
}

# if we have exec_info, we will use it as an exception
if record.exc_info:
self.client.track_exception(*record.exc_info, properties=properties)
return

# if we don't simply format the message and send the trace
formatted_message = self.format(record)
self.client.track_trace(formatted_message, properties=properties)
1 change: 1 addition & 0 deletions applicationinsights/logging/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .ApplicationInsightsHandler import ApplicationInsightsHandler
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# http://packaging.python.org/en/latest/tutorial.html#version
version='0.3.0',
version='0.4.0',

description='This project extends the Application Insights API surface to support Python.',
long_description=long_description,
Expand Down
3 changes: 2 additions & 1 deletion tests/applicationinsights_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from . import TestTelemetryClient
from . import channel_tests
from . import channel_tests
from . import logging_tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import unittest
import logging as pylogging

import sys, os, os.path
rootDirectory = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', '..')
if rootDirectory not in sys.path:
sys.path.append(rootDirectory)

from applicationinsights import logging

class TestApplicationInsightsHandler(unittest.TestCase):
def test_construct(self):
handler = logging.ApplicationInsightsHandler('test')
self.assertIsNotNone(handler)
self.assertEqual('test', handler.client.context.instrumentation_key)

def test_construct_raises_exception_on_no_instrumentation_key(self):
self.assertRaises(Exception, logging.ApplicationInsightsHandler, None)

def test_log_works_as_expected(self):
logger, sender = self._setup_logger()

expected = [
(logger.debug, 'debug message', 'Microsoft.ApplicationInsights.Message', 'test', 'MessageData', 'simple_logger - DEBUG - debug message'),
(logger.info, 'info message', 'Microsoft.ApplicationInsights.Message', 'test', 'MessageData', 'simple_logger - INFO - info message'),
(logger.warn, 'warn message', 'Microsoft.ApplicationInsights.Message', 'test', 'MessageData', 'simple_logger - WARNING - warn message'),
(logger.error, 'error message', 'Microsoft.ApplicationInsights.Message', 'test', 'MessageData', 'simple_logger - ERROR - error message'),
(logger.critical, 'critical message', 'Microsoft.ApplicationInsights.Message', 'test', 'MessageData', 'simple_logger - CRITICAL - critical message')
]

for logging_function, logging_parameter, envelope_type, ikey, data_type, message in expected:
logging_function(logging_parameter)
data = sender.data[0][0]
sender.data.clear()
self.assertEqual(envelope_type, data.name)
self.assertEqual(ikey, data.ikey)
self.assertEqual(data_type, data.data.base_type)
self.assertEqual(message, data.data.base_data.message)

def test_log_exception_works_as_expected(self):
logger, sender = self._setup_logger()

try:
raise Exception('blah')
except:
logger.exception('some error')

data = sender.data[0][0]
self.assertEqual('Microsoft.ApplicationInsights.Exception', data.name)
self.assertEqual('test', data.ikey)
self.assertEqual('ExceptionData', data.data.base_type)
self.assertEqual('blah', data.data.base_data.exceptions[0].message)

def _setup_logger(self):
logger = pylogging.getLogger('simple_logger')
logger.setLevel(pylogging.DEBUG)

handler = logging.ApplicationInsightsHandler('test')
handler.setLevel(pylogging.DEBUG)

# mock out the sender
sender = MockSynchronousSender()
queue = handler.client.channel.queue
queue.max_queue_length = 1
queue._sender = sender
sender.queue = queue

formatter = pylogging.Formatter('%(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

return logger, sender


class MockSynchronousSender:
def __init__(self):
self.send_buffer_size = 1
self.data = []
self.queue = None

def send(self, data_to_send):
self.data.append(data_to_send)
1 change: 1 addition & 0 deletions tests/applicationinsights_tests/logging_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import TestApplicationInsightsHandler
3 changes: 2 additions & 1 deletion tests/tests.py