Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit dea0794

Browse files
authored
Implement custom events in Azure (#925)
1 parent 86d5bd7 commit dea0794

File tree

8 files changed

+323
-69
lines changed

8 files changed

+323
-69
lines changed

contrib/opencensus-ext-azure/README.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ Modifying Logs
121121
logger.addHandler(handler)
122122
logger.warning('Hello, World!')
123123
124+
Events
125+
######
126+
127+
You can send `customEvent` telemetry in exactly the same way you would send `trace` telemetry except using the `AzureEventHandler` instead.
128+
129+
.. code:: python
130+
131+
import logging
132+
133+
from opencensus.ext.azure.log_exporter import AzureEventHandler
134+
135+
logger = logging.getLogger(__name__)
136+
logger.addHandler(AzureEventHandler(connection_string='InstrumentationKey=<your-instrumentation_key-here>'))
137+
logger.setLevel(logging.INFO)
138+
logger.info('Hello, World!')
124139
125140
Metrics
126141
~~~~~~~

contrib/opencensus-ext-azure/examples/logs/correlated.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@
3636
with tracer.span(name='test'):
3737
logger.warning('In the span')
3838
logger.warning('After the span')
39+
40+
input("...")

contrib/opencensus-ext-azure/examples/logs/error.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ def main():
3232

3333
if __name__ == '__main__':
3434
main()
35+
input("...")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2019, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
from opencensus.ext.azure.log_exporter import AzureEventHandler
18+
19+
logger = logging.getLogger(__name__)
20+
# TODO: you need to specify the instrumentation key in a connection string
21+
# and place it in the APPLICATIONINSIGHTS_CONNECTION_STRING
22+
# environment variable.
23+
logger.addHandler(AzureEventHandler())
24+
logger.setLevel(logging.INFO)
25+
logger.info('Hello, World!')
26+
27+
input("...")

contrib/opencensus-ext-azure/examples/logs/properties.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@
3232
result = 1 / 0 # generate a ZeroDivisionError
3333
except Exception:
3434
logger.exception('Captured an exception.', extra=properties)
35+
36+
input("...")

contrib/opencensus-ext-azure/examples/logs/simple.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@
2222
# environment variable.
2323
logger.addHandler(AzureLogHandler())
2424
logger.warning('Hello, World!')
25+
26+
input("...")

contrib/opencensus-ext-azure/opencensus/ext/azure/log_exporter/__init__.py

Lines changed: 80 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from opencensus.ext.azure.common.protocol import (
2525
Data,
2626
Envelope,
27+
Event,
2728
ExceptionData,
2829
Message,
2930
)
@@ -33,17 +34,51 @@
3334

3435
logger = logging.getLogger(__name__)
3536

36-
__all__ = ['AzureLogHandler']
37+
__all__ = ['AzureEventHandler', 'AzureLogHandler']
3738

3839

3940
class BaseLogHandler(logging.Handler):
40-
def __init__(self):
41+
42+
def __init__(self, **options):
4143
super(BaseLogHandler, self).__init__()
44+
self.options = Options(**options)
45+
utils.validate_instrumentation_key(self.options.instrumentation_key)
46+
if not 0 <= self.options.logging_sampling_rate <= 1:
47+
raise ValueError('Sampling must be in the range: [0,1]')
48+
self.export_interval = self.options.export_interval
49+
self.max_batch_size = self.options.max_batch_size
50+
self.storage = LocalFileStorage(
51+
path=self.options.storage_path,
52+
max_size=self.options.storage_max_size,
53+
maintenance_period=self.options.storage_maintenance_period,
54+
retention_period=self.options.storage_retention_period,
55+
)
56+
self._telemetry_processors = []
57+
self.addFilter(SamplingFilter(self.options.logging_sampling_rate))
4258
self._queue = Queue(capacity=8192) # TODO: make this configurable
4359
self._worker = Worker(self._queue, self)
4460
self._worker.start()
4561

62+
def _export(self, batch, event=None): # pragma: NO COVER
63+
try:
64+
if batch:
65+
envelopes = [self.log_record_to_envelope(x) for x in batch]
66+
envelopes = self.apply_telemetry_processors(envelopes)
67+
result = self._transmit(envelopes)
68+
if result > 0:
69+
self.storage.put(envelopes, result)
70+
if event:
71+
if isinstance(event, QueueExitEvent):
72+
self._transmit_from_storage() # send files before exit
73+
return
74+
if len(batch) < self.options.max_batch_size:
75+
self._transmit_from_storage()
76+
finally:
77+
if event:
78+
event.set()
79+
4680
def close(self):
81+
self.storage.close()
4782
self._worker.stop()
4883

4984
def createLock(self):
@@ -52,14 +87,7 @@ def createLock(self):
5287
def emit(self, record):
5388
self._queue.put(record, block=False)
5489

55-
def _export(self, batch, event=None):
56-
try:
57-
return self.export(batch)
58-
finally:
59-
if event:
60-
event.set()
61-
62-
def export(self, batch):
90+
def log_record_to_envelope(self, record):
6391
raise NotImplementedError # pragma: NO COVER
6492

6593
def flush(self, timeout=None):
@@ -121,74 +149,18 @@ def filter(self, record):
121149

122150

123151
class AzureLogHandler(TransportMixin, ProcessorMixin, BaseLogHandler):
124-
"""Handler for logging to Microsoft Azure Monitor.
125-
126-
:param options: Options for the log handler.
127-
"""
128-
129-
def __init__(self, **options):
130-
self.options = Options(**options)
131-
utils.validate_instrumentation_key(self.options.instrumentation_key)
132-
if not 0 <= self.options.logging_sampling_rate <= 1:
133-
raise ValueError('Sampling must be in the range: [0,1]')
134-
self.export_interval = self.options.export_interval
135-
self.max_batch_size = self.options.max_batch_size
136-
self.storage = LocalFileStorage(
137-
path=self.options.storage_path,
138-
max_size=self.options.storage_max_size,
139-
maintenance_period=self.options.storage_maintenance_period,
140-
retention_period=self.options.storage_retention_period,
141-
)
142-
self._telemetry_processors = []
143-
super(AzureLogHandler, self).__init__()
144-
self.addFilter(SamplingFilter(self.options.logging_sampling_rate))
145-
146-
def close(self):
147-
self.storage.close()
148-
super(AzureLogHandler, self).close()
149-
150-
def _export(self, batch, event=None): # pragma: NO COVER
151-
try:
152-
if batch:
153-
envelopes = [self.log_record_to_envelope(x) for x in batch]
154-
envelopes = self.apply_telemetry_processors(envelopes)
155-
result = self._transmit(envelopes)
156-
if result > 0:
157-
self.storage.put(envelopes, result)
158-
if event:
159-
if isinstance(event, QueueExitEvent):
160-
self._transmit_from_storage() # send files before exit
161-
return
162-
if len(batch) < self.options.max_batch_size:
163-
self._transmit_from_storage()
164-
finally:
165-
if event:
166-
event.set()
152+
"""Handler for logging to Microsoft Azure Monitor."""
167153

168154
def log_record_to_envelope(self, record):
169-
envelope = Envelope(
170-
iKey=self.options.instrumentation_key,
171-
tags=dict(utils.azure_monitor_context),
172-
time=utils.timestamp_to_iso_str(record.created),
173-
)
155+
envelope = create_envelope(self.options.instrumentation_key, record)
174156

175-
envelope.tags['ai.operation.id'] = getattr(
176-
record,
177-
'traceId',
178-
'00000000000000000000000000000000',
179-
)
180-
envelope.tags['ai.operation.parentId'] = '|{}.{}.'.format(
181-
envelope.tags['ai.operation.id'],
182-
getattr(record, 'spanId', '0000000000000000'),
183-
)
184157
properties = {
185158
'process': record.processName,
186159
'module': record.module,
187160
'fileName': record.pathname,
188161
'lineNumber': record.lineno,
189162
'level': record.levelname,
190163
}
191-
192164
if (hasattr(record, 'custom_dimensions') and
193165
isinstance(record.custom_dimensions, dict)):
194166
properties.update(record.custom_dimensions)
@@ -230,3 +202,43 @@ def log_record_to_envelope(self, record):
230202
)
231203
envelope.data = Data(baseData=data, baseType='MessageData')
232204
return envelope
205+
206+
207+
class AzureEventHandler(TransportMixin, ProcessorMixin, BaseLogHandler):
208+
"""Handler for sending custom events to Microsoft Azure Monitor."""
209+
210+
def log_record_to_envelope(self, record):
211+
envelope = create_envelope(self.options.instrumentation_key, record)
212+
213+
properties = {}
214+
if (hasattr(record, 'custom_dimensions') and
215+
isinstance(record.custom_dimensions, dict)):
216+
properties.update(record.custom_dimensions)
217+
218+
envelope.name = 'Microsoft.ApplicationInsights.Event'
219+
data = Event(
220+
name=self.format(record),
221+
properties=properties,
222+
)
223+
envelope.data = Data(baseData=data, baseType='EventData')
224+
225+
return envelope
226+
227+
228+
def create_envelope(instrumentation_key, record):
229+
envelope = Envelope(
230+
iKey=instrumentation_key,
231+
tags=dict(utils.azure_monitor_context),
232+
time=utils.timestamp_to_iso_str(record.created),
233+
)
234+
envelope.tags['ai.operation.id'] = getattr(
235+
record,
236+
'traceId',
237+
'00000000000000000000000000000000',
238+
)
239+
envelope.tags['ai.operation.parentId'] = '|{}.{}.'.format(
240+
envelope.tags['ai.operation.id'],
241+
getattr(record, 'spanId', '0000000000000000'),
242+
)
243+
244+
return envelope

0 commit comments

Comments
 (0)