Skip to content

Commit

Permalink
Add stability as a separate column in Markdown tables (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
lmolkova committed Mar 13, 2024
1 parent 1b17951 commit 3ff0eeb
Show file tree
Hide file tree
Showing 46 changed files with 670 additions and 1,206 deletions.
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)"

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 -->
36 changes: 18 additions & 18 deletions semantic-conventions/src/tests/data/markdown/deprecated/expected.md
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

0 comments on commit 3ff0eeb

Please sign in to comment.