8000 moving the user-agent analytics in a separate handler · localstack/localstack@114f70a · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

8000
Appearance settings

Commit 114f70a

Browse files
committed
moving the user-agent analytics in a separate handler
1 parent d8bfa32 commit 114f70a

File tree

4 files changed

+54
-39
lines changed

4 files changed

+54
-39
lines changed

localstack-core/localstack/aws/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def __init__(self, service_manager: ServiceManager = None) -> None:
7676
handlers.add_cors_response_headers,
7777
handlers.log_response,
7878
handlers.count_service_request,
79+
handlers.user_agent_request,
7980
metric_collector.update_metric_collection,
8081
]
8182
)

localstack-core/localstack/aws/handlers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
log_exception = logging.ExceptionLogger()
3636
log_response = logging.ResponseLogger()
3737
count_service_request = analytics.ServiceRequestCounter()
38+
user_agent_request = analytics.UserAgentCounter()
3839
handle_service_exception = service.ServiceExceptionSerializer()
3940
handle_internal_failure = fallback.InternalFailureHandler()
4041
serve_custom_service_request_handlers = chain.CompositeHandler()

localstack-core/localstack/aws/handlers/analytics.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import logging
22
import threading
3-
from typing import Optional
3+
from typing import Any, Optional
44

55
from localstack import config
66
from localstack.aws.api import RequestContext
77
from localstack.aws.chain import HandlerChain
88
from localstack.aws.client import parse_response
9+
from localstack.constants import INTERNAL_RESOURCE_PATH
910
from localstack.http import Response
1011
from localstack.utils.analytics.service_request_aggregator import (
1112
ServiceRequestAggregator,
1213
ServiceRequestInfo,
1314
)
15+
from localstack.utils.analytics.usage import UsageSetCounter
1416

1517
LOG = logging.getLogger(__name__)
1618

@@ -67,3 +69,52 @@ def _get_err_type(self, context: RequestContext, response: Response) -> Optional
6769
if config.DEBUG_ANALYTICS:
6870
LOG.exception("error parsing error response")
6971
return None
72+
73+
74+
class UsageCollectorFactory:
75+
_collector_registry: dict[str, Any] = {}
76+
"""Registry for the different paths."""
77+
78+
NAMESPACE_PREFIX = "agent:"
79+
"""Namespace prefix to track usage of public endpoints (_localstack/ and _aws/)."""
80+
81+
@classmethod
82+
def get_collector(cls, path: str):
83+
namespace = f"{cls.NAMESPACE_PREFIX}{path}"
84+
if namespace not in cls._collector_registry:
85+
cls._collector_registry[namespace] = UsageSetCounter(namespace)
86+
return cls._collector_registry[namespace]
87+
88+
89+
class UserAgentCounter:
90+
"""
91+
This handler collects User-Agents analytics for the LocalStack public endpoints (the ones with a _localstack or a
92+
_aws prefix).
93+
"""
94+
95+
def _record_usage(self, context: RequestContext) -> None:
96+
request_path = context.request.path
97+
user_agent = context.request.headers.get("User-Agent")
98+
if not request_path or not user_agent:
99+
return
100+
# Skip the endpoints for the new API Gateway implementation
101+
if "execute-api" in request_path:
102+
return
103+
# We only record the first segment in the path after the _internal/ or _aws/ prefix, as a path can have
104+
# potentially an infinite number of parameters.
105+
recorded_path = request_path.split("/")[:2]
106+
if len(recorded_path) < 2:
107+
return
108+
recorded_path = "/".join(recorded_path)
109+
collector = UsageCollectorFactory.get_collector(recorded_path)
110+
collector.record(user_agent)
111+
112+
def __call__(self, chain: HandlerChain, context: RequestContext, response: Response):
113+
if config.DISABLE_EVENTS:
114+
return
115+
116+
path = context.request.path
117+
if not (path.startswith(f"{INTERNAL_RESOURCE_PATH}/") or path.startswith("/_aws/")):
118+
return
119+
120+
self._record_usage(context)

localstack-core/localstack/aws/handlers/validation.py

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"""
44

55
import logging
6-
from typing import Any
76

87
from openapi_core import OpenAPI
98
from openapi_core.contrib.werkzeug import WerkzeugOpenAPIRequest, WerkzeugOpenAPIResponse
@@ -19,52 +18,16 @@
1918
from localstack.aws.chain import Handler, HandlerChain
2019
from localstack.constants import INTERNAL_RESOURCE_PATH
2120
from localstack.http import Response
22-
from localstack.utils.analytics.usage import UsageSetCounter
2321

2422
LOG = logging.getLogger(__name__)
2523

2624

27-
class UsageCollectorFactory:
28-
_collector_registry: dict[str, Any] = {}
29-
"""Registry for the different paths."""
30-
31-
NAMESPACE_PREFIX = "internal"
32-
"""Namespace prefix to track usage of public endpoints (_localstack/ and _aws/)."""
33-
34-
@classmethod
35-
def get_collector(cls, path: str):
36-
namespace = f"{cls.NAMESPACE_PREFIX}/{path}"
37-
if namespace not in cls._collector_registry:
38-
cls._collector_registry[namespace] = UsageSetCounter(namespace)
39-
return cls._collector_registry[namespace]
40-
41-
4225
class OpenAPIValidator(Handler):
4326
open_apis: list["OpenAPI"]
4427

4528
def __init__(self) -> None:
4629
self._load_specs()
4730

48-
def _record_usage(self, context: RequestContext) -> None:
49-
if config.DISABLE_EVENTS:
50-
return
51-
52-
request_path = context.request.path
53-
user_agent = context.request.headers.get("User-Agent")
54-
if not request_path or not user_agent:
55-
return
56-
# Skip the endpoints for the new API Gateway implementation
57-
if "execute-api" in request_path:
58-
return
59-
# We only record the first segment in the path after the _internal/ or _aws/ prefix, as a path can have
60-
# potentially an infinite number of parameters.
61-
recorded_path = request_path.split("/")[:2]
62-
if len(recorded_path) < 2:
63-
return
64-
recorded_path = "/".join(recorded_path)
65-
collector = UsageCollectorFactory.get_collector(recorded_path)
66-
collector.record(user_agent)
67-
6831
def _load_specs(self) -> None:
6932
"""Load the openapi spec plugins iff at least one between request and response validation is set."""
7033
if not (config.OPENAPI_VALIDATE_REQUEST or config.OPENAPI_VALIDATE_RESPONSE):
@@ -85,7 +48,6 @@ def __call__(self, chain: HandlerChain, context: RequestContext, response: Respo
8548
path = context.request.path
8649
if not (path.startswith(f"{INTERNAL_RESOURCE_PATH}/") or path.startswith("/_aws/")):
8750
return
88-
self._record_usage(context)
8951

9052
if not config.OPENAPI_VALIDATE_REQUEST:
9153
return

0 commit comments

Comments
 (0)
0