Skip to content

Commit c363239

Browse files
committed
Issue #195 add log level to log responses
also fine-tune/unify some other log handling aspects
1 parent bbb36c6 commit c363239

File tree

8 files changed

+101
-31
lines changed

8 files changed

+101
-31
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ and start a new "In Progress" section above it.
2121

2222
## In progress
2323

24+
25+
## 0.125.0
26+
27+
- Add log level to batch job logs response ([#195](https://github.com/Open-EO/openeo-python-driver/issues/195))
28+
29+
2430
## 0.124.0
2531

2632
- Better argument validation in `resample_spatial`/`resample_cube_spatial` (related to [Open-EO/openeo-python-client#690](https://github.com/Open-EO/openeo-python-client/issues/690))

openeo_driver/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.124.0a1"
1+
__version__ = "0.125.0a1"

openeo_driver/backend.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
from openeo_driver.datastructs import SarBackscatterArgs
3232
from openeo_driver.dry_run import SourceConstraint
3333
from openeo_driver.errors import CollectionNotFoundException, ServiceUnsupportedException, FeatureUnsupportedException
34-
from openeo_driver.constants import JOB_STATUS
34+
from openeo_driver.constants import JOB_STATUS, DEFAULT_LOG_LEVEL_RETRIEVAL
3535
from openeo_driver.processes import ProcessRegistry
3636
from openeo_driver.users import User
3737
from openeo_driver.users.oidc import OidcProvider
@@ -142,7 +142,15 @@ def remove_service(self, user_id: str, service_id: str) -> None:
142142
"""https://openeo.org/documentation/1.0/developers/api/reference.html#operation/delete-service"""
143143
raise NotImplementedError()
144144

145-
def get_log_entries(self, service_id: str, user_id: str, offset: str) -> List[dict]:
145+
def get_log_entries(
146+
self,
147+
service_id: str,
148+
*,
149+
user_id: str,
150+
offset: Union[str, None] = None,
151+
limit: Union[int, None] = None,
152+
level: str = DEFAULT_LOG_LEVEL_RETRIEVAL,
153+
) -> List[dict]:
146154
"""https://openeo.org/documentation/1.0/developers/api/reference.html#operation/debug-service"""
147155
# TODO require auth/user handle?
148156
return []
@@ -178,12 +186,12 @@ class LoadParameters(dict):
178186
"""
179187
A buffer provided in the units of the target CRS. If target CRS is not provided, then it is assumed to be the native CRS
180188
of the collection.
181-
182-
This buffer is applied to AOI when constructing the datacube, allowing operations that require neighbouring pixels
189+
190+
This buffer is applied to AOI when constructing the datacube, allowing operations that require neighbouring pixels
183191
to be implemented correctly. Examples are apply_kernel and apply_neighborhood, but also certain resampling operations
184192
could be affected by this.
185-
186-
The buffer has to be considered in the global extent!
193+
194+
The buffer has to be considered in the global extent!
187195
"""
188196
pixel_buffer = dict_item(default=None)
189197

@@ -552,9 +560,11 @@ def get_results(self, job_id: str, user_id: str) -> Dict[str, dict]:
552560
def get_log_entries(
553561
self,
554562
job_id: str,
563+
*,
555564
user_id: str,
556-
offset: Optional[str] = None,
557-
level: Optional[str] = None,
565+
offset: Union[str, None] = None,
566+
limit: Union[str, None] = None,
567+
level: str = DEFAULT_LOG_LEVEL_RETRIEVAL,
558568
) -> Iterable[dict]:
559569
"""
560570
https://openeo.org/documentation/1.0/developers/api/reference.html#operation/debug-job

openeo_driver/constants.py

+6
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,9 @@ class JOB_STATUS:
4949
"lower-right",
5050
"upper-right",
5151
]
52+
53+
54+
# Default value for `level` parameter in `POST /result`, `POST /jobs`, ... requests
55+
DEFAULT_LOG_LEVEL_PROCESSING = "info"
56+
# Default value for `level in `GET /jobs/{job_id}/logs`, `GET /services/{service_id}/logs` requests
57+
DEFAULT_LOG_LEVEL_RETRIEVAL = "debug"

openeo_driver/dummy/dummy_backend.py

+16-6
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
UserDefinedProcessMetadata,
4444
)
4545
from openeo_driver.config import OpenEoBackendConfig
46-
from openeo_driver.constants import JOB_STATUS, STAC_EXTENSION
46+
from openeo_driver.constants import JOB_STATUS, STAC_EXTENSION, DEFAULT_LOG_LEVEL_RETRIEVAL
4747
from openeo_driver.datacube import DriverDataCube, DriverMlModel, DriverVectorCube
4848
from openeo_driver.datastructs import StacAsset
4949
from openeo_driver.delayed_vector import DelayedVector
@@ -172,9 +172,17 @@ def list_services(self, user_id: str) -> List[ServiceMetadata]:
172172
def service_info(self, user_id: str, service_id: str) -> ServiceMetadata:
173173
return next(s for s in self._registry if s.id == service_id)
174174

175-
def get_log_entries(self, service_id: str, user_id: str, offset: str) -> List[dict]:
175+
def get_log_entries(
176+
self,
177+
service_id: str,
178+
*,
179+
user_id: str,
180+
offset: Union[str, None] = None,
181+
limit: Union[int, None] = None,
182+
level: str = DEFAULT_LOG_LEVEL_RETRIEVAL,
183+
) -> List[dict]:
176184
return [
177-
{"id": 3, "level": "info", "message": "Loaded data."}
185+
{"id": 3, "level": "info", "message": "Loaded data."},
178186
]
179187

180188

@@ -921,16 +929,18 @@ def get_result_assets(self, job_id: str, user_id: str) -> Dict[str, dict]:
921929
def get_log_entries(
922930
self,
923931
job_id: str,
932+
*,
924933
user_id: str,
925-
offset: Optional[str] = None,
926-
level: Optional[str] = None,
934+
offset: Union[str, None] = None,
935+
limit: Union[str, None] = None,
936+
level: str = DEFAULT_LOG_LEVEL_RETRIEVAL,
927937
) -> Iterable[dict]:
928938
self._get_job_info(job_id=job_id, user_id=user_id)
929939
default_logs = [{"id": "1", "level": "info", "message": "hello world"}]
930940
requested_level = normalize_log_level(level)
931941
for log in self._custom_job_logs.get(job_id, default_logs):
932942
if isinstance(log, dict):
933-
actual_level = normalize_log_level(log.get("log_level"))
943+
actual_level = normalize_log_level(log.get("level"))
934944
if actual_level < requested_level:
935945
continue
936946
yield log

openeo_driver/testing.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,11 @@ def ensure_auth_header(self):
206206
if not self.default_request_headers.get("Authorization"):
207207
self.set_auth_bearer_token()
208208

209-
def get(self, path: str, headers: dict = None) -> ApiResponse:
209+
def get(self, path: str, headers: dict = None, params: Optional[dict] = None) -> ApiResponse:
210210
"""Do versioned GET request, given non-versioned path"""
211-
return ApiResponse(self.client.get(path=self.url(path), headers=self._request_headers(headers)))
211+
return ApiResponse(
212+
self.client.get(path=self.url(path), headers=self._request_headers(headers), query_string=params)
213+
)
212214

213215
def head(self, path: str, headers: dict = None) -> ApiResponse:
214216
"""Do versioned GET request, given non-versioned path"""

openeo_driver/views.py

+25-12
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
JobListing,
4040
)
4141
from openeo_driver.config import get_backend_config, OpenEoBackendConfig
42-
from openeo_driver.constants import STAC_EXTENSION
42+
from openeo_driver.constants import STAC_EXTENSION, DEFAULT_LOG_LEVEL_RETRIEVAL, DEFAULT_LOG_LEVEL_PROCESSING
4343
from openeo_driver.datacube import DriverMlModel
4444
from openeo_driver.errors import (
4545
OpenEOApiException,
@@ -654,7 +654,7 @@ def result(user: User):
654654
process_graph = _extract_process_graph(post_data)
655655
budget = post_data.get("budget")
656656
plan = post_data.get("plan")
657-
log_level = post_data.get("log_level", "info")
657+
log_level = post_data.get("log_level", DEFAULT_LOG_LEVEL_PROCESSING)
658658
job_options = _extract_job_options(
659659
post_data, to_ignore=["process", "process_graph", "budget", "plan", "log_level"]
660660
)
@@ -880,7 +880,7 @@ def create_job(user: User):
880880
post_data, to_ignore=["process", "process_graph", "title", "description", "plan", "budget", "log_level"]
881881
)
882882
metadata = {k: post_data[k] for k in ["title", "description", "plan", "budget"] if k in post_data}
883-
metadata["log_level"] = post_data.get("log_level", "info")
883+
metadata["log_level"] = post_data.get("log_level", DEFAULT_LOG_LEVEL_PROCESSING)
884884
job_info = backend_implementation.batch_jobs.create_job(
885885
# TODO: remove `filter_supported_kwargs` (when all implementations have migrated to `user` iso `user_id`)
886886
**filter_supported_kwargs(
@@ -1513,16 +1513,26 @@ def download_job_result_signed(job_id, user_base64, secure_key, filename):
15131513
@blueprint.route("/jobs/<job_id>/logs", methods=["GET"])
15141514
@auth_handler.requires_bearer_auth
15151515
def get_job_logs(job_id, user: User):
1516-
offset = request.args.get("offset")
1517-
level = request.args.get("level", "debug")
1516+
offset = request.args.get("offset", default=None)
1517+
limit = request.args.get("limit", default=None, type=int)
1518+
level = request.args.get("level", default=DEFAULT_LOG_LEVEL_RETRIEVAL)
15181519
request_id = FlaskRequestCorrelationIdLogging.get_request_id()
15191520
# TODO: implement paging support: `limit`, next/prev/first/last `links`, ...
1520-
logs = backend_implementation.batch_jobs.get_log_entries(
1521+
1522+
# TODO: remove this `function_has_argument` once all implementations are migrated
1523+
if function_has_argument(backend_implementation.batch_jobs.get_log_entries, argument="limit"):
1524+
logs = backend_implementation.batch_jobs.get_log_entries(
1525+
job_id=job_id, user_id=user.user_id, offset=offset, level=level, limit=limit
1526+
)
1527+
else:
1528+
logs = backend_implementation.batch_jobs.get_log_entries(
15211529
job_id=job_id, user_id=user.user_id, offset=offset, level=level
15221530
)
15231531

15241532
def generate():
1525-
yield """{"logs":["""
1533+
yield "{"
1534+
yield f'"level": {json.dumps(level)},'
1535+
yield '"logs":['
15261536

15271537
sep = ""
15281538
try:
@@ -1550,7 +1560,9 @@ def generate():
15501560
}
15511561
yield sep + json.dumps(log)
15521562

1553-
yield """],"links":[]}"""
1563+
yield "],"
1564+
# TODO: add pagination links (next, prev, first, last)
1565+
yield '"links":[]}'
15541566

15551567
return current_app.response_class(generate(), mimetype="application/json")
15561568

@@ -1665,12 +1677,13 @@ def service_delete(service_id, user: User):
16651677
@blueprint.route('/services/<service_id>/logs', methods=['GET'])
16661678
@auth_handler.requires_bearer_auth
16671679
def service_logs(service_id, user: User):
1668-
level = request.args.get("level", "debug")
1669-
offset = request.args.get('offset', 0)
1680+
offset = request.args.get("offset", default=None)
1681+
limit = request.args.get("limit", default=None, type=int)
1682+
level = request.args.get("level", default=DEFAULT_LOG_LEVEL_RETRIEVAL)
16701683
logs = backend_implementation.secondary_services.get_log_entries(
1671-
service_id=service_id, user_id=user.user_id, offset=offset
1684+
service_id=service_id, user_id=user.user_id, offset=offset, limit=limit, level=level
16721685
)
1673-
return jsonify({"logs": logs, "links": []})
1686+
return jsonify({"logs": logs, "links": [], "level": level})
16741687

16751688

16761689
def register_views_udp(

tests/test_views.py

+25-2
Original file line numberDiff line numberDiff line change
@@ -3055,11 +3055,32 @@ def test_get_batch_job_logs(self, api):
30553055
with self._fresh_job_registry():
30563056
resp = api.get('/jobs/07024ee9-7847-4b8a-b260-6c879a2b3cdc/logs', headers=self.AUTH_HEADER)
30573057
assert resp.assert_status_code(200).json == {
3058+
"level": "debug",
30583059
"logs": [
3059-
{"id": "1", "level": "info", "message": "hello world"}
3060+
{"id": "1", "level": "info", "message": "hello world"},
30603061
],
3061-
"links": []
3062+
"links": [],
3063+
}
3064+
3065+
def test_get_batch_job_logs_level(self, api):
3066+
log_db = {
3067+
"07024ee9-7847-4b8a-b260-6c879a2b3cdc": [
3068+
{"id": "1", "level": "info", "message": "howdy world"},
3069+
{"id": "2", "level": "error", "message": "oh no"},
3070+
]
30623071
}
3072+
with self._fresh_job_registry():
3073+
with mock.patch.dict(dummy_backend.DummyBatchJobs._custom_job_logs, log_db):
3074+
resp = api.get(
3075+
"/jobs/07024ee9-7847-4b8a-b260-6c879a2b3cdc/logs",
3076+
headers=self.AUTH_HEADER,
3077+
params={"level": "warning"},
3078+
)
3079+
assert resp.assert_status_code(200).json == {
3080+
"level": "warning",
3081+
"logs": [{"id": "2", "level": "error", "message": "oh no"}],
3082+
"links": [],
3083+
}
30633084

30643085
def test_get_batch_job_logs_failure(self, api):
30653086
with self._fresh_job_registry():
@@ -3068,6 +3089,7 @@ def test_get_batch_job_logs_failure(self, api):
30683089
}):
30693090
resp = api.get('/jobs/07024ee9-7847-4b8a-b260-6c879a2b3cdc/logs', headers=self.AUTH_HEADER)
30703091
assert resp.assert_status_code(200).json == {
3092+
"level": "debug",
30713093
"logs": [
30723094
{
30733095
"id": "",
@@ -3170,6 +3192,7 @@ def test_delete_service_requires_authentication(self, api):
31703192
def test_service_logs_100(self, api):
31713193
logs = api.get('/services/wmts-foo/logs', headers=self.AUTH_HEADER).json
31723194
assert logs == {
3195+
"level": "debug",
31733196
"logs": [
31743197
{"id": 3, "level": "info", "message": "Loaded data."},
31753198
],

0 commit comments

Comments
 (0)