def test_setup_no_service_name(stdout): # GIVEN no service is explicitly defined # WHEN logger is setup # THEN service field should be "service_undefined" logger = Logger(stream=stdout) logger.info("Hello") log = json.loads(stdout.getvalue()) assert "service_undefined" == log["service"]
def test_setup_service_name(root_logger, stdout): # GIVEN service is explicitly defined # WHEN logger is setup # THEN service field should be equals service given service_name = "payment" logger = Logger(service=service_name, stream=stdout) logger.info("Hello") log = json.loads(stdout.getvalue()) assert service_name == log["service"]
def test_setup_service_env_var(monkeypatch, stdout): # GIVEN service is explicitly defined via POWERTOOLS_SERVICE_NAME env # WHEN logger is setup # THEN service field should be equals POWERTOOLS_SERVICE_NAME value service_name = "payment" monkeypatch.setenv("POWERTOOLS_SERVICE_NAME", service_name) logger = Logger(stream=stdout) logger.info("Hello") log = json.loads(stdout.getvalue()) assert service_name == log["service"]
def log_eventbridge_errors(response: dict, function_logger: Logger): """ Args: response: Response from putting events onto eventBridge function_logger: logger to write out errors to (if any exists) return: null """ if response["FailedEntryCount"] > 0: for entry in response["Entries"]: if entry.get("ErrorCode", False): function_logger.error(entry) return response["FailedEntryCount"]
def test_setup_sampling_rate(monkeypatch, stdout): # GIVEN samping rate is explicitly defined via POWERTOOLS_LOGGER_SAMPLE_RATE env # WHEN logger is setup # THEN sampling rate should be equals POWERTOOLS_LOGGER_SAMPLE_RATE value and should sample debug logs sampling_rate = "1" monkeypatch.setenv("POWERTOOLS_LOGGER_SAMPLE_RATE", sampling_rate) monkeypatch.setenv("LOG_LEVEL", "INFO") logger = Logger(stream=stdout) logger.debug("I am being sampled") log = json.loads(stdout.getvalue()) assert sampling_rate == log["sampling_rate"] assert "DEBUG" == log["level"] assert "I am being sampled" == log["message"]
def test_inject_lambda_context_log_no_request_by_default( monkeypatch, lambda_context, stdout): # GIVEN a lambda function is decorated with logger # WHEN logger is setup # THEN logger should not log event received by lambda handler lambda_event = {"greeting": "hello"} logger = Logger(stream=stdout) @logger.inject_lambda_context def handler(event, context): logger.info("Hello") handler(lambda_event, lambda_context) # Given that our string buffer has many log statements separated by newline \n # We need to clean it before we can assert on logs = [ json.loads(line.strip()) for line in stdout.getvalue().split("\n") if line ] event = {} for log in logs: if "greeting" in log["message"]: event = log["message"] assert event != lambda_event
def test_inject_lambda_context_log_event_request_env_var( monkeypatch, lambda_context, stdout): # GIVEN a lambda function is decorated with logger instructed to log event # via POWERTOOLS_LOGGER_LOG_EVENT env # WHEN logger is setup # THEN logger should log event received from Lambda lambda_event = {"greeting": "hello"} monkeypatch.setenv("POWERTOOLS_LOGGER_LOG_EVENT", "true") logger = Logger(stream=stdout) @logger.inject_lambda_context def handler(event, context): logger.info("Hello") handler(lambda_event, lambda_context) # Given that our string buffer has many log statements separated by newline \n # We need to clean it before we can assert on logs = [ json.loads(line.strip()) for line in stdout.getvalue().split("\n") if line ] event = {} for log in logs: if "greeting" in log["message"]: event = log["message"] assert event == lambda_event
def test_inject_lambda_cold_start(lambda_context, stdout): # GIVEN a lambda function is decorated with logger, and called twice # WHEN logger is setup # THEN cold_start key should only be true in the first call from aws_lambda_powertools.logging import logger # # As we run tests in parallel global cold_start value can be false # # here we reset to simulate the correct behaviour # # since Lambda will only import our logger lib once per concurrent execution logger.is_cold_start = True logger = Logger(stream=stdout) def custom_method(): logger.info("Hello from method") @logger.inject_lambda_context def handler(event, context): custom_method() logger.info("Hello") handler({}, lambda_context) handler({}, lambda_context) # Given that our string buffer has many log statements separated by newline \n # We need to clean it before we can assert on logs = [ json.loads(line.strip()) for line in stdout.getvalue().split("\n") if line ] first_log, second_log, third_log, fourth_log = logs # First execution assert "true" == first_log["cold_start"] assert "true" == second_log["cold_start"] # Second execution assert "false" == third_log["cold_start"] assert "false" == fourth_log["cold_start"]
def test_inject_lambda_context(lambda_context, stdout): # GIVEN a lambda function is decorated with logger # WHEN logger is setup # THEN lambda contextual info should always be in the logs logger_context_keys = ( "function_name", "function_memory_size", "function_arn", "function_request_id", ) logger = Logger(stream=stdout) @logger.inject_lambda_context def handler(event, context): logger.info("Hello") handler({}, lambda_context) log = json.loads(stdout.getvalue()) for key in logger_context_keys: assert key in log
def test_inject_lambda_context_log_event_request(lambda_context, stdout): # GIVEN a lambda function is decorated with logger instructed to log event # WHEN logger is setup # THEN logger should log event received from Lambda lambda_event = {"greeting": "hello"} logger = Logger(stream=stdout) @logger.inject_lambda_context(log_event=True) # @logger.inject_lambda_context(log_event=True) def handler(event, context): logger.info("Hello") handler(lambda_event, lambda_context) # Given that our string buffer has many log statements separated by newline \n # We need to clean it before we can assert on logs = [ json.loads(line.strip()) for line in stdout.getvalue().split("\n") if line ] logged_event, _ = logs assert "greeting" in logged_event["message"]
import os import json import boto3 from boto3.dynamodb.conditions import Key from aws_lambda_powertools.logging import Logger logger = Logger() from common.dynamodb import DecimalEncoder, map_keys, query_till_end def query_table(region, table, pk): """ Args: table: DynamoDB table object to query region: region to query on returns: items: items returned from the query """ kwargs = { "KeyConditionExpression": Key("pk").eq(pk), "ProjectionExpression": "arn, pckgVrsn, dplySts, rqrmntsTxt, exDt", "FilterExpression": "attribute_exists(dplySts)", # don't get latest version } items = query_till_end(table=table, kwargs=kwargs) return map_keys(items)
from zipfile import ZipFile, is_zipfile from typing import Dict, List, Optional import hermes.backend.dict from ask_sdk_core.exceptions import ApiClientException from ask_sdk_model.services import ApiClient, ApiClientRequest from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from marshmallow import ValidationError from .cache.bmemcached import Backend as BmemcachedBackend from .exceptions import GTFSDataError, NetworkDescriptionError, OperationMonitoringError from .model.line_stops import LineDetails from .model.passing_times import PassingTime, PointPassingTimes logger = Logger(service="STIB service") tracer = Tracer(service="STIB service") ENVIRONMENT = environ["env"] MEMCACHED_ENDPOINT = environ["cache_endpoint"] MEMCACHED_USERNAME = environ["cache_username"] MEMCACHED_PASSWORD = environ["cache_password"] @tracer.capture_method def initialize_cache() -> hermes.Hermes: if ENVIRONMENT == "Sandbox": logger.info( { "operation": "Setting Hermes caching backend", "environment": ENVIRONMENT.upper(),
# or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. import gettext from ask_sdk_core.dispatch_components import AbstractRequestInterceptor from ask_sdk_core.handler_input import HandlerInput from ask_sdk_core.utils import get_locale from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer # Logging/tracing configuration logger = Logger(service="i18n interceptor") tracer = Tracer(service="i18n interceptor") class LocalizationInterceptor(AbstractRequestInterceptor): """Request interceptor to handle multiple locales.""" def process(self, handler_input): """Parse locale information and add i18n manager to the request attributes.""" # type: (HandlerInput) -> None logger.debug("In LocalizationInterceptor") locale = get_locale(handler_input) logger.debug({"Locale": locale}) i18n = gettext.translation( "base", localedir="core/locales/", languages=[locale], fallback=True
import os import re import requests from aws_lambda_powertools.logging import Logger logger = Logger(service="Console Sign-in") class SlackMessage: def __init__(self, user_arn): self.user_arn = user_arn def _user_name(self): """ Extracts the username from an AWS User ARN Parameters: self.user_arn (string): AWS User ARN, e.g. arn:aws:iam::123456789012:user/[email protected] Returns: username (string): Username in email format, e.g. [email protected] """ arn = re.search(r'arn:aws:iam::\d{12}:user/([a-z]+@\w+\.com)', self.user_arn) return arn.group(1) def _payload(self) -> dict: p = {'username': self._user_name()} return p def post(self, slack_url: str) -> requests.Response:
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. from ask_sdk_core.dispatch_components import AbstractRequestHandler from ask_sdk_core.handler_input import HandlerInput from ask_sdk_core.utils import is_intent_name from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from requests import Response from ...data import data # Logging/tracing configuration logger = Logger(service="Fallback Amazon handler") tracer = Tracer(service="Fallback Amazon handler") class FallBackHandler(AbstractRequestHandler): """Handler for Fallback Intent.""" def can_handle(self, handler_input): # type: (HandlerInput) -> bool return is_intent_name("AMAZON.FallbackIntent")(handler_input) def handle(self, handler_input): # type: (HandlerInput) -> Response logger.debug("In FallbackIntentHandler") _ = handler_input.attributes_manager.request_attributes["_"]
# specific language governing permissions and limitations under the # License. import json from calendar import timegm from os import environ from datetime import datetime, timedelta, timezone import requests from boto3.session import Session from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from botocore.exceptions import ClientError # Logging/tracing configuration logger = Logger(service="Token helper") tracer = Tracer(service="Token helper") class TokenException(Exception): """Exception for token generation errors""" def __init__(self, msg, original_exception): super(TokenException, self).__init__(msg + (": %s" % original_exception)) self.original_exception = original_exception class SecurityToken: """Authentication token and expiration date access/management.""" def __init__(self, token, token_validity_time: int = None): if token_validity_time:
def test_logger_append_duplicated(stdout): logger = Logger(stream=stdout, request_id="value") logger.structure_logs(append=True, request_id="new_value") logger.info("log") log = json.loads(stdout.getvalue()) assert "new_value" == log["request_id"]
# License. from typing import List, Optional from ask_sdk_core.dispatch_components import AbstractRequestHandler from ask_sdk_core.handler_input import HandlerInput from ask_sdk_core.utils import is_intent_name from ask_sdk_model import Response from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from ...data import data from ...service.model.passing_times import PassingTime # Logging/tracing configuration logger = Logger(service="Get arrival time intent handler") tracer = Tracer(service="Get arrival time intent handler") class GetArrivalTimesNoPrefsIntentHandler(AbstractRequestHandler): """Handler for get arrival time Intent: no available preferences scenario.""" def can_handle(self, handler_input): # type: (HandlerInput) -> bool # Extract persistent attributes and check if they are all present attr = handler_input.attributes_manager.persistent_attributes attributes_are_present = ("favorite_stop_id" in attr and "favorite_line_id" in attr) return not attributes_are_present and is_intent_name( "GetArrivalTimesIntent")(handler_input)
# # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. from ask_sdk_core.dispatch_components import AbstractRequestHandler from ask_sdk_core.handler_input import HandlerInput from ask_sdk_core.utils import is_intent_name from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from requests import Response # Logging/tracing configuration logger = Logger(service="Repeat Amazon handler") tracer = Tracer(service="Repeat Amazon handler") class RepeatHandler(AbstractRequestHandler): """Handler for Repeat Intent.""" def can_handle(self, handler_input): # type: (HandlerInput) -> bool return is_intent_name("AMAZON.RepeatIntent")(handler_input) def handle(self, handler_input): # type: (HandlerInput) -> Response logger.debug("In RepeatIntentHandler")
from ask_sdk_model.dialog.dynamic_entities_directive import * from ask_sdk_model.dialog_state import DialogState from ask_sdk_model.er.dynamic import ( Entity, EntityListItem, EntityValueAndSynonyms, UpdateBehavior, ) from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from ...data import data from ...service.model.line_stops import LineDetails # Logging/tracing configuration logger = Logger(service="Favorite line intent handler") tracer = Tracer(service="Favorite line intent handler") class StartedInProgressFavoriteLineHandler(AbstractRequestHandler): """ Handler to delegate the favorite line intent dialog to alexa """ def can_handle(self, handler_input): # type: (HandlerInput) -> bool return ( is_intent_name("SetFavoriteLineIntent")(handler_input) and get_dialog_state(handler_input) != DialogState.COMPLETED )
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. from ask_sdk_core.dispatch_components import AbstractRequestHandler from ask_sdk_core.handler_input import HandlerInput from ask_sdk_core.utils import is_intent_name, is_request_type from ask_sdk_model import Response from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from ...data import data # Logging/tracing configuration logger = Logger(service="Common Amazon handlers") tracer = Tracer(service="Common Amazon handlers") class YesIntentHandler(AbstractRequestHandler): """Single handler for Yes Intent.""" def can_handle(self, handler_input): # type: (HandlerInput) -> bool _ = handler_input.attributes_manager.request_attributes["_"] session_attributes = handler_input.attributes_manager.session_attributes return session_attributes["repeat_prompt"] == _( data.ASK_FOR_PREFERENCES_REPROMPT) and is_intent_name( "AMAZON.YesIntent")(handler_input) def handle(self, handler_input):
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. from ask_sdk_core.dispatch_components import AbstractRequestHandler from ask_sdk_core.handler_input import HandlerInput from ask_sdk_core.utils import is_intent_name, is_request_type from ask_sdk_model import Response from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from ...data import data # Logging/tracing configuration logger = Logger(service="Save trip preferences intent handler") tracer = Tracer(service="Save trip preferences intent handler") class SaveTripPreferencesHandler(AbstractRequestHandler): """Single handler for save trip preferences Intent.""" def can_handle(self, handler_input): # type: (HandlerInput) -> bool return is_intent_name("SaveTripPreferencesIntent")(handler_input) def handle(self, handler_input): # type: (HandlerInput) -> Response logger.debug("In SaveTripPreferencesHandler")
import json from os import environ from typing import Optional import requests import six from ask_sdk_core.exceptions import ApiClientException from ask_sdk_model.services import ApiClient, ApiClientRequest, ApiClientResponse from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from urllib3.util import parse_url from .token_helper import TokenHelper logger = Logger(service="STIB API client") tracer = Tracer(service="STIB API client") OPEN_DATA_API_ENDPOINT = environ["open_data_api_endpoint"] tracer.put_metadata(key="open_data_api_endpoint", value=OPEN_DATA_API_ENDPOINT) class OpenDataAPIClient(ApiClient): """OpenData API client implementation wrapping HTTP requests""" def __init__(self): self.token_helper = TokenHelper() def invoke(self, request): # type: (ApiClientRequest) -> ApiClientResponse """Dispatches a request to an API endpoint described in the
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. from ask_sdk_core.dispatch_components import AbstractRequestHandler from ask_sdk_core.handler_input import HandlerInput from ask_sdk_core.utils import is_request_type from ask_sdk_model import Response from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from ..data import data # Logging/tracing configuration logger = Logger(service="Launch handler") tracer = Tracer(service="Launch handler") class LaunchRequestHandler(AbstractRequestHandler): """Handler for Skill Launch.""" # Todo: Make this two different launch handlers ? def can_handle(self, handler_input): # type: (HandlerInput) -> bool return is_request_type("LaunchRequest")(handler_input) def handle(self, handler_input): # type: (HandlerInput) -> Response
def test_logger_invalid_sampling_rate(): with pytest.raises(InvalidLoggerSamplingRateError): Logger(sampling_rate="TEST")
from typing import Any, Dict, IO import boto3 import imageio import rawpy from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.utilities.typing import LambdaContext from mypy_boto3_s3 import S3Client from mypy_boto3_s3.type_defs import PutObjectOutputTypeDef from mypy_boto3_sts import STSClient from common.models import JpegData, JpegDataItem, PutDdbItemAction from common.util.dataclasses import lambda_dataclass_response LOGGER = Logger(utc=True) S3_CLIENT: S3Client = boto3.client("s3") S3_EXPIRATION_DELTA_DAYS = 15 CROSS_ACCOUNT_IAM_ROLE_ARN = os.environ.get('CROSS_ACCOUNT_IAM_ROLE_ARN', '') PHOTOOPS_S3_BUCKET = os.environ['PHOTOOPS_S3_BUCKET'] PHOTOOPS_IMAGE_CACHE_PREFIX = 'cache' @dataclass class Response(PutDdbItemAction): '''Function response''' Item: JpegDataItem
def __init__(self): self.logger = Logger() self.tracer = Tracer()
# # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. from ask_sdk_core.dispatch_components import (AbstractRequestInterceptor, AbstractResponseInterceptor) from ask_sdk_core.handler_input import HandlerInput from ask_sdk_model import Response from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer # Logging/tracing configuration logger = Logger(service="Logger interceptor") tracer = Tracer(service="Logger interceptor") class RequestLoggerInterceptor(AbstractRequestInterceptor): """Request Interceptor for logging purposes.""" def process(self, handler_input): """Log the request envelope.""" # type: (HandlerInput) -> None logger.debug({"Request Envelope": handler_input.request_envelope}) class ResponseLoggerInterceptor(AbstractResponseInterceptor): """Response Interceptor for logging purposes.""" def process(self, handler_input, response):
class Utils: def __init__(self): self.logger = Logger() self.tracer = Tracer() def set_trace_id(self, lambda_handler): def get_trace_id(raw_trace_id): for t in raw_trace_id.split(';'): if t.startswith('Root'): return t.split('=')[1] return None def decorate(event, context): if '_X_AMZN_TRACE_ID' in os.environ: if get_trace_id(os.environ['_X_AMZN_TRACE_ID']) is not None: os.environ['trace_id'] = get_trace_id( os.environ['_X_AMZN_TRACE_ID']) if 'trace_id' not in os.environ: START_TIME = time.time() HEX = hex(int(START_TIME))[2:] os.environ['trace_id'] = "0-{}-{}".format( HEX, str(binascii.hexlify(urandom(12)), 'utf-8')) self.logger.structure_logs(append=True, trace_id=os.environ['trace_id']) self.logger.info(os.environ.get('trace_id')) response = lambda_handler(event, context) return response return decorate def api_gateway_response(self, lambda_handler): def decorate(event, context): response = lambda_handler(event, context) if isinstance(response, tuple): body = response[0] status = response[1] else: body = response status = 200 if isinstance(body, Exception): if status == 200: status = 400 self.logger.exception(body) self.tracer.put_annotation("ErrorCode", body.__class__.__name__) self.tracer.put_annotation("ErrorMessage", "{}".format(body)) res = { "isBase64Encoded": False, "statusCode": status, 'headers': { "Access-Control-Allow-Origin": "*", "Content-Type": "application/json" }, 'body': json.dumps({ "Code": body.__class__.__name__, "Message": "{}".format(body), "TraceId": os.environ.get('trace_id') }) } else: if body is not None and isinstance(body, str) is False: body = json.dumps(body) res = { 'isBase64Encoded': False, 'statusCode': status, 'headers': { "Access-Control-Allow-Origin": "*", "Content-Type": "application/json" } } if body is not None: res['body'] = body return res return decorate
# or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, either express or implied. See the License for the # specific language governing permissions and limitations under the # License. from ask_sdk_core.dispatch_components import AbstractExceptionHandler from ask_sdk_core.handler_input import HandlerInput from aws_lambda_powertools.logging import Logger from aws_lambda_powertools.tracing import Tracer from ...data import data from ...service.exceptions import OpenDataServiceException # Logging/tracing configuration logger = Logger(service="Exception handler") tracer = Tracer(service="Exception handler") class GenericExceptionHandler(AbstractExceptionHandler): """ Catch All Exception handler. This handler catches all kinds of exceptions and prints the stack trace on AWS Cloudwatch with the request envelope. """ def can_handle(self, handler_input, exception): # type: (HandlerInput, Exception) -> bool return True def handle(self, handler_input, exception):