Skip to content

Commit a352d40

Browse files
committed
add full support for Lambda functions
1 parent 473ba1a commit a352d40

15 files changed

+381
-20
lines changed

aws_embedded_metrics/constants.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
114
DEFAULT_NAMESPACE = "aws-embedded-metrics"
15+
MAX_DIMENSIONS = 10
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
from aws_embedded_metrics.environment import Environment
15+
from aws_embedded_metrics.environment.lambda_environment import LambdaEnvironment
16+
17+
18+
async def resolve_environment() -> Environment:
19+
# TODO: this should support agent-based environments
20+
return LambdaEnvironment()

aws_embedded_metrics/environment/environment_provider.py

Lines changed: 0 additions & 9 deletions
This file was deleted.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
from aws_embedded_metrics.environment import Environment
15+
from aws_embedded_metrics.logger.metrics_context import MetricsContext
16+
from aws_embedded_metrics.sinks import Sink
17+
from aws_embedded_metrics.sinks.lambda_sink import LambdaSink
18+
import os
19+
20+
21+
def get_env(key: str) -> str:
22+
if key in os.environ:
23+
return os.environ[key]
24+
return ""
25+
26+
27+
sink = LambdaSink()
28+
29+
30+
class LambdaEnvironment(Environment):
31+
@staticmethod
32+
def probe() -> bool:
33+
return len(get_env("AWS_LAMBDA_FUNCTION_NAME")) > 0
34+
35+
@staticmethod
36+
def get_name() -> str:
37+
return LambdaEnvironment.get_log_group_name()
38+
39+
@staticmethod
40+
def get_type() -> str:
41+
return "AWS::Lambda::Function"
42+
43+
@staticmethod
44+
def get_log_group_name() -> str:
45+
return get_env("AWS_LAMBDA_FUNCTION_NAME")
46+
47+
@staticmethod
48+
def configure_context(context: MetricsContext) -> None:
49+
context.set_property("executionEnvironment", get_env("AWS_EXECUTION_ENV"))
50+
context.set_property("memorySize", get_env("AWS_LAMBDA_FUNCTION_MEMORY_SIZE"))
51+
context.set_property("functionVersion", get_env("AWS_LAMBDA_FUNCTION_VERSION"))
52+
context.set_property("logStreamId", get_env("AWS_LAMBDA_LOG_STREAM_NAME"))
53+
trace_id = get_env("_X_AMZN_TRACE_ID")
54+
55+
if len(trace_id) > 0 and "Sampled=1" in trace_id:
56+
context.set_property("traceId", trace_id)
57+
58+
@staticmethod
59+
def get_sink() -> Sink:
60+
"""Create the appropriate sink for this environment."""
61+
return sink
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
from aws_embedded_metrics.logger.metrics_logger_factory import create_metrics_logger
15+
import inspect
16+
import asyncio
17+
from functools import wraps
18+
19+
20+
def metric_scope(fn): # type: ignore
21+
logger = create_metrics_logger()
22+
23+
if asyncio.iscoroutinefunction(fn):
24+
25+
@wraps(fn)
26+
async def wrapper(*args, **kwargs): # type: ignore
27+
if "metrics" in inspect.signature(fn).parameters:
28+
kwargs["metrics"] = logger
29+
try:
30+
return await fn(*args, **kwargs)
31+
except Exception as e:
32+
raise e
33+
finally:
34+
await logger.flush()
35+
36+
return wrapper
37+
else:
38+
39+
@wraps(fn)
40+
def wrapper(*args, **kwargs): # type: ignore
41+
if "metrics" in inspect.signature(fn).parameters:
42+
kwargs["metrics"] = logger
43+
try:
44+
return fn(*args, **kwargs)
45+
except Exception as e:
46+
raise e
47+
finally:
48+
loop = asyncio.get_event_loop()
49+
loop.run_until_complete(logger.flush())
50+
51+
return wrapper

aws_embedded_metrics/logger/metrics_logger.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,39 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
114
from aws_embedded_metrics.environment import Environment
2-
from aws_embedded_metrics.environment.environment_provider import EnvironmentProvider
315
from aws_embedded_metrics.logger.metrics_context import MetricsContext
416
from aws_embedded_metrics.config import get_config
5-
from typing import Any, Dict
17+
from typing import Any, Awaitable, Callable, Dict
618

719
Config = get_config()
820

921

1022
class MetricsLogger:
1123
def __init__(
12-
self, env_provider: EnvironmentProvider, context: MetricsContext = None
24+
self,
25+
resolve_environment: Callable[..., Awaitable[Environment]],
26+
context: MetricsContext = None,
1327
):
14-
self.env_provider = env_provider
28+
self.resolve_environment = resolve_environment
1529
self.context: MetricsContext = context or MetricsContext.empty()
1630

1731
async def flush(self) -> None:
1832
# resolve the environment and get the sink
1933
# MOST of the time this will run synchonrously
2034
# This only runs asynchronously if executing for the
2135
# first time in a non-lambda environment
22-
environment = await self.env_provider.get()
36+
environment = await self.resolve_environment()
2337

2438
self.__configureContextForEnvironment(environment)
2539
sink = environment.get_sink()
@@ -56,4 +70,6 @@ def put_metric(self, key: str, value: float, unit: str = "None") -> "MetricsLogg
5670
return self
5771

5872
def new(self) -> "MetricsLogger":
59-
return MetricsLogger(self.env_provider, self.context.create_copy_with_context())
73+
return MetricsLogger(
74+
self.resolve_environment, self.context.create_copy_with_context()
75+
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
from aws_embedded_metrics.logger.metrics_context import MetricsContext
15+
from aws_embedded_metrics.logger.metrics_logger import MetricsLogger
16+
from aws_embedded_metrics.environment.environment_detector import resolve_environment
17+
18+
19+
def create_metrics_logger() -> MetricsLogger:
20+
context = MetricsContext.empty()
21+
logger = MetricsLogger(resolve_environment, context)
22+
return logger
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import abc
15+
from aws_embedded_metrics.logger.metrics_context import MetricsContext
16+
17+
18+
class Serializer(abc.ABC):
19+
@staticmethod
20+
@abc.abstractmethod
21+
def serialize(context: MetricsContext) -> str:
22+
"""Flushes the metrics context to the sink."""
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
from aws_embedded_metrics.logger.metrics_context import MetricsContext
15+
from aws_embedded_metrics.serializers import Serializer
16+
from aws_embedded_metrics.constants import MAX_DIMENSIONS
17+
import json
18+
from typing import Dict, List
19+
20+
21+
class LogSerializer(Serializer):
22+
@staticmethod
23+
def serialize(context: MetricsContext) -> str:
24+
dimension_keys = []
25+
dimensions_properties: Dict[str, str] = {}
26+
27+
for dimension_set in context.get_dimensions():
28+
keys = list(dimension_set.keys())
29+
dimension_keys.append(keys[0:MAX_DIMENSIONS])
30+
dimensions_properties = {**dimensions_properties, **dimension_set}
31+
32+
metric_pointers: List[Dict[str, str]] = []
33+
34+
metric_definitions = {
35+
"Dimensions": dimension_keys,
36+
"Metrics": metric_pointers,
37+
"Namespace": context.namespace,
38+
}
39+
cloud_watch_metrics = [metric_definitions]
40+
41+
body = {
42+
**dimensions_properties,
43+
**context.properties,
44+
"CloudWatchMetrics": cloud_watch_metrics,
45+
"Version": "0",
46+
}
47+
48+
for metric_name, metric in context.metrics.items():
49+
50+
if len(metric.values) == 1:
51+
body[metric_name] = metric.values[0]
52+
else:
53+
body[metric_name] = metric.values
54+
55+
metric_pointers.append({"Name": metric_name, "Unit": metric.unit})
56+
57+
return json.dumps(body)

aws_embedded_metrics/sinks/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
114
import abc
215
from aws_embedded_metrics.logger.metrics_context import MetricsContext
316

417

518
class Sink(abc.ABC):
619
"""The mechanism by which logs are sent to their destination."""
720

21+
@staticmethod
822
@abc.abstractmethod
9-
def name(self) -> str:
23+
def name() -> str:
1024
"""The name of the sink."""
1125

1226
@abc.abstractmethod
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2019 Amazon.com, Inc. or its affiliates.
2+
# Licensed under the Apache License, Version 2.0 (the
3+
# "License"); you may not use this file except in compliance
4+
# with the License. You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
from aws_embedded_metrics.logger.metrics_context import MetricsContext
15+
from aws_embedded_metrics.sinks import Sink
16+
from aws_embedded_metrics.serializers import Serializer
17+
from aws_embedded_metrics.serializers.log_serializer import LogSerializer
18+
19+
20+
class LambdaSink(Sink):
21+
def __init__(self, serializer: Serializer = LogSerializer()):
22+
self.serializer = serializer
23+
24+
def accept(self, context: MetricsContext) -> None:
25+
print(self.serializer.serialize(context))
26+
27+
@staticmethod
28+
def name() -> str:
29+
return "LambdaSink"

examples/lambda/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Lambda Function Example
2+
3+
1. Edit the function.py example as desired
4+
2. Create a lambda function
5+
3. Run the deploy script providing the ARN and region
6+
7+
```sh
8+
./deploy.sh arn:aws:lambda:<REGION>:<ACCOUNT>:function:<FUNCTION_NAME> <REGION>
9+
# example:
10+
# ./deploy.sh arn:aws:lambda:us-east-1:123456789012:function:MyFunction us-east-1
11+
```

0 commit comments

Comments
 (0)