Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9a35e14
Use exception logging to capture failures in app insights
jisantuc Nov 2, 2021
eed12ed
Format
jisantuc Nov 2, 2021
7b7b645
move global exception middleware to pccommon
jisantuc Nov 2, 2021
105ca53
format
jisantuc Nov 2, 2021
ba18c6d
fix return type of middleware
jisantuc Nov 2, 2021
0a1ee32
remove unused
jisantuc Nov 2, 2021
8cbce3e
Update changelog
jisantuc Nov 2, 2021
09b56c5
wip -- log encountered exceptions
jisantuc Nov 1, 2021
1ef3f3e
include extra metadata with exception
jisantuc Nov 2, 2021
eafc80c
remove metrics reporting
jisantuc Nov 4, 2021
899249f
configure liveness paths
jisantuc Nov 4, 2021
04b6a68
remove erroneous middlewares
jisantuc Nov 5, 2021
eaacd1f
format
jisantuc Nov 5, 2021
81170e6
remove more erroneous middleware
jisantuc Nov 5, 2021
86b1189
Remove unused metrics module
jisantuc Nov 9, 2021
3e2c2d6
remove unused
jisantuc Nov 9, 2021
7127de1
Remove pctiler unused
jisantuc Nov 10, 2021
43398b8
Remove more unused
jisantuc Nov 10, 2021
dc0919b
Add special cd exception for this branch
jisantuc Nov 10, 2021
ef58fb0
run on pull requests instead 🤞🏻
jisantuc Nov 10, 2021
87fa2a9
correct tiler liveness env variable name
jisantuc Nov 10, 2021
bcd27f2
bail on LIVENESS_PATH checks
jisantuc Nov 10, 2021
fd54cfc
Format (_really_ thought I did that already)
jisantuc Nov 10, 2021
e1ee4c7
Remove pr trigger from cd
jisantuc Nov 11, 2021
6c96bba
Merge branch 'main' into feature/js/ship-errors-to-app-insights
lossyrob Dec 10, 2021
ddbdbe9
Remove unused import
mmcfarland Dec 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Organized and updated documentation markdown docs [#11](https://github.com/microsoft/planetary-computer-apis/pull/11)

### Added
- Errors are logged using exceptions and including request metadata [#14](https://github.com/microsoft/planetary-computer-apis/pull/14)
### Fixed
- Updated search limit constraints to avoid 500s [#15](https://github.com/microsoft/planetary-computer-apis/pull/15)
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,11 @@ spec:

livenessProbe:
httpGet:
path: /_mgmt/ping
path: "/_mgmt/ping"
port: http
readinessProbe:
httpGet:
path: /_mgmt/ping
path: "/_mgmt/ping"
port: http
resources:
{{- toYaml .Values.stac.deploy.resources | nindent 12 }}
Expand Down
26 changes: 0 additions & 26 deletions pccommon/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,32 +72,6 @@ def init_logging(service_name: str) -> None:
logger.info("Not adding Azure log handler since no instrumentation key defined")


def log_collection_request(
domain: str,
logger: logging.Logger,
collection_id: Optional[str],
item_id: Optional[str],
request: Request,
) -> None:
logger.info(
f"{domain} request for collection {collection_id}",
extra={
"custom_dimensions": dict(
[
(k, v)
for k, v in {
"collection_id": collection_id,
"item_id": item_id,
"url": f"{request.url}",
"service_name": domain,
}.items()
if v is not None
]
)
},
)


def request_to_path(request: Request) -> str:
parsed_url = urlparse(f"{request.url}")
return parsed_url.path
22 changes: 0 additions & 22 deletions pccommon/metrics.py

This file was deleted.

32 changes: 32 additions & 0 deletions pccommon/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import logging
from typing import Awaitable, Callable

from fastapi import HTTPException, Request, Response

from pccommon.logging import request_to_path
from pccommon.tracing import HTTP_METHOD, HTTP_PATH, HTTP_URL

logger = logging.getLogger(__name__)


async def handle_exceptions(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
try:
return await call_next(request)
except HTTPException:
raise
except Exception as e:
logger.exception(
"Exception when handling request",
extra={
"custom_dimensions": {
"stackTrace": f"{e}",
HTTP_URL: str(request.url),
HTTP_METHOD: str(request.method),
HTTP_PATH: request_to_path(request),
"service": "tiler",
}
},
)
raise
2 changes: 2 additions & 0 deletions pccommon/tracing.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import os

from opencensus.ext.azure.trace_exporter import AzureExporter
from opencensus.trace.attributes_helper import COMMON_ATTRIBUTES

Expand Down
7 changes: 4 additions & 3 deletions pcstac/pcstac/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@
from starlette.responses import PlainTextResponse

from pccommon.logging import init_logging
from pccommon.middleware import handle_exceptions
from pccommon.openapi import fixup_schema
from pcstac.api import PCStacApi
from pcstac.client import PCClient
from pcstac.config import API_DESCRIPTION, API_TITLE, API_VERSION, get_settings
from pcstac.errors import PC_DEFAULT_STATUS_CODES
from pcstac.middleware import count_collection_requests, trace_request
from pcstac.middleware import trace_request
from pcstac.search import PCItemCollectionUri, PCSearch, PCSearchGetRequest

DEBUG: bool = os.getenv("DEBUG") == "TRUE" or False
Expand Down Expand Up @@ -90,10 +91,10 @@


@app.middleware("http")
async def _count_collection_requests(
async def _handle_exceptions(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
return await count_collection_requests(request, call_next)
return await handle_exceptions(request, call_next)


@app.middleware("http")
Expand Down
49 changes: 5 additions & 44 deletions pcstac/pcstac/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,11 @@
from typing import Awaitable, Callable, Optional, Tuple

from fastapi import Request, Response
from opencensus.stats import aggregation as aggregation_module
from opencensus.stats import measure as measure_module
from opencensus.stats import view as view_module
from opencensus.tags import tag_map as tag_map_module
from opencensus.trace.samplers import ProbabilitySampler
from opencensus.trace.span import SpanKind
from opencensus.trace.tracer import Tracer

from pccommon.logging import log_collection_request, request_to_path
from pccommon.metrics import stats_recorder, view_manager
from pccommon.logging import request_to_path
from pccommon.tracing import (
HTTP_METHOD,
HTTP_PATH,
Expand All @@ -23,25 +18,7 @@

logger = logging.getLogger(__name__)


browse_request_measure = measure_module.MeasureInt(
"browse-request-count",
"Browsing requests under a given collection",
"browse-request",
)
browse_request_view = view_module.View(
"Browse request view",
browse_request_measure.description,
["collection", "item", "requestPath"],
browse_request_measure,
aggregation_module.CountAggregation(),
)

_log_metrics = view_manager and stats_recorder and exporter

if _log_metrics:
view_manager.register_view(browse_request_view)
mmap = stats_recorder.new_measurement_map()
_log_metrics = exporter is not None

collection_id_re = re.compile(
r".*/collections/?(?P<collection_id>[a-zA-Z0-9\-\%]+)?(/items/(?P<item_id>.*))?.*" # noqa
Expand All @@ -63,7 +40,8 @@ def _collection_item_from_request(
async def trace_request(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
if _log_metrics:
request_path = request_to_path(request).strip("/")
if _log_metrics and request.method.lower() != "head":
tracer = Tracer(
exporter=exporter,
sampler=ProbabilitySampler(1.0),
Expand All @@ -86,7 +64,7 @@ async def trace_request(
attribute_key=HTTP_URL, attribute_value=f"{request.url}"
)
tracer.add_attribute_to_current_span(
attribute_key=HTTP_PATH, attribute_value=request_to_path(request)
attribute_key=HTTP_PATH, attribute_value=request_path
)
tracer.add_attribute_to_current_span(
attribute_key=HTTP_METHOD, attribute_value=str(request.method)
Expand All @@ -109,20 +87,3 @@ async def trace_request(
return response
else:
return await call_next(request)


async def count_collection_requests(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
if _log_metrics:
tmap = tag_map_module.TagMap()
(collection_id, item_id) = _collection_item_from_request(request)
if collection_id:
tmap.insert("collection", collection_id)
if item_id:
tmap.insert("item", item_id)
log_collection_request("stac", logger, collection_id, item_id, request)
tmap.insert("requestPath", request_to_path(request))
mmap.measure_int_put(browse_request_measure, 1)
mmap.record(tmap)
return await call_next(request)
11 changes: 6 additions & 5 deletions pctiler/pctiler/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
from titiler.core.errors import DEFAULT_STATUS_CODES, add_exception_handlers

from pccommon.logging import init_logging
from pccommon.middleware import handle_exceptions
from pccommon.openapi import fixup_schema
from pctiler.config import get_settings
from pctiler.db import close_db_connection, connect_to_db
from pctiler.endpoints import item, legend, pg_mosaic
from pctiler.middleware import count_data_requests, trace_request
from pctiler.middleware import trace_request

# Initialize logging
init_logging("tiler")
Expand Down Expand Up @@ -59,17 +60,17 @@


@app.middleware("http")
async def _count_data_requests(
async def _trace_requests(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
return await count_data_requests(request, call_next)
return await trace_request(request, call_next)


@app.middleware("http")
async def _trace_requests(
async def _handle_exceptions(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
return await trace_request(request, call_next)
return await handle_exceptions(request, call_next)


add_exception_handlers(
Expand Down
72 changes: 5 additions & 67 deletions pctiler/pctiler/middleware.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import logging
from typing import Awaitable, Callable
from urllib.parse import urlparse

from fastapi import Request, Response
from opencensus.stats import aggregation as aggregation_module
from opencensus.stats import measure as measure_module
from opencensus.stats import view as view_module
from opencensus.tags import tag_map as tag_map_module
from opencensus.trace.samplers import ProbabilitySampler
from opencensus.trace.span import SpanKind
from opencensus.trace.tracer import Tracer

from pccommon.logging import log_collection_request, request_to_path
from pccommon.metrics import stats_recorder, view_manager
from pccommon.logging import request_to_path
from pccommon.tracing import (
HTTP_METHOD,
HTTP_PATH,
Expand All @@ -23,51 +17,14 @@

logger = logging.getLogger(__name__)

# This list was extracted from every `get` query param in the
# OpenAPI specification at
# http https://planetarycomputer.microsoft.com/api/data/v1/openapi.json
# If a string doesn't appear in the list of attributes in a view, we can't
# send it as a tag to application insights.
# The Azure exporter for python opencensus doesn't seem to want to send more
# than 10 tags, so this list is a bespoke selection from the full list of
# query params, also with the requestPath.
all_query_params = [
"assets",
"collection",
"colormap_name",
"expression",
"format",
"height",
"item",
"items",
"width",
"requestPath",
]

data_request_count_measure = measure_module.MeasureInt(
"data-request-count",
"Requests for data (info, bounds, maps, etc.)",
"data-request-count",
)
data_request_count_view = view_module.View(
"Data request count view",
data_request_count_measure.description,
all_query_params,
data_request_count_measure,
aggregation_module.CountAggregation(),
)

_log_metrics = view_manager and stats_recorder and exporter

if _log_metrics:
view_manager.register_view(data_request_count_view)
mmap = stats_recorder.new_measurement_map()
_log_metrics = exporter is not None


async def trace_request(
request: Request, call_next: Callable[[Request], Awaitable[Response]]
) -> Response:
if _log_metrics:
request_path = request_to_path(request).strip("/")
if _log_metrics and request.method.lower() != "head":
tracer = Tracer(
exporter=exporter,
sampler=ProbabilitySampler(1.0),
Expand All @@ -85,7 +42,7 @@ async def trace_request(
response = await call_next(request)

tracer.add_attribute_to_current_span(
attribute_key=HTTP_PATH, attribute_value=request_to_path(request)
attribute_key=HTTP_PATH, attribute_value=request_path
)
tracer.add_attribute_to_current_span(
attribute_key=HTTP_STATUS_CODE, attribute_value=response.status_code
Expand Down Expand Up @@ -114,22 +71,3 @@ async def trace_request(
return response
else:
return await call_next(request)


async def count_data_requests(request: Request, call_next): # type: ignore
if _log_metrics:
tmap = tag_map_module.TagMap()
for k, v in request.query_params.items():
tmap.insert(k, v)
parsed_url = urlparse(f"{request.url}")
tmap.insert("requestPath", parsed_url.path)
mmap.measure_int_put(data_request_count_measure, 1)
mmap.record(tmap)
log_collection_request(
"tiler",
logger,
request.query_params.get("collection"),
request.query_params.get("item"),
request,
)
return await call_next(request)