Skip to content

Commit 3ff0eeb

Browse files
authored
Add stability as a separate column in Markdown tables (#278)
1 parent 1b17951 commit 3ff0eeb

File tree

46 files changed

+670
-1206
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+670
-1206
lines changed

semantic-conventions/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Please update the changelog as part of any significant pull request.
88
([#267](https://github.com/open-telemetry/build-tools/pull/267))
99
- Change minimum support python version to 3.10 in setup.cfg and Dockerfile
1010
([#285](https://github.com/open-telemetry/build-tools/pull/285))
11+
- BREAKING: Add dedicated column for stability to Markdown tables.
12+
([#278](https://github.com/open-telemetry/build-tools/pull/278))
1113
- BREAKING: Make stability required (also: fix ref and extends, render badges on metrics).
1214
([#272](https://github.com/open-telemetry/build-tools/pull/272))
1315
- BREAKING: Make stability and deprecation independent properties.

semantic-conventions/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ After `{semantic_convention_id}`, optional parameters enclosed in parentheses ca
6767
- `ref`: prints attributes that are referenced from another semantic convention;
6868
- `remove_constraint`: does not print additional constraints of the semantic convention.
6969

70+
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`.
71+
When badges are disabled, the stability column contains plain text representation of stability or deprecation status.
72+
7073
### Examples
7174

7275
These examples assume that a semantic convention with the id `http.server` extends another semantic convention with the id `http`.

semantic-conventions/src/opentelemetry/semconv/main.py

+12-18
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,9 @@ def main():
9191
def process_markdown(semconv, args):
9292
options = MarkdownOptions(
9393
check_only=args.md_check,
94-
enable_stable=args.md_stable,
95-
enable_experimental=args.md_experimental,
96-
enable_deprecated=args.md_enable_deprecated,
97-
use_badge=args.md_use_badges,
94+
disable_stable_badge=args.md_disable_stable,
95+
disable_experimental_badge=args.md_disable_experimental,
96+
disable_deprecated_badge=args.md_disable_deprecated,
9897
break_count=args.md_break_conditional,
9998
exclude_files=exclude_file_list(args.markdown_root, args.exclude),
10099
)
@@ -221,31 +220,26 @@ def add_md_parser(subparsers):
221220
required=False,
222221
)
223222
parser.add_argument(
224-
"--md-use-badges",
225-
help="Use stability badges instead of labels for attributes.",
223+
"--md-disable-stable-badge",
224+
help="Removes badges from attributes marked as stable.",
226225
required=False,
226+
default=False,
227227
action="store_true",
228228
)
229229
parser.add_argument(
230-
"--md-stable",
231-
help="Add labels to attributes marked as stable.",
230+
"--md-disable-experimental-badge",
231+
help="Removes badges from attributes marked as experimental.",
232232
required=False,
233+
default=False,
233234
action="store_true",
234235
)
235236
parser.add_argument(
236-
"--md-experimental",
237-
help="Add labels to attributes marked as experimental.",
237+
"--md-disable-deprecated-badge",
238+
help="Removes badges from attributes marked as deprecated.",
238239
required=False,
240+
default=False,
239241
action="store_true",
240242
)
241-
parser.add_argument(
242-
"--md-disable-deprecated",
243-
help="Removes deprecated notes of deprecated attributes.",
244-
required=False,
245-
default=True,
246-
dest="md_enable_deprecated",
247-
action="store_false",
248-
)
249243

250244

251245
def add_compat_check_parser(subparsers):

semantic-conventions/src/opentelemetry/semconv/templating/markdown/__init__.py

+35-41
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@
4141

4242
from .utils import VisualDiffer
4343

44+
_OPENTELEMETRY_IO_SPEC_URL = "https://opentelemetry.io/docs/specs/"
4445
_REQUIREMENT_LEVEL_URL = (
45-
"https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/"
46+
_OPENTELEMETRY_IO_SPEC_URL + "semconv/general/attribute-requirement-level/"
4647
)
4748

4849

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

107108
self.table_headers = (
108-
f"| Attribute | Type | Description | Examples | {req_level} |"
109-
"\n|---|---|---|---|---|\n"
109+
f"| Attribute | Type | Description | Examples | {req_level} | Stability |"
110+
"\n|---|---|---|---|---|---|\n"
110111
)
111112
self.table_headers_omitting_req_level = (
112-
"| Attribute | Type | Description | Examples |\n|---|---|---|---|\n"
113+
"| Attribute | Type | Description | Examples | Stability |"
114+
"\n|---|---|---|---|---|\n"
113115
)
114116

115117
def to_markdown_attr(
@@ -126,10 +128,7 @@ def to_markdown_attr(
126128
if isinstance(attribute.attr_type, EnumAttributeType)
127129
else AttributeType.get_instantiated_type(attribute.attr_type)
128130
)
129-
description = (
130-
self._description_with_badge(attribute.stability, attribute.deprecated)
131-
+ attribute.brief
132-
)
131+
description = attribute.brief
133132
if attribute.note:
134133
self.render_ctx.add_note(attribute.note)
135134
description += f" [{len(self.render_ctx.notes)}]"
@@ -156,12 +155,15 @@ def to_markdown_attr(
156155
else:
157156
examples = "; ".join(f"`{ex}`" for ex in example_list)
158157

158+
stability = self._render_stability(attribute)
159159
if self.render_ctx.is_omit_requirement_level:
160-
output.write(f"| {name} | {attr_type} | {description} | {examples} |\n")
160+
output.write(
161+
f"| {name} | {attr_type} | {description} | {examples} | {stability} |\n"
162+
)
161163
else:
162164
required = self.derive_requirement_level(attribute)
163165
output.write(
164-
f"| {name} | {attr_type} | {description} | {examples} | {required} |\n"
166+
f"| {name} | {attr_type} | {description} | {examples} | {required} | {stability} |\n"
165167
)
166168

167169
def derive_requirement_level(self, attribute: SemanticAttribute):
@@ -175,7 +177,7 @@ def derive_requirement_level(self, attribute: SemanticAttribute):
175177
self.render_ctx.add_note(attribute.requirement_level_msg)
176178
required = f"`Conditionally Required` [{len(self.render_ctx.notes)}]"
177179
elif attribute.requirement_level == RequirementLevel.OPT_IN:
178-
required = "Opt-In"
180+
required = "`Opt-In`"
179181
else: # attribute.requirement_level == Required.RECOMMENDED or None
180182
# check if there are any notes
181183
if (
@@ -240,21 +242,20 @@ def to_markdown_metric_table(
240242
instrument = MetricSemanticConvention.canonical_instrument_name_by_yaml_name[
241243
semconv.instrument
242244
]
245+
243246
output.write(
244-
"| Name | Instrument Type | Unit (UCUM) | Description |\n"
245-
"| -------- | --------------- | ----------- | -------------- |\n"
247+
"| Name | Instrument Type | Unit (UCUM) | Description | Stability |\n"
248+
"| -------- | --------------- | ----------- | -------------- | --------- |\n"
246249
)
247250

248-
description = (
249-
self._description_with_badge(semconv.stability, semconv.deprecated)
250-
+ semconv.brief
251-
)
251+
description = semconv.brief
252252
if semconv.note:
253253
self.render_ctx.add_note(semconv.note)
254254
description += f" [{len(self.render_ctx.notes)}]"
255255

256+
stability = self._render_stability(semconv)
256257
output.write(
257-
f"| `{semconv.metric_name}` | {instrument} | `{semconv.unit}` | {description} |\n"
258+
f"| `{semconv.metric_name}` | {instrument} | `{semconv.unit}` | {description} | {stability} |\n"
258259
)
259260
self.to_markdown_notes(output)
260261

@@ -328,20 +329,18 @@ def to_markdown_enum(self, output: io.StringIO):
328329
else:
329330
output.write("MUST be one of the following:")
330331
output.write("\n\n")
331-
output.write("| Value | Description |\n|---|---|")
332+
output.write("| Value | Description | Stability |\n|---|---|---|")
332333
member: EnumMember
333334
counter = 1
334335
notes = []
335336
for member in enum.members:
336-
description = (
337-
self._description_with_badge(member.stability, member.deprecated)
338-
+ member.brief
339-
)
337+
description = member.brief
340338
if member.note:
341339
description += f" [{counter}]"
342340
counter += 1
343341
notes.append(member.note)
344-
output.write(f"\n| `{member.value}` | {description} |")
342+
stability = self._render_stability(member)
343+
output.write(f"\n| `{member.value}` | {description} | {stability} |")
345344
counter = 1
346345
if not notes:
347346
output.write("\n")
@@ -537,20 +536,15 @@ def _render_group(self, semconv, parameters, output):
537536

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

540-
def _description_with_badge(self, stability: StabilityLevel, deprecated: str):
541-
description = ""
542-
if deprecated and self.options.enable_deprecated:
543-
if "deprecated" in deprecated.lower():
544-
description = f"**{deprecated}**<br>"
545-
else:
546-
deprecated_msg = self.options.deprecated_md_snippet().format(deprecated)
547-
description = f"{deprecated_msg}<br>"
548-
elif stability == StabilityLevel.STABLE and self.options.enable_stable:
549-
description = f"{self.options.stable_md_snippet()}<br>"
550-
elif (
551-
stability == StabilityLevel.EXPERIMENTAL
552-
and self.options.enable_experimental
553-
):
554-
description = f"{self.options.experimental_md_snippet()}<br>"
555-
556-
return description
539+
def _render_stability(
540+
self,
541+
item: typing.Union[SemanticAttribute | BaseSemanticConvention | EnumMember],
542+
):
543+
if item.deprecated:
544+
return self.options.deprecated_md_snippet(item.deprecated)
545+
if item.stability == StabilityLevel.STABLE:
546+
return self.options.stable_md_snippet()
547+
if item.stability == StabilityLevel.EXPERIMENTAL:
548+
return self.options.experimental_md_snippet()
549+
550+
raise ValueError(f"Unknown stability level {item.stability}")

semantic-conventions/src/opentelemetry/semconv/templating/markdown/options.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,24 @@
1919
@dataclass()
2020
class MarkdownOptions:
2121
check_only: bool = False
22-
enable_stable: bool = False
23-
enable_experimental: bool = False
24-
enable_deprecated: bool = True
25-
use_badge: bool = False
22+
disable_stable_badge: bool = False
23+
disable_experimental_badge: bool = False
24+
disable_deprecated_badge: bool = False
2625
break_count: int = 50
2726
exclude_files: List[str] = field(default_factory=list)
2827

2928
def stable_md_snippet(self):
30-
if self.use_badge:
31-
return "![Stable](https://img.shields.io/badge/-stable-lightgreen)"
32-
return "**Stable**"
29+
if self.disable_stable_badge:
30+
return "Stable"
31+
return "![Stable](https://img.shields.io/badge/-stable-lightgreen)"
3332

3433
def experimental_md_snippet(self):
35-
if self.use_badge:
36-
return "![Experimental](https://img.shields.io/badge/-experimental-blue)"
37-
return "**Experimental**"
34+
if self.disable_experimental_badge:
35+
return "Experimental"
36+
return "![Experimental](https://img.shields.io/badge/-experimental-blue)"
3837

39-
def deprecated_md_snippet(self):
40-
if self.use_badge:
41-
return "![Deprecated](https://img.shields.io/badge/-deprecated-red)"
42-
return "**Deprecated: {}**"
38+
def deprecated_md_snippet(self, deprecated_note: str):
39+
if self.disable_deprecated_badge:
40+
return f"Deprecated: {deprecated_note}"
41+
42+
return f"![Deprecated](https://img.shields.io/badge/-deprecated-red)<br>{deprecated_note}"
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Attribute Group Example
22

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

33
<!-- semconv custom_http(full) -->
4-
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) |
5-
|---|---|---|---|---|
6-
| `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` |
7-
| `custom_http.request.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` |
8-
| `general.some_general_attribute.<key>` | string | This is a general attribute. | ``some_general_attribute.some_key="abc"`` | `Recommended` |
9-
| `referenced_http.request.referenced.header.<key>` | string[] | This is a referenced attribute. | ``http.request.header.content_type=["application/json"]`` | `Recommended` |
4+
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |
5+
|---|---|---|---|---|---|
6+
| `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 |
7+
| `custom_http.request.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` | Experimental |
8+
| `general.some_general_attribute.<key>` | string | This is a general attribute. | ``some_general_attribute.some_key="abc"`` | `Recommended` | Experimental |
9+
| `referenced_http.request.referenced.header.<key>` | string[] | This is a referenced attribute. | ``http.request.header.content_type=["application/json"]`` | `Recommended` | Experimental |
1010
<!-- endsemconv -->

semantic-conventions/src/tests/data/markdown/deprecated/expected.md

+18-18
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,29 @@
22

33
<!-- Re-generate TOC with `TODO: ADD cmd` -->
44
<!-- semconv http -->
5-
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) |
6-
|---|---|---|---|---|
7-
| `http.flavor` | string | **Deprecated. Use attribute `flavor_new` instead.**<br>Kind of HTTP protocol used [1] | `1.0` | `Recommended` |
8-
| `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` |
9-
| `http.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` |
10-
| `http.scheme` | string | The URI scheme identifying the used protocol. | `http`; `https` | `Recommended` |
11-
| `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 |
12-
| `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` |
13-
| `http.target` | string | The full request target as passed in a HTTP request line or equivalent. | `/path/12314/?q=ddds#123` | `Recommended` |
14-
| `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` |
15-
| `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` |
5+
| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability |
6+
|---|---|---|---|---|---|
7+
| `http.flavor` | string | Kind of HTTP protocol used [1] | `1.0` | `Recommended` | Deprecated: Use attribute `flavor_new` instead. |
8+
| `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 |
9+
| `http.method` | string | HTTP request method. | `GET`; `POST`; `HEAD` | `Required` | Experimental |
10+
| `http.scheme` | string | The URI scheme identifying the used protocol. | `http`; `https` | `Recommended` | Experimental |
11+
| `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 |
12+
| `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. |
13+
| `http.target` | string | The full request target as passed in a HTTP request line or equivalent. | `/path/12314/?q=ddds#123` | `Recommended` | Experimental |
14+
| `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 |
15+
| `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 |
1616

1717
**[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.
1818

1919
`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.
2020

21-
| Value | Description |
22-
|---|---|
23-
| `1.0` | HTTP 1.0 |
24-
| `1.1` | HTTP 1.1 |
25-
| `2.0` | HTTP 2 |
26-
| `SPDY` | SPDY protocol. |
27-
| `QUIC` | QUIC protocol. |
21+
| Value | Description | Stability |
22+
|---|---|---|
23+
| `1.0` | HTTP 1.0 | Experimental |
24+
| `1.1` | HTTP 1.1 | Experimental |
25+
| `2.0` | HTTP 2 | Experimental |
26+
| `SPDY` | SPDY protocol. | Experimental |
27+
| `QUIC` | QUIC protocol. | Experimental |
2828
<!-- endsemconv -->
2929

3030
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.

semantic-conventions/src/tests/data/markdown/deprecated/http.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ groups:
7979
brief: 'QUIC protocol.'
8080
stability: experimental
8181
brief: 'Kind of HTTP protocol used'
82-
deprecated: Deprecated. Use attribute `flavor_new` instead.
82+
deprecated: Use attribute `flavor_new` instead.
8383
note: >
8484
If `net.transport` is not specified, it can be assumed to be `IP.TCP` except if `http.flavor`
8585
is `QUIC`, in which case `IP.UDP` is assumed.

0 commit comments

Comments
 (0)