Skip to content

Commit dcb272c

Browse files
authored
Merge branch 'develop' into time2
2 parents 70da961 + e17db6c commit dcb272c

File tree

23 files changed

+735
-366
lines changed

23 files changed

+735
-366
lines changed

.github/boring-cyborg.yml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,48 @@
11
##### Labeler ##########################################################################################################
22
labelPRBasedOnFilePath:
3-
area/logger:
3+
logger:
44
- aws_lambda_powertools/logging/*
55
- aws_lambda_powertools/logging/**/*
66
- aws_lambda_powertools/package_logger.py
7-
area/tracer:
7+
tracer:
88
- aws_lambda_powertools/tracing/*
99
- aws_lambda_powertools/tracing/**/*
10-
area/metrics:
10+
metrics:
1111
- aws_lambda_powertools/metrics/*
1212
- aws_lambda_powertools/metrics/**/*
13-
area/event_handlers:
13+
event_handlers:
1414
- aws_lambda_powertools/event_handler/*
1515
- aws_lambda_powertools/event_handler/**/*
16-
area/middleware_factory:
16+
middleware_factory:
1717
- aws_lambda_powertools/middleware_factory/*
1818
- aws_lambda_powertools/middleware_factory/**/*
19-
area/parameters:
19+
parameters:
2020
- aws_lambda_powertools/parameters/*
2121
- aws_lambda_powertools/parameters/**/*
22-
area/batch:
22+
batch:
2323
- aws_lambda_powertools/batch/*
2424
- aws_lambda_powertools/batch/**/*
25-
area/validator:
25+
validator:
2626
- aws_lambda_powertools/validation/*
2727
- aws_lambda_powertools/validation/**/*
28-
area/event_sources:
28+
event_sources:
2929
- aws_lambda_powertools/data_classes/*
3030
- aws_lambda_powertools/data_classes/**/*
31-
area/parser:
31+
parser:
3232
- aws_lambda_powertools/parser/*
3333
- aws_lambda_powertools/parser/**/*
34-
area/idempotency:
34+
idempotency:
3535
- aws_lambda_powertools/idempotency/*
3636
- aws_lambda_powertools/idempotency/**/*
37-
area/feature_flags:
37+
feature_flags:
3838
- aws_lambda_powertools/feature_flags/*
3939
- aws_lambda_powertools/feature_flags/**/*
40-
area/jmespath:
40+
jmespath:
4141
- aws_lambda_powertools/utilities/jmespath_utils/*
42-
area/typing:
42+
typing:
4343
- aws_lambda_powertools/utilities/typing/*
4444
- mypy.ini
45-
area/commons:
45+
commons:
4646
- aws_lambda_powertools/shared/*
4747

4848
documentation:

CHANGELOG.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@
44
<a name="unreleased"></a>
55
# Unreleased
66

7+
## Features
8+
9+
* **metrics:** add EphemeralMetrics as a non-singleton option ([#1676](https://github.com/awslabs/aws-lambda-powertools-python/issues/1676))
10+
11+
12+
<a name="v2.1.0"></a>
13+
## [v2.1.0] - 2022-10-31
714
## Bug Fixes
815

916
* **ci:** linting issues after flake8-blackbear,mypy upgrades
1017
* **deps:** update build system to poetry-core ([#1651](https://github.com/awslabs/aws-lambda-powertools-python/issues/1651))
18+
* **idempotency:** idempotent_function should support standalone falsy values ([#1669](https://github.com/awslabs/aws-lambda-powertools-python/issues/1669))
19+
* **logger:** fix unknown attributes being ignored by mypy ([#1670](https://github.com/awslabs/aws-lambda-powertools-python/issues/1670))
1120

1221
## Documentation
1322

1423
* **community:** fix social handlers for Ran ([#1654](https://github.com/awslabs/aws-lambda-powertools-python/issues/1654))
1524
* **community:** fix twitch parent domain for embedded video
25+
* **homepage:** remove 3.6 and add hero image
1626
* **homepage:** add Pulumi code example ([#1652](https://github.com/awslabs/aws-lambda-powertools-python/issues/1652))
1727
* **index:** fold support us banner
1828
* **index:** add quotes to pip for zsh customers
@@ -22,16 +32,21 @@
2232
## Features
2333

2434
* **layers:** add layer balancer script ([#1643](https://github.com/awslabs/aws-lambda-powertools-python/issues/1643))
35+
* **logger:** add use_rfc3339 and auto-complete formatter opts in Logger ([#1662](https://github.com/awslabs/aws-lambda-powertools-python/issues/1662))
2536
* **logger:** accept arbitrary keyword=value for ephemeral metadata ([#1658](https://github.com/awslabs/aws-lambda-powertools-python/issues/1658))
2637

2738
## Maintenance
2839

40+
* update v2 layer ARN on documentation
2941
* **ci:** fix typo on version description
30-
* **deps:** bump docker/setup-qemu-action from 2.0.0 to 2.1.0 ([#1627](https://github.com/awslabs/aws-lambda-powertools-python/issues/1627))
3142
* **deps:** bump peaceiris/actions-gh-pages from 3.8.0 to 3.9.0 ([#1649](https://github.com/awslabs/aws-lambda-powertools-python/issues/1649))
43+
* **deps:** bump docker/setup-qemu-action from 2.0.0 to 2.1.0 ([#1627](https://github.com/awslabs/aws-lambda-powertools-python/issues/1627))
44+
* **deps-dev:** bump aws-cdk-lib from 2.47.0 to 2.48.0 ([#1664](https://github.com/awslabs/aws-lambda-powertools-python/issues/1664))
3245
* **deps-dev:** bump flake8-variables-names from 0.0.4 to 0.0.5 ([#1628](https://github.com/awslabs/aws-lambda-powertools-python/issues/1628))
3346
* **deps-dev:** bump pytest-asyncio from 0.16.0 to 0.20.1 ([#1635](https://github.com/awslabs/aws-lambda-powertools-python/issues/1635))
47+
* **deps-dev:** bump aws-cdk-lib from 2.48.0 to 2.49.0 ([#1671](https://github.com/awslabs/aws-lambda-powertools-python/issues/1671))
3448
* **docs:** remove v2 banner on top of the docs
49+
* **governance:** remove 'area/' from PR labels
3550

3651

3752
<a name="v2.0.0"></a>
@@ -2524,7 +2539,8 @@
25242539
* Merge pull request [#5](https://github.com/awslabs/aws-lambda-powertools-python/issues/5) from jfuss/feat/python38
25252540

25262541

2527-
[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.0.0...HEAD
2542+
[Unreleased]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.1.0...HEAD
2543+
[v2.1.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v2.0.0...v2.1.0
25282544
[v2.0.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.31.1...v2.0.0
25292545
[v1.31.1]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.31.0...v1.31.1
25302546
[v1.31.0]: https://github.com/awslabs/aws-lambda-powertools-python/compare/v1.30.0...v1.31.0

aws_lambda_powertools/logging/formatter.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ class LambdaPowertoolsFormatter(BasePowertoolsFormatter):
6262

6363
default_time_format = "%Y-%m-%d %H:%M:%S,%F%z" # '2021-04-17 18:19:57,656+0200'
6464
custom_ms_time_directive = "%F"
65+
RFC3339_ISO8601_FORMAT = "%Y-%m-%dT%H:%M:%S.%F%z" # '2022-10-27T16:27:43.738+02:00'
6566

6667
def __init__(
6768
self,
@@ -72,6 +73,7 @@ def __init__(
7273
use_datetime_directive: bool = False,
7374
log_record_order: Optional[List[str]] = None,
7475
utc: bool = False,
76+
use_rfc3339: bool = False,
7577
**kwargs,
7678
):
7779
"""Return a LambdaPowertoolsFormatter instance.
@@ -106,6 +108,9 @@ def __init__(
106108
also supports a custom %F directive for milliseconds.
107109
utc : bool, optional
108110
set logging timestamp to UTC, by default False to continue to use local time as per stdlib
111+
use_rfc3339: bool, optional
112+
Whether to use a popular dateformat that complies with both RFC3339 and ISO8601.
113+
e.g., 2022-10-27T16:27:43.738+02:00.
109114
log_record_order : list, optional
110115
set order of log keys when logging, by default ["level", "location", "message", "timestamp"]
111116
kwargs
@@ -129,6 +134,7 @@ def __init__(
129134
self.log_record_order = log_record_order or ["level", "location", "message", "timestamp"]
130135
self.log_format = dict.fromkeys(self.log_record_order) # Set the insertion order for the log messages
131136
self.update_formatter = self.append_keys # alias to old method
137+
self.use_rfc3339_iso8601 = use_rfc3339
132138

133139
if self.utc:
134140
self.converter = time.gmtime # type: ignore
@@ -153,36 +159,51 @@ def format(self, record: logging.LogRecord) -> str: # noqa: A003
153159
return self.serialize(log=formatted_log)
154160

155161
def formatTime(self, record: logging.LogRecord, datefmt: Optional[str] = None) -> str:
162+
# As of Py3.7, we can infer milliseconds directly from any datetime
163+
# saving processing time as we can shortcircuit early
164+
# Maintenance: In V3, we (and Java) should move to this format by default
165+
# since we've provided enough time for those migrating from std logging
166+
if self.use_rfc3339_iso8601:
167+
if self.utc:
168+
ts_as_datetime = datetime.fromtimestamp(record.created, tz=timezone.utc)
169+
else:
170+
ts_as_datetime = datetime.fromtimestamp(record.created).astimezone()
171+
172+
return ts_as_datetime.isoformat(timespec="milliseconds") # 2022-10-27T17:42:26.841+0200
173+
174+
# converts to local/UTC TZ as struct time
156175
record_ts = self.converter(record.created) # type: ignore
157176

158177
if datefmt is None: # pragma: no cover, it'll always be None in std logging, but mypy
159178
datefmt = self.datefmt
160179

161180
# NOTE: Python `time.strftime` doesn't provide msec directives
162-
# so we create a custom one (%F) and replace logging record ts
181+
# so we create a custom one (%F) and replace logging record_ts
163182
# Reason 2 is that std logging doesn't support msec after TZ
164183
msecs = "%03d" % record.msecs
165184

166-
# Datetime format codes might be optionally used
167-
# however it only makes a difference if `datefmt` is passed
168-
# since format codes are the same except %f
185+
# Datetime format codes is a superset of time format codes
186+
# therefore we only honour them if explicitly asked
187+
# by default, those migrating from std logging will use time format codes
188+
# https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes
169189
if self.use_datetime_directive and datefmt:
170-
# record.msecs are microseconds, divide by 1000 and we get milliseconds
190+
# record.msecs are microseconds, divide by 1000 to get milliseconds
171191
timestamp = record.created + record.msecs / 1000
172192

173193
if self.utc:
174194
dt = datetime.fromtimestamp(timestamp, tz=timezone.utc)
175195
else:
176-
# make sure local timezone is included
177196
dt = datetime.fromtimestamp(timestamp).astimezone()
178197

179198
custom_fmt = datefmt.replace(self.custom_ms_time_directive, msecs)
180199
return dt.strftime(custom_fmt)
181200

201+
# Only time format codes being used
182202
elif datefmt:
183203
custom_fmt = datefmt.replace(self.custom_ms_time_directive, msecs)
184204
return time.strftime(custom_fmt, record_ts)
185205

206+
# Use default fmt: 2021-05-03 10:20:19,650+0200
186207
custom_fmt = self.default_time_format.replace(self.custom_ms_time_directive, msecs)
187208
return time.strftime(custom_fmt, record_ts)
188209

aws_lambda_powertools/logging/logger.py

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,19 @@
66
import random
77
import sys
88
import traceback
9-
from typing import IO, Any, Callable, Dict, Iterable, Mapping, Optional, TypeVar, Union
9+
from typing import (
10+
IO,
11+
TYPE_CHECKING,
12+
Any,
13+
Callable,
14+
Dict,
15+
Iterable,
16+
List,
17+
Mapping,
18+
Optional,
19+
TypeVar,
20+
Union,
21+
)
1022

1123
import jmespath
1224

@@ -86,14 +98,16 @@ class Logger(logging.Logger): # lgtm [py/missing-call-to-init]
8698
Parameters propagated to LambdaPowertoolsFormatter
8799
--------------------------------------------------
88100
datefmt: str, optional
89-
String directives (strftime) to format log timestamp using `time`, by default it uses RFC
90-
3339.
101+
String directives (strftime) to format log timestamp using `time`, by default it uses 2021-05-03 11:47:12,494+0200. # noqa: E501
91102
use_datetime_directive: bool, optional
92103
Interpret `datefmt` as a format string for `datetime.datetime.strftime`, rather than
93104
`time.strftime`.
94105
95106
See https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior . This
96107
also supports a custom %F directive for milliseconds.
108+
use_rfc3339: bool, optional
109+
Whether to use a popular date format that complies with both RFC3339 and ISO8601.
110+
e.g., 2022-10-27T16:27:43.738+02:00.
97111
json_serializer : Callable, optional
98112
function to serialize `obj` to a JSON formatted `str`, by default json.dumps
99113
json_deserializer : Callable, optional
@@ -187,6 +201,14 @@ def __init__(
187201
stream: Optional[IO[str]] = None,
188202
logger_formatter: Optional[PowertoolsFormatter] = None,
189203
logger_handler: Optional[logging.Handler] = None,
204+
json_serializer: Optional[Callable[[Dict], str]] = None,
205+
json_deserializer: Optional[Callable[[Union[Dict, str, bool, int, float]], str]] = None,
206+
json_default: Optional[Callable[[Any], Any]] = None,
207+
datefmt: Optional[str] = None,
208+
use_datetime_directive: bool = False,
209+
log_record_order: Optional[List[str]] = None,
210+
utc: bool = False,
211+
use_rfc3339: bool = False,
190212
**kwargs,
191213
):
192214
self.service = resolve_env_var_choice(
@@ -205,12 +227,29 @@ def __init__(
205227
self._default_log_keys = {"service": self.service, "sampling_rate": self.sampling_rate}
206228
self._logger = self._get_logger()
207229

208-
self._init_logger(**kwargs)
209-
210-
def __getattr__(self, name):
211-
# Proxy attributes not found to actual logger to support backward compatibility
212-
# https://github.com/awslabs/aws-lambda-powertools-python/issues/97
213-
return getattr(self._logger, name)
230+
# NOTE: This is primarily to improve UX, so IDEs can autocomplete LambdaPowertoolsFormatter options
231+
# previously, we masked all of them as kwargs thus limiting feature discovery
232+
formatter_options = {
233+
"json_serializer": json_serializer,
234+
"json_deserializer": json_deserializer,
235+
"json_default": json_default,
236+
"datefmt": datefmt,
237+
"use_datetime_directive": use_datetime_directive,
238+
"log_record_order": log_record_order,
239+
"utc": utc,
240+
"use_rfc3339": use_rfc3339,
241+
}
242+
243+
self._init_logger(formatter_options=formatter_options, **kwargs)
244+
245+
# Prevent __getattr__ from shielding unknown attribute errors in type checkers
246+
# https://github.com/awslabs/aws-lambda-powertools-python/issues/1660
247+
if not TYPE_CHECKING:
248+
249+
def __getattr__(self, name):
250+
# Proxy attributes not found to actual logger to support backward compatibility
251+
# https://github.com/awslabs/aws-lambda-powertools-python/issues/97
252+
return getattr(self._logger, name)
214253

215254
def _get_logger(self):
216255
"""Returns a Logger named {self.service}, or {self.service.filename} for child loggers"""
@@ -220,7 +259,7 @@ def _get_logger(self):
220259

221260
return logging.getLogger(logger_name)
222261

223-
def _init_logger(self, **kwargs):
262+
def _init_logger(self, formatter_options: Optional[Dict] = None, **kwargs):
224263
"""Configures new logger"""
225264

226265
# Skip configuration if it's a child logger or a pre-configured logger
@@ -235,7 +274,7 @@ def _init_logger(self, **kwargs):
235274
self._configure_sampling()
236275
self._logger.setLevel(self.log_level)
237276
self._logger.addHandler(self.logger_handler)
238-
self.structure_logs(**kwargs)
277+
self.structure_logs(formatter_options=formatter_options, **kwargs)
239278

240279
# Maintenance: We can drop this upon Py3.7 EOL. It's a backport for "location" key to work
241280
self._logger.findCaller = self.findCaller
@@ -501,19 +540,23 @@ def registered_formatter(self) -> BasePowertoolsFormatter:
501540
"""Convenience property to access logger formatter"""
502541
return self.registered_handler.formatter # type: ignore
503542

504-
def structure_logs(self, append: bool = False, **keys):
543+
def structure_logs(self, append: bool = False, formatter_options: Optional[Dict] = None, **keys):
505544
"""Sets logging formatting to JSON.
506545
507546
Optionally, it can append keyword arguments
508-
to an existing logger so it is available across future log statements.
547+
to an existing logger, so it is available across future log statements.
509548
510549
Last keyword argument and value wins if duplicated.
511550
512551
Parameters
513552
----------
514553
append : bool, optional
515554
append keys provided to logger formatter, by default False
555+
formatter_options : dict, optional
556+
LambdaPowertoolsFormatter options to be propagated, by default {}
516557
"""
558+
formatter_options = formatter_options or {}
559+
517560
# There are 3 operational modes for this method
518561
## 1. Register a Powertools Formatter for the first time
519562
## 2. Append new keys to the current logger formatter; deprecated in favour of append_keys
@@ -523,7 +566,7 @@ def structure_logs(self, append: bool = False, **keys):
523566
log_keys = {**self._default_log_keys, **keys}
524567
is_logger_preconfigured = getattr(self._logger, "init", False)
525568
if not is_logger_preconfigured:
526-
formatter = self.logger_formatter or LambdaPowertoolsFormatter(**log_keys) # type: ignore
569+
formatter = self.logger_formatter or LambdaPowertoolsFormatter(**formatter_options, **log_keys) # type: ignore # noqa: E501
527570
self.registered_handler.setFormatter(formatter)
528571

529572
# when using a custom Lambda Powertools Formatter

aws_lambda_powertools/metrics/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
from .base import MetricUnit
44
from .exceptions import MetricUnitError, MetricValueError, SchemaValidationError
55
from .metric import single_metric
6-
from .metrics import Metrics
6+
from .metrics import EphemeralMetrics, Metrics
77

88
__all__ = [
99
"Metrics",
10+
"EphemeralMetrics",
1011
"single_metric",
1112
"MetricUnit",
1213
"MetricUnitError",

0 commit comments

Comments
 (0)