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

Add stability as a separate column in Markdown tables #278

Merged
merged 10 commits into from
Mar 13, 2024
Merged
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: 2 additions & 0 deletions semantic-conventions/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Please update the changelog as part of any significant pull request.
([#267](https://github.com/open-telemetry/build-tools/pull/267))
- Change minimum support python version to 3.10 in setup.cfg and Dockerfile
([#285](https://github.com/open-telemetry/build-tools/pull/285))
- BREAKING: Add dedicated column for stability to Markdown tables.
([#278](https://github.com/open-telemetry/build-tools/pull/278))
- BREAKING: Make stability required (also: fix ref and extends, render badges on metrics).
([#272](https://github.com/open-telemetry/build-tools/pull/272))
- BREAKING: Make stability and deprecation independent properties.
Expand Down
3 changes: 3 additions & 0 deletions semantic-conventions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ After `{semantic_convention_id}`, optional parameters enclosed in parentheses ca
- `ref`: prints attributes that are referenced from another semantic convention;
- `remove_constraint`: does not print additional constraints of the semantic convention.

By default markdown tables are rendered with stability badges (like ![Stable](https://img.shields.io/badge/-stable-lightgreen) or ![Experimental](https://img.shields.io/badge/-experimental-blue)) which can be disabled with `--md-disable-stable-badge`, `--md-disable-experimental-badge`, `--md-disable-deprecated-badge`.
When badges are disabled, the stability column contains plain text representation of stability or deprecation status.

### Examples

These examples assume that a semantic convention with the id `http.server` extends another semantic convention with the id `http`.
Expand Down
30 changes: 12 additions & 18 deletions semantic-conventions/src/opentelemetry/semconv/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,9 @@ def main():
def process_markdown(semconv, args):
options = MarkdownOptions(
check_only=args.md_check,
enable_stable=args.md_stable,
enable_experimental=args.md_experimental,
enable_deprecated=args.md_enable_deprecated,
use_badge=args.md_use_badges,
disable_stable_badge=args.md_disable_stable,
disable_experimental_badge=args.md_disable_experimental,
disable_deprecated_badge=args.md_disable_deprecated,
break_count=args.md_break_conditional,
exclude_files=exclude_file_list(args.markdown_root, args.exclude),
)
Expand Down Expand Up @@ -221,31 +220,26 @@ def add_md_parser(subparsers):
required=False,
)
parser.add_argument(
"--md-use-badges",
help="Use stability badges instead of labels for attributes.",
"--md-disable-stable-badge",
help="Removes badges from attributes marked as stable.",
required=False,
default=False,
action="store_true",
)
parser.add_argument(
"--md-stable",
help="Add labels to attributes marked as stable.",
"--md-disable-experimental-badge",
help="Removes badges from attributes marked as experimental.",
required=False,
default=False,
action="store_true",
)
parser.add_argument(
"--md-experimental",
help="Add labels to attributes marked as experimental.",
"--md-disable-deprecated-badge",
help="Removes badges from attributes marked as deprecated.",
required=False,
default=False,
action="store_true",
)
parser.add_argument(
"--md-disable-deprecated",
help="Removes deprecated notes of deprecated attributes.",
required=False,
default=True,
dest="md_enable_deprecated",
action="store_false",
)


def add_compat_check_parser(subparsers):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@

from .utils import VisualDiffer

_OPENTELEMETRY_IO_SPEC_URL = "https://opentelemetry.io/docs/specs/"
_REQUIREMENT_LEVEL_URL = (
"https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/"
_OPENTELEMETRY_IO_SPEC_URL + "semconv/general/attribute-requirement-level/"
)


Expand Down Expand Up @@ -105,11 +106,12 @@ def __init__(
req_level = f"[Requirement Level]({_REQUIREMENT_LEVEL_URL})"

self.table_headers = (
f"| Attribute | Type | Description | Examples | {req_level} |"
"\n|---|---|---|---|---|\n"
f"| Attribute | Type | Description | Examples | {req_level} | Stability |"
"\n|---|---|---|---|---|---|\n"
)
self.table_headers_omitting_req_level = (
"| Attribute | Type | Description | Examples |\n|---|---|---|---|\n"
"| Attribute | Type | Description | Examples | Stability |"
"\n|---|---|---|---|---|\n"
)

def to_markdown_attr(
Expand All @@ -126,10 +128,7 @@ def to_markdown_attr(
if isinstance(attribute.attr_type, EnumAttributeType)
else AttributeType.get_instantiated_type(attribute.attr_type)
)
description = (
self._description_with_badge(attribute.stability, attribute.deprecated)
+ attribute.brief
)
description = attribute.brief
if attribute.note:
self.render_ctx.add_note(attribute.note)
description += f" [{len(self.render_ctx.notes)}]"
Expand All @@ -156,12 +155,15 @@ def to_markdown_attr(
else:
examples = "; ".join(f"`{ex}`" for ex in example_list)

stability = self._render_stability(attribute)
if self.render_ctx.is_omit_requirement_level:
output.write(f"| {name} | {attr_type} | {description} | {examples} |\n")
output.write(
f"| {name} | {attr_type} | {description} | {examples} | {stability} |\n"
)
else:
required = self.derive_requirement_level(attribute)
output.write(
f"| {name} | {attr_type} | {description} | {examples} | {required} |\n"
f"| {name} | {attr_type} | {description} | {examples} | {required} | {stability} |\n"
)

def derive_requirement_level(self, attribute: SemanticAttribute):
Expand All @@ -175,7 +177,7 @@ def derive_requirement_level(self, attribute: SemanticAttribute):
self.render_ctx.add_note(attribute.requirement_level_msg)
required = f"`Conditionally Required` [{len(self.render_ctx.notes)}]"
elif attribute.requirement_level == RequirementLevel.OPT_IN:
required = "Opt-In"
required = "`Opt-In`"
else: # attribute.requirement_level == Required.RECOMMENDED or None
# check if there are any notes
if (
Expand Down Expand Up @@ -240,21 +242,20 @@ def to_markdown_metric_table(
instrument = MetricSemanticConvention.canonical_instrument_name_by_yaml_name[
semconv.instrument
]

output.write(
"| Name | Instrument Type | Unit (UCUM) | Description |\n"
"| -------- | --------------- | ----------- | -------------- |\n"
"| Name | Instrument Type | Unit (UCUM) | Description | Stability |\n"
"| -------- | --------------- | ----------- | -------------- | --------- |\n"
)

description = (
self._description_with_badge(semconv.stability, semconv.deprecated)
+ semconv.brief
)
description = semconv.brief
if semconv.note:
self.render_ctx.add_note(semconv.note)
description += f" [{len(self.render_ctx.notes)}]"

stability = self._render_stability(semconv)
output.write(
f"| `{semconv.metric_name}` | {instrument} | `{semconv.unit}` | {description} |\n"
f"| `{semconv.metric_name}` | {instrument} | `{semconv.unit}` | {description} | {stability} |\n"
)
self.to_markdown_notes(output)

Expand Down Expand Up @@ -328,20 +329,18 @@ def to_markdown_enum(self, output: io.StringIO):
else:
output.write("MUST be one of the following:")
output.write("\n\n")
output.write("| Value | Description |\n|---|---|")
output.write("| Value | Description | Stability |\n|---|---|---|")
member: EnumMember
counter = 1
notes = []
for member in enum.members:
description = (
self._description_with_badge(member.stability, member.deprecated)
+ member.brief
)
description = member.brief
if member.note:
description += f" [{counter}]"
counter += 1
notes.append(member.note)
output.write(f"\n| `{member.value}` | {description} |")
stability = self._render_stability(member)
output.write(f"\n| `{member.value}` | {description} | {stability} |")
counter = 1
if not notes:
output.write("\n")
Expand Down Expand Up @@ -537,20 +536,15 @@ def _render_group(self, semconv, parameters, output):

output.write("<!-- endsemconv -->")

def _description_with_badge(self, stability: StabilityLevel, deprecated: str):
description = ""
if deprecated and self.options.enable_deprecated:
if "deprecated" in deprecated.lower():
description = f"**{deprecated}**<br>"
else:
deprecated_msg = self.options.deprecated_md_snippet().format(deprecated)
description = f"{deprecated_msg}<br>"
elif stability == StabilityLevel.STABLE and self.options.enable_stable:
description = f"{self.options.stable_md_snippet()}<br>"
elif (
stability == StabilityLevel.EXPERIMENTAL
and self.options.enable_experimental
):
description = f"{self.options.experimental_md_snippet()}<br>"

return description
def _render_stability(
self,
item: typing.Union[SemanticAttribute | BaseSemanticConvention | EnumMember],
):
if item.deprecated:
return self.options.deprecated_md_snippet(item.deprecated)
if item.stability == StabilityLevel.STABLE:
return self.options.stable_md_snippet()
if item.stability == StabilityLevel.EXPERIMENTAL:
return self.options.experimental_md_snippet()

raise ValueError(f"Unknown stability level {item.stability}")
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@
@dataclass()
class MarkdownOptions:
check_only: bool = False
enable_stable: bool = False
enable_experimental: bool = False
enable_deprecated: bool = True
use_badge: bool = False
disable_stable_badge: bool = False
disable_experimental_badge: bool = False
disable_deprecated_badge: bool = False
break_count: int = 50
exclude_files: List[str] = field(default_factory=list)

def stable_md_snippet(self):
if self.use_badge:
return "![Stable](https://img.shields.io/badge/-stable-lightgreen)"
return "**Stable**"
if self.disable_stable_badge:
return "Stable"
return "![Stable](https://img.shields.io/badge/-stable-lightgreen)"
lmolkova marked this conversation as resolved.
Show resolved Hide resolved

def experimental_md_snippet(self):
if self.use_badge:
return "![Experimental](https://img.shields.io/badge/-experimental-blue)"
return "**Experimental**"
if self.disable_experimental_badge:
return "Experimental"
return "![Experimental](https://img.shields.io/badge/-experimental-blue)"

def deprecated_md_snippet(self):
if self.use_badge:
return "![Deprecated](https://img.shields.io/badge/-deprecated-red)"
return "**Deprecated: {}**"
def deprecated_md_snippet(self, deprecated_note: str):
if self.disable_deprecated_badge:
return f"Deprecated: {deprecated_note}"

return f"![Deprecated](https://img.shields.io/badge/-deprecated-red)<br>{deprecated_note}"
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Attribute Group Example

<!-- semconv span_attribute_group -->
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) |
|---|---|---|---|---|
| `foo.bar` | string | Attribute 1 | `baz` | `Recommended` if available |
| `foo.qux` | int | Attribute 2 | `42` | `Conditionally Required` if available |
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |
|---|---|---|---|---|---|
| `foo.bar` | string | Attribute 1 | `baz` | `Recommended` if available | Experimental |
| `foo.qux` | int | Attribute 2 | `42` | `Conditionally Required` if available | Experimental |
<!-- endsemconv -->
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Custom HTTP Semantic Conventions

<!-- semconv custom_http(full) -->
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) |
|---|---|---|---|---|
| `custom_http.request.header.<key>` | string[] | HTTP request headers, `<key>` being the normalized HTTP Header name (lowercase, with - characters replaced by _), the value being the header values. | ``http.request.header.content_type=["application/json"]`` | `Recommended` |
| `custom_http.request.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` |
| `general.some_general_attribute.<key>` | string | This is a general attribute. | ``some_general_attribute.some_key="abc"`` | `Recommended` |
| `referenced_http.request.referenced.header.<key>` | string[] | This is a referenced attribute. | ``http.request.header.content_type=["application/json"]`` | `Recommended` |
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |
|---|---|---|---|---|---|
| `custom_http.request.header.<key>` | string[] | HTTP request headers, `<key>` being the normalized HTTP Header name (lowercase, with - characters replaced by _), the value being the header values. | ``http.request.header.content_type=["application/json"]`` | `Recommended` | Experimental |
| `custom_http.request.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` | Experimental |
| `general.some_general_attribute.<key>` | string | This is a general attribute. | ``some_general_attribute.some_key="abc"`` | `Recommended` | Experimental |
| `referenced_http.request.referenced.header.<key>` | string[] | This is a referenced attribute. | ``http.request.header.content_type=["application/json"]`` | `Recommended` | Experimental |
<!-- endsemconv -->
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@

<!-- Re-generate TOC with `TODO: ADD cmd` -->
<!-- semconv http -->
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) |
|---|---|---|---|---|
| `http.flavor` | string | **Deprecated. Use attribute `flavor_new` instead.**<br>Kind of HTTP protocol used [1] | `1.0` | `Recommended` |
| `http.host` | string | The value of the [HTTP host header](https://tools.ietf.org/html/rfc7230#section-5.4). When the header is empty or not present, this attribute should be the same. | `www.example.org` | `Recommended` |
| `http.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` |
| `http.scheme` | string | The URI scheme identifying the used protocol. | `http`; `https` | `Recommended` |
| `http.status_code` | int | [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6). | `200` | `Conditionally Required` if and only if one was received/sent |
| `http.status_text` | string | **Deprecated: Use attribute `status_description` instead.**<br>[HTTP reason phrase](https://tools.ietf.org/html/rfc7230#section-3.1.2). | `OK` | `Recommended` |
| `http.target` | string | The full request target as passed in a HTTP request line or equivalent. | `/path/12314/?q=ddds#123` | `Recommended` |
| `http.url` | string | Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`. Usually the fragment is not transmitted over HTTP, but if it is known, it should be included nevertheless. | `https://www.foo.bar/search?q=OpenTelemetry#SemConv` | `Recommended` |
| `http.user_agent` | string | Value of the [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) header sent by the client. | `CERN-LineMode/2.15 libwww/2.17b3` | `Recommended` |
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |
|---|---|---|---|---|---|
| `http.flavor` | string | Kind of HTTP protocol used [1] | `1.0` | `Recommended` | Deprecated: Use attribute `flavor_new` instead. |
| `http.host` | string | The value of the [HTTP host header](https://tools.ietf.org/html/rfc7230#section-5.4). When the header is empty or not present, this attribute should be the same. | `www.example.org` | `Recommended` | Experimental |
| `http.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` | Experimental |
| `http.scheme` | string | The URI scheme identifying the used protocol. | `http`; `https` | `Recommended` | Experimental |
| `http.status_code` | int | [HTTP response status code](https://tools.ietf.org/html/rfc7231#section-6). | `200` | `Conditionally Required` if and only if one was received/sent | Experimental |
| `http.status_text` | string | [HTTP reason phrase](https://tools.ietf.org/html/rfc7230#section-3.1.2). | `OK` | `Recommended` | Deprecated: Use attribute `status_description` instead. |
| `http.target` | string | The full request target as passed in a HTTP request line or equivalent. | `/path/12314/?q=ddds#123` | `Recommended` | Experimental |
| `http.url` | string | Full HTTP request URL in the form `scheme://host[:port]/path?query[#fragment]`. Usually the fragment is not transmitted over HTTP, but if it is known, it should be included nevertheless. | `https://www.foo.bar/search?q=OpenTelemetry#SemConv` | `Recommended` | Experimental |
| `http.user_agent` | string | Value of the [HTTP User-Agent](https://tools.ietf.org/html/rfc7231#section-5.5.3) header sent by the client. | `CERN-LineMode/2.15 libwww/2.17b3` | `Recommended` | Experimental |

**[1]:** If `net.transport` is not specified, it can be assumed to be `IP.TCP` except if `http.flavor` is `QUIC`, in which case `IP.UDP` is assumed.

`http.flavor` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used.

| Value | Description |
|---|---|
| `1.0` | HTTP 1.0 |
| `1.1` | HTTP 1.1 |
| `2.0` | HTTP 2 |
| `SPDY` | SPDY protocol. |
| `QUIC` | QUIC protocol. |
| Value | Description | Stability |
|---|---|---|
| `1.0` | HTTP 1.0 | Experimental |
| `1.1` | HTTP 1.1 | Experimental |
| `2.0` | HTTP 2 | Experimental |
| `SPDY` | SPDY protocol. | Experimental |
| `QUIC` | QUIC protocol. | Experimental |
<!-- endsemconv -->

It is recommended to also use the general [network attributes][], especially `net.peer.ip`. If `net.transport` is not specified, it can be assumed to be `IP.TCP` except if `http.flavor` is `QUIC`, in which case `IP.UDP` is assumed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ groups:
brief: 'QUIC protocol.'
stability: experimental
brief: 'Kind of HTTP protocol used'
deprecated: Deprecated. Use attribute `flavor_new` instead.
deprecated: Use attribute `flavor_new` instead.
note: >
If `net.transport` is not specified, it can be assumed to be `IP.TCP` except if `http.flavor`
is `QUIC`, in which case `IP.UDP` is assumed.
Expand Down
Loading
Loading