Skip to content

Commit 434b445

Browse files
authored
chore: increment statsd as warn (#23041)
1 parent 92b9c06 commit 434b445

File tree

5 files changed

+115
-63
lines changed

5 files changed

+115
-63
lines changed

superset/utils/decorators.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,17 @@ def wrapped(*args: Any, **kwargs: Any) -> Any:
4545
current_app.config["STATS_LOGGER"].gauge(f"{metric_prefix_}.ok", 1)
4646
return result
4747
except Exception as ex:
48-
current_app.config["STATS_LOGGER"].gauge(f"{metric_prefix_}.error", 1)
48+
if (
49+
hasattr(ex, "status")
50+
and ex.status < 500 # type: ignore # pylint: disable=no-member
51+
):
52+
current_app.config["STATS_LOGGER"].gauge(
53+
f"{metric_prefix_}.warning", 1
54+
)
55+
else:
56+
current_app.config["STATS_LOGGER"].gauge(
57+
f"{metric_prefix_}.error", 1
58+
)
4959
raise ex
5060

5161
return wrapped

superset/views/base_api.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,13 @@ def wraps(self: BaseSupersetApiMixin, *args: Any, **kwargs: Any) -> Response:
112112
try:
113113
duration, response = time_function(f, self, *args, **kwargs)
114114
except Exception as ex:
115-
self.incr_stats("error", func_name)
115+
if (
116+
hasattr(ex, "status")
117+
and ex.status < 500 # type: ignore # pylint: disable=no-member
118+
):
119+
self.incr_stats("warning", func_name)
120+
else:
121+
self.incr_stats("error", func_name)
116122
raise ex
117123

118124
self.send_stats_metrics(response, func_name, duration)
@@ -205,6 +211,8 @@ def send_stats_metrics(
205211
"""
206212
if 200 <= response.status_code < 400:
207213
self.incr_stats("success", key)
214+
elif 400 <= response.status_code < 500:
215+
self.incr_stats("warning", key)
208216
else:
209217
self.incr_stats("error", key)
210218
if time_delta:

tests/integration_tests/base_tests.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ def post_assert_metric(
9090
rv = client.post(uri, json=data)
9191
if 200 <= rv.status_code < 400:
9292
mock_method.assert_called_once_with("success", func_name)
93+
elif 400 <= rv.status_code < 500:
94+
mock_method.assert_called_once_with("warning", func_name)
9395
else:
9496
mock_method.assert_called_once_with("error", func_name)
9597
return rv
@@ -455,6 +457,8 @@ def get_assert_metric(self, uri: str, func_name: str) -> Response:
455457
rv = self.client.get(uri)
456458
if 200 <= rv.status_code < 400:
457459
mock_method.assert_called_once_with("success", func_name)
460+
elif 400 <= rv.status_code < 500:
461+
mock_method.assert_called_once_with("warning", func_name)
458462
else:
459463
mock_method.assert_called_once_with("error", func_name)
460464
return rv
@@ -474,6 +478,8 @@ def delete_assert_metric(self, uri: str, func_name: str) -> Response:
474478
rv = self.client.delete(uri)
475479
if 200 <= rv.status_code < 400:
476480
mock_method.assert_called_once_with("success", func_name)
481+
elif 400 <= rv.status_code < 500:
482+
mock_method.assert_called_once_with("warning", func_name)
477483
else:
478484
mock_method.assert_called_once_with("error", func_name)
479485
return rv
@@ -501,6 +507,8 @@ def put_assert_metric(
501507
rv = self.client.put(uri, json=data)
502508
if 200 <= rv.status_code < 400:
503509
mock_method.assert_called_once_with("success", func_name)
510+
elif 400 <= rv.status_code < 500:
511+
mock_method.assert_called_once_with("warning", func_name)
504512
else:
505513
mock_method.assert_called_once_with("error", func_name)
506514
return rv

tests/integration_tests/utils/decorators_tests.py

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
19+
from contextlib import nullcontext
20+
from enum import Enum
21+
from inspect import isclass
22+
from typing import Any, Optional
23+
from unittest.mock import call, Mock, patch
24+
25+
import pytest
26+
27+
from superset import app
28+
from superset.utils import decorators
29+
30+
31+
class ResponseValues(str, Enum):
32+
FAIL = "fail"
33+
WARN = "warn"
34+
OK = "ok"
35+
36+
37+
def test_debounce() -> None:
38+
mock = Mock()
39+
40+
@decorators.debounce()
41+
def myfunc(arg1: int, arg2: int, kwarg1: str = "abc", kwarg2: int = 2) -> int:
42+
mock(arg1, kwarg1)
43+
return arg1 + arg2 + kwarg2
44+
45+
# should be called only once when arguments don't change
46+
myfunc(1, 1)
47+
myfunc(1, 1)
48+
result = myfunc(1, 1)
49+
mock.assert_called_once_with(1, "abc")
50+
assert result == 4
51+
52+
# kwarg order shouldn't matter
53+
myfunc(1, 0, kwarg2=2, kwarg1="haha")
54+
result = myfunc(1, 0, kwarg1="haha", kwarg2=2)
55+
mock.assert_has_calls([call(1, "abc"), call(1, "haha")])
56+
assert result == 3
57+
58+
59+
@pytest.mark.parametrize(
60+
"response_value, expected_exception, expected_result",
61+
[
62+
(ResponseValues.OK, None, "custom.prefix.ok"),
63+
(ResponseValues.FAIL, ValueError, "custom.prefix.error"),
64+
(ResponseValues.WARN, FileNotFoundError, "custom.prefix.warn"),
65+
],
66+
)
67+
def test_statsd_gauge(
68+
response_value: str, expected_exception: Optional[Exception], expected_result: str
69+
) -> None:
70+
@decorators.statsd_gauge("custom.prefix")
71+
def my_func(response: ResponseValues, *args: Any, **kwargs: Any) -> str:
72+
if response == ResponseValues.FAIL:
73+
raise ValueError("Error")
74+
if response == ResponseValues.WARN:
75+
raise FileNotFoundError("Not found")
76+
return "OK"
77+
78+
with patch.object(app.config["STATS_LOGGER"], "gauge") as mock:
79+
cm = (
80+
pytest.raises(expected_exception)
81+
if isclass(expected_exception) and issubclass(expected_exception, Exception)
82+
else nullcontext()
83+
)
84+
85+
with cm:
86+
my_func(response_value, 1, 2)
87+
mock.assert_called_once_with(expected_result, 1)

0 commit comments

Comments
 (0)