-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Switch to official Flask AppInsights middleware * Add action exception handler * Reduce default log level * Short-circuit formatting when logger is disabled * Remove unnecessary list * Switch to async AppInsights channel * Ensure all telemetry is always flushed * Remove unnecessary supertype
- Loading branch information
Showing
9 changed files
with
135 additions
and
136 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,95 +1,111 @@ | ||
from logging import CRITICAL | ||
from logging import DEBUG | ||
from logging import Formatter | ||
from logging import Handler | ||
from logging import INFO | ||
from logging import Logger | ||
from logging import StreamHandler | ||
from logging import WARNING | ||
from logging import getLogger | ||
from typing import Any | ||
from typing import Iterable | ||
from typing import Optional | ||
|
||
from applicationinsights import TelemetryClient | ||
from applicationinsights import exceptions | ||
from applicationinsights.channel import AsynchronousQueue | ||
from applicationinsights.channel import AsynchronousSender | ||
from applicationinsights.channel import TelemetryChannel | ||
from applicationinsights.channel import TelemetryContext | ||
from applicationinsights.logging import LoggingHandler | ||
from cached_property import cached_property | ||
|
||
from opwen_email_server.config import APPINSIGHTS_KEY | ||
from opwen_email_server.config import LOG_LEVEL | ||
from opwen_email_server.constants.logging import SEPARATOR | ||
from opwen_email_server.constants.logging import STDERR | ||
from opwen_email_server.constants.logging import TELEMETRY_QUEUE_ITEMS | ||
from opwen_email_server.constants.logging import TELEMETRY_QUEUE_SECONDS | ||
from opwen_email_server.utils.collections import append | ||
from opwen_email_server.utils.collections import singleton | ||
|
||
|
||
@singleton | ||
def _get_log_handlers() -> Iterable[Handler]: | ||
stderr = StreamHandler() | ||
stderr.setFormatter(Formatter(STDERR)) | ||
return [stderr] | ||
def _create_telemetry_channel() -> Optional[TelemetryChannel]: | ||
if not APPINSIGHTS_KEY: | ||
return None | ||
|
||
sender = AsynchronousSender() | ||
queue = AsynchronousQueue(sender) | ||
context = TelemetryContext() | ||
context.instrumentation_key = APPINSIGHTS_KEY | ||
return TelemetryChannel(context, queue) | ||
|
||
@singleton | ||
def _get_logger() -> Logger: | ||
log = getLogger() | ||
for handler in _get_log_handlers(): | ||
log.addHandler(handler) | ||
log.setLevel(LOG_LEVEL) | ||
return log | ||
|
||
class LogMixin: | ||
_telemetry_channel = _create_telemetry_channel() | ||
|
||
@singleton | ||
def _get_telemetry_client() -> Optional[TelemetryClient]: | ||
if not APPINSIGHTS_KEY: | ||
return None | ||
@cached_property | ||
def _default_log_handlers(self) -> Iterable[Handler]: | ||
handlers = [] | ||
|
||
stderr = StreamHandler() | ||
stderr.setFormatter(Formatter(STDERR)) | ||
handlers.append(stderr) | ||
|
||
if APPINSIGHTS_KEY: | ||
handlers.append(LoggingHandler( | ||
APPINSIGHTS_KEY, | ||
telemetry_channel=self._telemetry_channel)) | ||
|
||
telemetry_client = TelemetryClient(APPINSIGHTS_KEY) | ||
telemetry_client.channel.sender.send_interval_in_milliseconds = \ | ||
TELEMETRY_QUEUE_SECONDS * 1000 | ||
telemetry_client.channel.sender.max_queue_item_count = \ | ||
TELEMETRY_QUEUE_ITEMS | ||
exceptions.enable(APPINSIGHTS_KEY) | ||
return handlers | ||
|
||
return telemetry_client | ||
@cached_property | ||
def _logger(self) -> Logger: | ||
log = getLogger() | ||
for handler in self._default_log_handlers: | ||
log.addHandler(handler) | ||
log.setLevel(LOG_LEVEL) | ||
return log | ||
|
||
@cached_property | ||
def _telemetry_client(self) -> Optional[TelemetryClient]: | ||
if not APPINSIGHTS_KEY: | ||
return None | ||
|
||
class LogMixin(object): | ||
_logger = _get_logger() | ||
_telemetry_client = _get_telemetry_client() | ||
return TelemetryClient(APPINSIGHTS_KEY, self._telemetry_channel) | ||
|
||
def log_debug(self, message: str, *args: Any): | ||
self._log('debug', message, args) | ||
self._log(DEBUG, message, args) | ||
|
||
def log_info(self, message: str, *args: Any): | ||
self._log('info', message, args) | ||
self._log(INFO, message, args) | ||
|
||
def log_warning(self, message: str, *args: Any): | ||
self._log('warning', message, args) | ||
self._log(WARNING, message, args) | ||
|
||
def log_exception(self, message: str, *args: Any): | ||
self._log('exception', message, args) | ||
def log_exception(self, ex: Exception, message: str, *args: Any): | ||
self._log(CRITICAL, message + ' (%r)', append(args, ex)) | ||
|
||
if self._telemetry_client: | ||
# noinspection PyBroadException | ||
try: | ||
raise ex | ||
except Exception: | ||
self._telemetry_client.track_exception() | ||
self._telemetry_channel.flush() | ||
|
||
def _log(self, level: int, log_message: str, log_args: Iterable[Any]): | ||
if not self._logger.isEnabledFor(level): | ||
return | ||
|
||
def _log(self, level: str, log_message: str, log_args: Iterable[Any]): | ||
message_parts = ['%s'] | ||
args = [self.__class__.__name__] | ||
message_parts.append(log_message) | ||
args.extend(log_args) | ||
message = SEPARATOR.join(message_parts) | ||
log = getattr(self._logger, level) | ||
log(message, *args) | ||
self._logger.log(level, message, *args) | ||
|
||
if self._telemetry_client: | ||
self._telemetry_client.track_trace( | ||
message % tuple(args), {'level': level}) | ||
|
||
if self.should_send_message_immediately(level): | ||
self._telemetry_client.flush() | ||
|
||
# noinspection PyMethodMayBeStatic | ||
def log_event(self, event_name: str, properties: Optional[dict] = None): | ||
self._logger.info('%s%s%s', event_name, SEPARATOR, properties) | ||
self.log_info('%s%s%s', event_name, SEPARATOR, properties) | ||
|
||
if self._telemetry_client: | ||
self._telemetry_client.track_event(event_name, properties) | ||
self._telemetry_client.flush() | ||
|
||
# noinspection PyMethodMayBeStatic | ||
def should_send_message_immediately(self, level: str) -> bool: | ||
return level != 'debug' | ||
self._telemetry_channel.flush() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.