Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reduce sentry errors #810

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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: 1 addition & 1 deletion bot/kodiak/entrypoints/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async def webhook_event(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid signature: X-Hub-Signature",
)
log = logger.bind(event_name=x_github_event, event=event)
log = logger.bind(event_name=x_github_event)
installation_id: int | None = event.get("installation", {}).get("id")

if github_event in {
Expand Down
36 changes: 36 additions & 0 deletions bot/kodiak/errors.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
from __future__ import annotations

from collections.abc import Iterable
from typing import Any

from typing_extensions import Literal


class RetryForSkippableChecks(Exception):
pass

Expand All @@ -15,3 +23,31 @@ def __init__(self, method: str, http_status_code: int, response: bytes) -> None:

class GitHubApiInternalServerError(Exception):
pass


def identify_github_graphql_error(
errors: Iterable[Any],
) -> set[Literal["rate_limited", "internal", "not_found", "unknown"]]:
error_kinds = (
set()
) # type: set[Literal["rate_limited", "internal","not_found", "unknown"]]
if not errors:
return error_kinds
try:
for error in errors:
if "type" in error:
if error["type"] == "RATE_LIMITED":
error_kinds.add("rate_limited")
elif error["type"] == "NOT_FOUND":
error_kinds.add("not_found")
else:
error_kinds.add("unknown")
elif "message" in error and error["message"].startswith(
"Something went wrong while executing your query."
):
error_kinds.add("internal")
else:
error_kinds.add("unknown")
except TypeError:
pass
return error_kinds
2 changes: 1 addition & 1 deletion bot/kodiak/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ async def set_status(msg: str, markdown_content: Optional[str] = None) -> None:
return

if api_call_retries_remaining == 0:
log.warning("timeout reached for api calls to GitHub")
log.info("api_call_timeout")
if api_call_errors:
first_error = api_call_errors[0]
await set_status(
Expand Down
22 changes: 17 additions & 5 deletions bot/kodiak/queries/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import httpx as http
import jwt
import pydantic
import sentry_sdk
import structlog
import toml
from mypy_extensions import TypedDict
Expand All @@ -17,6 +18,7 @@

import kodiak.app_config as conf
from kodiak.config import V1, MergeMethod
from kodiak.errors import identify_github_graphql_error
from kodiak.queries.commits import Commit, CommitConnection, GitActor
from kodiak.queries.commits import User as PullRequestCommitUser
from kodiak.queries.commits import get_commits
Expand All @@ -34,7 +36,7 @@ class ErrorLocation(TypedDict):
column: int


class GraphQLError(TypedDict):
class GraphQLError(TypedDict, total=False):
message: str
locations: List[ErrorLocation]
type: Optional[str]
Expand Down Expand Up @@ -857,9 +859,11 @@ async def send_query(
log = log.bind(rate_limit=rate_limit)
try:
res.raise_for_status()
except http.HTTPError:
log.warning("github api request error", res=res, exc_info=True)
return None
except http.HTTPStatusError as e:
with sentry_sdk.configure_scope() as scope:
scope.set_tag("http_error_code", e.response.status_code)
log.warning("github api request error", res=res, exc_info=True)
return None
return cast(GraphQLResponse, res.json())

async def get_api_features(self) -> ApiFeatures | None:
Expand Down Expand Up @@ -1014,8 +1018,16 @@ async def get_event_info(self, pr_number: int) -> Optional[EventInfoResponse]:
return None

data = res.get("data")
errors = res.get("errors")
if errors is not None:
error_kinds = identify_github_graphql_error(errors)
if "unknown" in error_kinds:
log.warning("unknown_error_found", errors=errors)
else:
log.info("api_error")

if data is None:
log.error("could not fetch event info", res=res)
log.error("no data returned in api call", res=res)
return None

repository = get_repo(data=data)
Expand Down
56 changes: 56 additions & 0 deletions bot/kodiak/test_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from __future__ import annotations

from typing import Any

import pytest

from kodiak import errors


@pytest.mark.parametrize(
"response,expected",
[
(
[
{
"message": "Something went wrong while executing your query. Please include `904B:6354:1B60F6:57C5B5:626C9136` when reporting this issue."
}
],
{"internal"},
),
(
[
{
"type": "RATE_LIMITED",
"message": "API rate limit exceeded for installation ID 000001.",
}
],
{"rate_limited"},
),
(
[{"message": "Some unexpected error"}],
{"unknown"},
),
(
[
{
"locations": [{"column": "3", "line": "147"}],
"message": "Could not resolve to a Repository with the name 'chdsbd/.github'.",
"path": ["orgConfigRepo"],
"type": "NOT_FOUND",
}
],
{"not_found"},
),
(
{},
set(),
),
(
None,
set(),
),
],
)
def test_identify_github_graphql_error(response: Any, expected: set[str]) -> None:
assert errors.identify_github_graphql_error(response) == expected