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

feat(py): Expose DataCategory via C-ABI #651

Merged
merged 6 commits into from
Jul 8, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
123 changes: 98 additions & 25 deletions py/sentry_relay/consts.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,98 @@
__all__ = ["SPAN_STATUS_CODE_TO_NAME", "SPAN_STATUS_NAME_TO_CODE"]


SPAN_STATUS_CODE_TO_NAME = {
0: "ok",
1: "cancelled",
2: "unknown_error",
3: "invalid_argument",
4: "deadline_exceeded",
5: "not_found",
6: "already_exists",
7: "permission_denied",
8: "resource_exhausted",
9: "failed_precondition",
10: "aborted",
11: "out_of_range",
12: "unimplemented",
13: "internal_error",
14: "unavailable",
15: "data_loss",
16: "unauthenticated",
}

SPAN_STATUS_NAME_TO_CODE = dict((v, k) for k, v in SPAN_STATUS_CODE_TO_NAME.items())
SPAN_STATUS_NAME_TO_CODE["unknown"] = SPAN_STATUS_NAME_TO_CODE["unknown_error"]
from enum import IntEnum

from sentry_relay._lowlevel import lib
from sentry_relay.utils import decode_str, encode_str


__all__ = ["DataCategory", "SPAN_STATUS_CODE_TO_NAME", "SPAN_STATUS_NAME_TO_CODE"]


class BaseDataCategory(object):
@classmethod
def parse(cls, name):
"""
Parses a `DataCategory` from its API name.
"""
category = cls(lib.relay_data_category_parse(encode_str(name or "")))
if category == DataCategory.UNKNOWN:
return None # Unknown is a Rust-only value, replace with None
return category

@classmethod
def from_event_type(cls, event_type):
"""
Parses a `DataCategory` from an event type.
"""
s = encode_str(event_type or "")
return cls(lib.relay_data_category_from_event_type(s))

@classmethod
def event_categories(cls):
"""
Returns categories that count as events, including transactions.
"""
return frozenset(
DataCategory.DEFAULT,
DataCategory.ERROR,
DataCategory.TRANSACTION,
DataCategory.SECURITY,
)

@classmethod
def error_categories(cls):
"""
Returns categories that count as traditional error tracking events.
"""
return frozenset(
DataCategory.DEFAULT, DataCategory.ERROR, DataCategory.SECURITY
Copy link
Member Author

@jan-auer jan-auer Jul 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this changes compared to Sentry, where SECURITY was not included:

https://github.com/getsentry/sentry/blob/9f08305e09866c8bd6d0c24f5b0aabdd7dd6c59c/src/sentry/constants.py#L532-L533

This is mainly used by quotas, which will now include security events as originally intended.

)

def api_name(self):
"""
Returns the API name of the given `DataCategory`.
"""
return decode_str(lib.relay_data_category_name(self.value), free=True)


def _make_data_categories():
prefix = "RELAY_DATA_CATEGORY_"
categories = {}

for attr in dir(lib):
if not attr.startswith(prefix):
continue

category_name = attr[len(prefix) :]
categories[category_name] = getattr(lib, attr)

data_categories = IntEnum(
"DataCategory", categories, type=BaseDataCategory, module=__name__
)
globals()[data_categories.__name__] = data_categories


_make_data_categories()


SPAN_STATUS_CODE_TO_NAME = {}
SPAN_STATUS_NAME_TO_CODE = {}


def _make_span_statuses():
prefix = "RELAY_SPAN_STATUS_"

for attr in dir(lib):
if not attr.startswith(prefix):
continue

status_name = attr[len(prefix) :].lower()
Copy link
Member Author

@jan-auer jan-auer Jul 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this is actually safe - per convention the constants must always match the default serialization. That said, if we ever diverge from this in the future (which we should not!) we can call into an API for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this seems safe.

status_code = getattr(lib, attr)

SPAN_STATUS_CODE_TO_NAME[status_code] = status_name
SPAN_STATUS_NAME_TO_CODE[status_name] = status_code

# Legacy alias
SPAN_STATUS_NAME_TO_CODE["unknown_error"] = SPAN_STATUS_NAME_TO_CODE["unknown"]


_make_span_statuses()
45 changes: 45 additions & 0 deletions py/tests/test_consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from sentry_relay import DataCategory, SPAN_STATUS_CODE_TO_NAME


def test_parse_data_category():
assert DataCategory.parse("default") == DataCategory.DEFAULT
assert DataCategory.parse("transaction") == DataCategory.TRANSACTION
assert DataCategory.parse("") is None
assert DataCategory.parse(None) is None
assert DataCategory.parse("something completely different") is None


def test_data_category_from_event_type():
assert DataCategory.from_event_type("transaction") == DataCategory.TRANSACTION
# Special case!
assert DataCategory.from_event_type("default") == DataCategory.ERROR
# Anything unknown is coerced to "default", which is ERROR
assert DataCategory.from_event_type("") == DataCategory.ERROR
assert DataCategory.from_event_type(None) == DataCategory.ERROR


def test_data_category_api_name():
assert DataCategory.ERROR.api_name() == "error"


def test_span_mapping():
# This is a pure regression test to protect against accidental renames.
assert SPAN_STATUS_CODE_TO_NAME == {
0: "ok",
1: "cancelled",
2: "unknown",
3: "invalid_argument",
4: "deadline_exceeded",
5: "not_found",
6: "already_exists",
7: "permission_denied",
8: "resource_exhausted",
9: "failed_precondition",
10: "aborted",
11: "out_of_range",
12: "unimplemented",
13: "internal_error",
14: "unavailable",
15: "data_loss",
16: "unauthenticated",
}
12 changes: 11 additions & 1 deletion relay-cabi/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ language = "C"

[parse]
expand = ["relay-cabi"]
include = ["relay-common"]
parse_deps = true

[enum]
rename_variants = "QualifiedScreamingSnakeCase"
rename_variants = "ScreamingSnakeCase"
prefix_with_name = true

[export]
include = ["SpanStatus"]

[export.rename]
"DataCategory" = "RelayDataCategory"
"SpanStatus" = "RelaySpanStatus"
154 changes: 153 additions & 1 deletion relay-cabi/include/relay.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#ifndef RELAY_H_INCLUDED
#define RELAY_H_INCLUDED

/* Generated with cbindgen:0.14.2 */
/* Generated with cbindgen:0.14.3 */

/* Warning, this file is autogenerated. Do not modify this manually. */

Expand All @@ -12,6 +12,41 @@
#include <stdint.h>
#include <stdlib.h>

/**
* Classifies the type of data that is being ingested.
*/
enum RelayDataCategory {
/**
* Reserved and unused.
*/
RELAY_DATA_CATEGORY_DEFAULT,
/**
* Error events and Events with an `event_type` not explicitly listed below.
*/
RELAY_DATA_CATEGORY_ERROR,
/**
* Transaction events.
*/
RELAY_DATA_CATEGORY_TRANSACTION,
/**
* Events with an event type of `csp`, `hpkp`, `expectct` and `expectstaple`.
*/
RELAY_DATA_CATEGORY_SECURITY,
/**
* An attachment. Quantity is the size of the attachment in bytes.
*/
RELAY_DATA_CATEGORY_ATTACHMENT,
/**
* Session updates. Quantity is the number of updates in the batch.
*/
RELAY_DATA_CATEGORY_SESSION,
/**
* Any other data category not known by this Relay.
*/
RELAY_DATA_CATEGORY_UNKNOWN = -1,
};
typedef int8_t RelayDataCategory;

/**
* Controls the globbing behaviors.
*/
Expand Down Expand Up @@ -44,6 +79,108 @@ enum RelayErrorCode {
};
typedef uint32_t RelayErrorCode;

/**
* Trace status.
*
* Values from https://github.com/open-telemetry/opentelemetry-specification/blob/8fb6c14e4709e75a9aaa64b0dbbdf02a6067682a/specification/api-tracing.md#status
* Mapping to HTTP from https://github.com/open-telemetry/opentelemetry-specification/blob/8fb6c14e4709e75a9aaa64b0dbbdf02a6067682a/specification/data-http.md#status
*/
enum RelaySpanStatus {
/**
* The operation completed successfully.
*
* HTTP status 100..299 + successful redirects from the 3xx range.
*/
RELAY_SPAN_STATUS_OK = 0,
/**
* The operation was cancelled (typically by the user).
*/
RELAY_SPAN_STATUS_CANCELLED = 1,
/**
* Unknown. Any non-standard HTTP status code.
*
* "We do not know whether the transaction failed or succeeded"
*/
RELAY_SPAN_STATUS_UNKNOWN = 2,
/**
* Client specified an invalid argument. 4xx.
*
* Note that this differs from FailedPrecondition. InvalidArgument indicates arguments that
* are problematic regardless of the state of the system.
*/
RELAY_SPAN_STATUS_INVALID_ARGUMENT = 3,
/**
* Deadline expired before operation could complete.
*
* For operations that change the state of the system, this error may be returned even if the
* operation has been completed successfully.
*
* HTTP redirect loops and 504 Gateway Timeout
*/
RELAY_SPAN_STATUS_DEADLINE_EXCEEDED = 4,
/**
* 404 Not Found. Some requested entity (file or directory) was not found.
*/
RELAY_SPAN_STATUS_NOT_FOUND = 5,
/**
* Already exists (409)
*
* Some entity that we attempted to create already exists.
*/
RELAY_SPAN_STATUS_ALREADY_EXISTS = 6,
/**
* 403 Forbidden
*
* The caller does not have permission to execute the specified operation.
*/
RELAY_SPAN_STATUS_PERMISSION_DENIED = 7,
/**
* 429 Too Many Requests
*
* Some resource has been exhausted, perhaps a per-user quota or perhaps the entire file
* system is out of space.
*/
RELAY_SPAN_STATUS_RESOURCE_EXHAUSTED = 8,
/**
* Operation was rejected because the system is not in a state required for the operation's
* execution
*/
RELAY_SPAN_STATUS_FAILED_PRECONDITION = 9,
/**
* The operation was aborted, typically due to a concurrency issue.
*/
RELAY_SPAN_STATUS_ABORTED = 10,
/**
* Operation was attempted past the valid range.
*/
RELAY_SPAN_STATUS_OUT_OF_RANGE = 11,
/**
* 501 Not Implemented
*
* Operation is not implemented or not enabled.
*/
RELAY_SPAN_STATUS_UNIMPLEMENTED = 12,
/**
* Other/generic 5xx.
*/
RELAY_SPAN_STATUS_INTERNAL_ERROR = 13,
/**
* 503 Service Unavailable
*/
RELAY_SPAN_STATUS_UNAVAILABLE = 14,
/**
* Unrecoverable data loss or corruption
*/
RELAY_SPAN_STATUS_DATA_LOSS = 15,
/**
* 401 Unauthorized (actually does mean unauthenticated according to RFC 7235)
*
* Prefer PermissionDenied if a user is logged in.
*/
RELAY_SPAN_STATUS_UNAUTHENTICATED = 16,
};
typedef uint8_t RelaySpanStatus;

typedef struct RelayGeoIpLookup RelayGeoIpLookup;

/**
Expand Down Expand Up @@ -144,6 +281,21 @@ RelayStr relay_create_register_challenge(const RelayBuf *data,
const RelayStr *signature,
uint32_t max_age);

/**
* Parses a `DataCategory` from an event type.
*/
RelayDataCategory relay_data_category_from_event_type(const RelayStr *event_type);

/**
* Returns the API name of the given `DataCategory`.
*/
RelayStr relay_data_category_name(RelayDataCategory category);

/**
* Parses a `DataCategory` from its API name.
*/
RelayDataCategory relay_data_category_parse(const RelayStr *name);

/**
* Clears the last error.
*/
Expand Down
Loading