Skip to content

Commit d6b16c0

Browse files
authored
Merge pull request #124 from python-ellar/request_logging_debug
Added some log debug
2 parents 033cb82 + 672e10e commit d6b16c0

34 files changed

+351
-56
lines changed

.coveragerc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ exclude_lines =
55
def __str__
66
if self.debug:
77
if settings.DEBUG
8+
if t.TYPE_CHECKING
89
raise AssertionError
910
raise NotImplementedError
1011
if 0:

ellar/common/constants.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ def __deepcopy__(
128128

129129
NOT_SET: Any = NOT_SET_TYPE()
130130

131-
131+
ELLAR_LOG_FMT_STRING = "%(levelname)s: [%(name)s] %(message)s"
132132
DEFAULT_LOGGING = {
133133
"version": 1,
134134
"disable_existing_loggers": False,
@@ -142,6 +142,10 @@ def __deepcopy__(
142142
"()": "uvicorn.logging.AccessFormatter",
143143
"fmt": '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s',
144144
},
145+
"ellar-formatter": {
146+
"()": "logging.Formatter",
147+
"fmt": ELLAR_LOG_FMT_STRING,
148+
},
145149
},
146150
"handlers": {
147151
"default": {
@@ -154,6 +158,11 @@ def __deepcopy__(
154158
"class": "logging.StreamHandler",
155159
"stream": "ext://sys.stdout",
156160
},
161+
"ellar-default": {
162+
"formatter": "ellar-formatter",
163+
"class": "logging.StreamHandler",
164+
"stream": "ext://sys.stdout",
165+
},
157166
"console": {
158167
"class": "logging.StreamHandler",
159168
},
@@ -163,7 +172,12 @@ def __deepcopy__(
163172
"uvicorn.error": {"level": "INFO"},
164173
"uvicorn.access": {"handlers": ["access"], "level": "INFO", "propagate": False},
165174
"ellar": {
166-
"handlers": ["default"],
175+
"handlers": ["ellar-default"],
176+
"level": "INFO",
177+
"propagate": False,
178+
},
179+
"ellar.request": {
180+
"handlers": ["ellar-default"],
167181
"level": "INFO",
168182
"propagate": False,
169183
},

ellar/common/decorators/controller.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ellar.common.logger import logger
1717
from ellar.common.models import ControllerBase, ControllerType
1818
from ellar.di import RequestScope, injectable
19+
from ellar.di.scopes import DIScope
1920
from ellar.reflect import REFLECT_TYPE, reflect
2021

2122
from ..routing.controller import (
@@ -76,6 +77,7 @@ def Controller(
7677
*,
7778
name: t.Optional[str] = None,
7879
include_in_schema: bool = True,
80+
scope: t.Optional[t.Union[t.Type[DIScope], DIScope]] = RequestScope,
7981
) -> t.Union[t.Type[ControllerBase], t.Callable[..., t.Any], t.Any]:
8082
"""
8183
========= CLASS DECORATOR ==============
@@ -84,6 +86,7 @@ def Controller(
8486
:param prefix: Route Prefix default=[ControllerName]
8587
:param name: route name prefix for url reversing, eg name:route_name default=''
8688
:param include_in_schema: include controller in OPENAPI schema
89+
:param scope: Controller Instance Lifetime scope
8790
:return: t.Type[ControllerBase]
8891
"""
8992
_prefix: t.Optional[t.Any] = prefix if prefix is not None else NOT_SET
@@ -134,7 +137,8 @@ def _decorator(cls: t.Type) -> t.Type[ControllerBase]:
134137
) and not hasattr(cls, "__CONTROLLER_WATERMARK__"):
135138
reflect.define_metadata(CONTROLLER_WATERMARK, True, _controller_type)
136139
reflect_all_controller_type_routes(_controller_type)
137-
injectable(RequestScope)(cls)
140+
141+
injectable(scope or RequestScope)(cls)
138142

139143
for key in CONTROLLER_METADATA.keys:
140144
reflect.define_metadata(key, kwargs[key], _controller_type)

ellar/common/logger.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import logging
22

33
logger = logging.getLogger("ellar")
4+
request_logger = logging.getLogger("ellar.request")

ellar/common/params/args/resolver_generators.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
class BulkArgsResolverGenerator:
1818
"""
19-
This class splits Schema into different ModelFields to he resolved independently and computed back later.
19+
This class splits Schema into different ModelFields to be resolved independently and computed back later.
2020
class ASchema(BaseModel):
2121
A: int
2222
B: int

ellar/common/params/resolvers/bulk_parameter.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import typing as t
22

33
from ellar.common.interfaces import IExecutionContext
4+
from ellar.common.logger import request_logger
45
from pydantic.error_wrappers import ErrorWrapper
56
from pydantic.fields import ModelField
67

@@ -28,6 +29,9 @@ def get_model_fields(self) -> t.List[ModelField]:
2829
async def resolve_handle(
2930
self, ctx: IExecutionContext, *args: t.Any, **kwargs: t.Any
3031
) -> t.Tuple:
32+
request_logger.debug(
33+
f"Resolving Bulk Path Parameters - '{self.__class__.__name__}'"
34+
)
3135
values: t.Dict[str, t.Any] = {}
3236
errors: t.List[ErrorWrapper] = []
3337

@@ -67,6 +71,9 @@ def __init__(self, *args: t.Any, is_grouped: bool = False, **kwargs: t.Any):
6771
async def resolve_grouped_fields(
6872
self, ctx: IExecutionContext, body: t.Any
6973
) -> t.Tuple:
74+
request_logger.debug(
75+
f"Resolving Form Grouped Field - '{self.__class__.__name__}'"
76+
)
7077
value, resolver_errors = await self._get_resolver_data(ctx, body)
7178
if resolver_errors:
7279
return value, resolver_errors
@@ -100,6 +107,7 @@ async def _get_resolver_data(self, ctx: IExecutionContext, body: t.Any) -> t.Tup
100107
async def resolve_handle(
101108
self, ctx: IExecutionContext, *args: t.Any, **kwargs: t.Any
102109
) -> t.Tuple:
110+
request_logger.debug(f"Resolving Form Parameters - '{self.__class__.__name__}'")
103111
_body = await self.get_request_body(ctx)
104112
if self._resolvers:
105113
return await self._use_resolver(ctx, _body)
@@ -112,6 +120,9 @@ class BulkBodyParameterResolver(BodyParameterResolver, BulkParameterResolver):
112120
async def resolve_handle(
113121
self, ctx: IExecutionContext, *args: t.Any, **kwargs: t.Any
114122
) -> t.Tuple:
123+
request_logger.debug(
124+
f"Resolving Request Body Parameters - '{self.__class__.__name__}'"
125+
)
115126
_body = await self.get_request_body(ctx)
116127
values, errors = await super(BulkBodyParameterResolver, self).resolve_handle(
117128
ctx, *args, body=_body, **kwargs

ellar/common/params/resolvers/non_parameter/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from abc import ABC, abstractmethod
33

44
from ellar.common.interfaces import IExecutionContext
5-
from ellar.common.logger import logger
5+
from ellar.common.logger import request_logger
66
from ellar.common.types import T
77
from pydantic.error_wrappers import ErrorWrapper
88

@@ -73,7 +73,7 @@ async def resolve(
7373
value = await self.get_value(ctx)
7474
return {self.parameter_name: value}, []
7575
except Exception as ex:
76-
logger.error(
76+
request_logger.error(
7777
f"Unable to resolve `{self.lookup_connection_field}` in connection \nErrorMessage: {ex}"
7878
)
7979
return {}, [ErrorWrapper(ex, loc=str(self.parameter_name))]

ellar/common/params/resolvers/parameter.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ellar.common.datastructures import UploadFile
1313
from ellar.common.exceptions import RequestValidationError
1414
from ellar.common.interfaces import IExecutionContext
15+
from ellar.common.logger import request_logger
1516
from pydantic.error_wrappers import ErrorWrapper
1617
from pydantic.fields import Undefined
1718
from pydantic.utils import lenient_issubclass
@@ -32,6 +33,9 @@ def get_received_parameter(
3233
async def resolve_handle(
3334
self, ctx: IExecutionContext, *args: t.Any, **kwargs: t.Any
3435
) -> t.Tuple:
36+
request_logger.debug(
37+
f"Resolving Header Parameters - '{self.__class__.__name__}'"
38+
)
3539
received_params = self.get_received_parameter(ctx=ctx)
3640
if (
3741
self.model_field.shape in sequence_shapes
@@ -80,6 +84,7 @@ def get_received_parameter(cls, ctx: IExecutionContext) -> t.Mapping[str, t.Any]
8084
return connection.path_params
8185

8286
async def resolve_handle(self, ctx: IExecutionContext, **kwargs: t.Any) -> t.Tuple:
87+
request_logger.debug(f"Resolving Path Parameters - '{self.__class__.__name__}'")
8388
received_params = self.get_received_parameter(ctx=ctx)
8489
value = received_params.get(str(self.model_field.alias))
8590
self.assert_field_info()
@@ -103,6 +108,9 @@ class WsBodyParameterResolver(BaseRouteParameterResolver):
103108
async def resolve_handle(
104109
self, ctx: IExecutionContext, *args: t.Any, body: t.Any, **kwargs: t.Any
105110
) -> t.Tuple:
111+
request_logger.debug(
112+
f"Resolving Websocket Body Parameters - '{self.__class__.__name__}'"
113+
)
106114
embed = getattr(self.model_field.field_info, "embed", False)
107115
received_body = {self.model_field.alias: body}
108116
loc = ("body",)
@@ -123,6 +131,9 @@ def __init__(self, *args: t.Any, **kwargs: t.Any):
123131
super().__init__(*args, **kwargs)
124132

125133
async def get_request_body(self, ctx: IExecutionContext) -> t.Any:
134+
request_logger.debug(
135+
f"Resolving Request Body Parameters - '{self.__class__.__name__}'"
136+
)
126137
try:
127138
request = ctx.switch_to_http_connection().get_request()
128139
body_bytes = await request.body()
@@ -142,8 +153,10 @@ async def get_request_body(self, ctx: IExecutionContext) -> t.Any:
142153
body_bytes = json_body
143154
return body_bytes
144155
except json.JSONDecodeError as e:
156+
request_logger.error("JSONDecodeError: ", exc_info=True)
145157
raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))]) from e
146-
except Exception as e: # pragma: no cover
158+
except Exception as e:
159+
request_logger.error("Unable to parse the body: ", exc_info=e)
147160
raise HTTPException(
148161
status_code=400, detail="There was an error parsing the body"
149162
) from e
@@ -170,11 +183,15 @@ async def process_and_validate(
170183
return values, errors_
171184

172185
async def get_request_body(self, ctx: IExecutionContext) -> t.Any:
186+
request_logger.debug(
187+
f"Resolving Request Form Parameters - '{self.__class__.__name__}'"
188+
)
173189
try:
174190
request = ctx.switch_to_http_connection().get_request()
175191
body_bytes = await request.form()
176192
return body_bytes
177193
except Exception as e:
194+
request_logger.error("Unable to parse the body: ", exc_info=True)
178195
raise HTTPException(
179196
status_code=400, detail="There was an error parsing the body"
180197
) from e

ellar/common/responses/models/base.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from ellar.common.exceptions import RequestValidationError
66
from ellar.common.helper.modelfield import create_model_field
77
from ellar.common.interfaces import IExecutionContext, IResponseModel
8+
from ellar.common.logger import request_logger
89
from ellar.common.serializer import SerializerFilter, serialize_object
910
from ellar.reflect import reflect
1011
from pydantic import BaseModel
@@ -25,7 +26,21 @@ def serialize_if_pydantic_object(obj: t.Any) -> t.Any:
2526

2627

2728
class ResponseModelField(ModelField):
29+
"""
30+
A representation of response schema defined in route function
31+
32+
@get('/', response={200: ASchema, 404: ErrorSchema})
33+
def example():
34+
pass
35+
36+
During module building, `ASchema` and `ErrorSchema` will be converted to ResponseModelField
37+
types for the of validation and OPENAPI documentation
38+
"""
39+
2840
def validate_object(self, obj: t.Any) -> t.Any:
41+
request_logger.debug(
42+
f"Validating Response Object - '{self.__class__.__name__}'"
43+
)
2944
values, error = self.validate(obj, {}, loc=(self.alias,))
3045
if error:
3146
_errors = list(error) if isinstance(error, list) else [error]
@@ -35,6 +50,7 @@ def validate_object(self, obj: t.Any) -> t.Any:
3550
def serialize(
3651
self, obj: t.Any, serializer_filter: t.Optional[SerializerFilter] = None
3752
) -> t.Union[t.List[t.Dict], t.Dict, t.Any]:
53+
request_logger.debug(f"Serializing Response Data - '{self.__class__.__name__}'")
3854
try:
3955
values = self.validate_object(obj=obj)
4056
except RequestValidationError:
@@ -47,6 +63,21 @@ def serialize(
4763

4864

4965
class BaseResponseModel(IResponseModel, ABC):
66+
"""
67+
A base model representation of endpoint response. It provides essential information about a response type, just as
68+
it is defined on the endpoint, the status code and schema plus description for OPENAPI documentation.
69+
70+
For example:
71+
72+
@get('/', response={200: ASchema, 404: ErrorSchema})
73+
def example():
74+
pass
75+
76+
From the Above example, two response models will be generated.
77+
- response model for 200 status and ASchema and
78+
- response model for 400 status and ErrorSchema
79+
"""
80+
5081
__slots__ = (
5182
"_response_type",
5283
"_media_type",
@@ -121,7 +152,10 @@ def serialize(
121152
def create_response(
122153
self, context: IExecutionContext, response_obj: t.Any, status_code: int
123154
) -> Response:
124-
"""Cant create custom responses, Please override this function to create a custom response"""
155+
"""Please override this function to create a custom response"""
156+
request_logger.debug(
157+
f"Creating Response from returned Handler value - '{self.__class__.__name__}'"
158+
)
125159
response_args, headers = self.get_context_response(
126160
context=context, status_code=status_code
127161
)
@@ -154,6 +188,16 @@ def get_context_response(
154188

155189

156190
class ResponseModel(BaseResponseModel):
191+
"""
192+
Handles endpoint models with Response Type as schema
193+
194+
from starlette.responses import PlainTextResponse
195+
196+
@get('/', response={200: PlainTextResponse})
197+
def example():
198+
pass
199+
"""
200+
157201
def serialize(
158202
self,
159203
response_obj: t.Any,

ellar/common/responses/models/file.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from enum import Enum
33

44
from ellar.common.interfaces import IExecutionContext
5+
from ellar.common.logger import request_logger
56
from ellar.common.serializer import Serializer, SerializerFilter, serialize_object
67

78
from ..response_types import FileResponse, Response, StreamingResponse
@@ -40,6 +41,9 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
4041
def create_response(
4142
self, context: IExecutionContext, response_obj: t.Any, status_code: int
4243
) -> Response:
44+
request_logger.debug(
45+
f"Creating Response from returned Handler value - '{self.__class__.__name__}'"
46+
)
4347
response_args, headers = self.get_context_response(
4448
context=context, status_code=status_code
4549
)
@@ -82,6 +86,10 @@ def get_model_field(self) -> t.Optional[t.Union[ResponseModelField, t.Any]]:
8286
def create_response(
8387
self, context: IExecutionContext, response_obj: t.Any, status_code: int
8488
) -> Response:
89+
request_logger.debug(
90+
f"Creating Response from returned Handler value - '{self.__class__.__name__}'"
91+
)
92+
8593
response_args, headers = self.get_context_response(
8694
context=context, status_code=status_code
8795
)

ellar/common/responses/models/html.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import typing as t
22

33
from ellar.common.interfaces import IExecutionContext
4+
from ellar.common.logger import request_logger
45
from ellar.common.templating import (
56
Environment,
67
TemplateResponse,
@@ -31,6 +32,9 @@ def __init__(
3132
def create_response(
3233
self, context: IExecutionContext, response_obj: t.Any, status_code: int
3334
) -> Response:
35+
request_logger.debug(
36+
f"Creating Response from returned Handler value - '{self.__class__.__name__}'"
37+
)
3438
jinja_environment = context.get_service_provider().get(Environment)
3539
template_name = self._get_template_name(ctx=context)
3640
template_context = {

ellar/common/responses/models/json.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from ellar.common.constants import SERIALIZER_FILTER_KEY
44
from ellar.common.helper.modelfield import create_model_field
55
from ellar.common.interfaces import IExecutionContext
6+
from ellar.common.logger import request_logger
67
from ellar.common.serializer import SerializerFilter, serialize_object
78
from ellar.reflect import reflect
89

@@ -25,6 +26,9 @@ class JSONResponseModel(ResponseModel):
2526
def create_response(
2627
self, context: IExecutionContext, response_obj: t.Any, status_code: int
2728
) -> Response:
29+
request_logger.debug(
30+
f"Creating Response from returned Handler value - '{self.__class__.__name__}'"
31+
)
2832
json_response_class = t.cast(
2933
t.Type[JSONResponse],
3034
context.get_app().config.DEFAULT_JSON_CLASS or self._response_type,

0 commit comments

Comments
 (0)