""" Contains all mappers (formatters) for CloudWatch logs """ import json import logging from json import JSONDecodeError from typing import Any from samcli.lib.observability.cw_logs.cw_log_event import CWLogEvent from samcli.lib.observability.observability_info_puller import ObservabilityEventMapper from samcli.lib.utils.colors import Colored from samcli.lib.utils.time import timestamp_to_iso LOG = logging.getLogger(__name__) class CWKeywordHighlighterFormatter(ObservabilityEventMapper[CWLogEvent]): """ Mapper implementation which will highlight given keywords in CloudWatch logs """ def __init__(self, colored: Colored, keyword=None): """ Parameters ---------- colored : Colored Colored class that will be used to highlight the keywords in log event keyword : str Keyword that will be highlighted """ self._keyword = keyword self._colored = colored def map(self, event: CWLogEvent) -> CWLogEvent: if self._keyword: highlight = self._colored.underline(self._keyword) event.message = event.message.replace(self._keyword, highlight) return event class CWColorizeErrorsFormatter(ObservabilityEventMapper[CWLogEvent]): """ Mapper implementation which will colorize some pre-defined error messages """ # couple of pre-defined error messages for lambda functions which will be colorized when getting the logs NODEJS_CRASH_MESSAGE = "Process exited before completing request" TIMEOUT_MSG = "Task timed out" def __init__(self, colored: Colored): self._colored = colored def map(self, event: CWLogEvent) -> CWLogEvent: if ( CWColorizeErrorsFormatter.NODEJS_CRASH_MESSAGE in event.message or CWColorizeErrorsFormatter.TIMEOUT_MSG in event.message ): event.message = self._colored.red(event.message) return event class CWJsonFormatter(ObservabilityEventMapper[CWLogEvent]): """ Mapper implementation which will auto indent the input if the input is a JSON object """ # pylint: disable=R0201 # Pylint recommends converting this method to a static one but we want it to stay as it is # since formatters/mappers are combined in an array of ObservabilityEventMapper class def map(self, event: CWLogEvent) -> CWLogEvent: try: if event.message.startswith("{"): msg_dict = json.loads(event.message) event.message = json.dumps(msg_dict, indent=2) except JSONDecodeError as err: LOG.debug("Can't decode string (%s) as JSON. Error (%s)", event.message, err) return event class CWPrettyPrintFormatter(ObservabilityEventMapper[CWLogEvent]): """ Mapper implementation which will format given CloudWatch log event into string with coloring log stream name and timestamp """ def __init__(self, colored: Colored): self._colored = colored def map(self, event: CWLogEvent) -> CWLogEvent: timestamp = self._colored.yellow(timestamp_to_iso(int(event.timestamp))) log_stream_name = self._colored.cyan(event.log_stream_name) event.message = f"{log_stream_name} {timestamp} {event.message}" return event class CWAddNewLineIfItDoesntExist(ObservabilityEventMapper): """ Mapper implementation which will add new lines at the end of events if it is not already there """ def map(self, event: Any) -> Any: # if it is a CWLogEvent, append new line at the end of event.message if isinstance(event, CWLogEvent) and not event.message.endswith("\n"): event.message = f"{event.message}\n" return event # if event is a string, then append new line at the end of the string if isinstance(event, str) and not event.endswith("\n"): return f"{event}\n" # no-action for unknown events return event class CWLogEventJSONMapper(ObservabilityEventMapper[CWLogEvent]): """ Converts given CWLogEvent into JSON string """ # pylint: disable=no-self-use def map(self, event: CWLogEvent) -> CWLogEvent: event.message = json.dumps(event.event) return event