From e19722adca075887edefba5b0424ec64131bc5e6 Mon Sep 17 00:00:00 2001 From: Nev Wylie <54870357+MSNev@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:44:38 -0700 Subject: [PATCH] draft: Initial Event body fields support - Take over Event type to represent both Log and Span events - Add Body and Body Fields Support - Fix and update forge tests --- .gitignore | 3 + crates/weaver_forge/data/exception.yaml | 54 +++ crates/weaver_forge/data/mobile-events.yaml | 140 +++--- crates/weaver_forge/data/trace-exception.yaml | 16 + .../attribute_group/registry_exception.md | 85 ++++ .../expected_output/attribute_groups.md | 89 ++++ .../event/android_lifecycle_events.md | 27 -- .../event/device_app_lifecycle.md | 11 + .../event/ios_lifecycle_events.md | 27 -- .../expected_output/event/trace_exception.md | 89 ++++ crates/weaver_forge/expected_output/events.md | 115 ++++- .../group/android_lifecycle_events.md | 31 -- .../group/device_app_lifecycle.md | 38 ++ .../group/ios_lifecycle_events.md | 31 -- .../group/registry_exception.md | 93 ++++ .../expected_output/group/trace_exception.md | 117 +++++ crates/weaver_forge/expected_output/groups.md | 226 ++++++++-- .../weaver_forge/expected_output/registry.md | 5 +- .../overloaded-templates/test/group.md | 18 + crates/weaver_forge/src/file_loader.rs | 4 +- crates/weaver_forge/src/registry.rs | 10 + crates/weaver_forge/templates/test/body.j2 | 52 +++ crates/weaver_forge/templates/test/events.md | 4 + crates/weaver_forge/templates/test/group.md | 5 + crates/weaver_forge/templates/test/groups.md | 4 + .../test/expected_output/registry.md | 5 +- .../weaver_resolved_schema/src/attribute.rs | 2 +- crates/weaver_resolved_schema/src/body.rs | 91 ++++ crates/weaver_resolved_schema/src/lib.rs | 1 + crates/weaver_resolved_schema/src/registry.rs | 5 + crates/weaver_resolved_schema/src/signal.rs | 31 +- .../expected-attribute-catalog.json | 157 ++++--- .../expected-event-catalog.json | 140 ++++++ .../expected-registry.json | 333 ++++++++++++++- .../registry/browser-event.yaml | 46 ++ .../registry/browser-ref-event.yaml | 52 +++ .../registry/client-exception-event.yaml | 39 ++ .../registry/log-event.yaml | 7 + .../registry/mobile-events.yaml | 138 +++--- .../registry/ping-event.yaml | 5 + .../registry/referenced-attributes.yaml | 32 ++ crates/weaver_resolver/src/attribute.rs | 40 +- crates/weaver_resolver/src/body.rs | 88 ++++ crates/weaver_resolver/src/lib.rs | 1 + crates/weaver_resolver/src/registry.rs | 22 + crates/weaver_semconv/data/event.yaml | 74 ++++ crates/weaver_semconv/src/body.rs | 401 ++++++++++++++++++ crates/weaver_semconv/src/group.rs | 21 +- crates/weaver_semconv/src/lib.rs | 2 + crates/weaver_semconv/src/registry.rs | 2 + 50 files changed, 2604 insertions(+), 425 deletions(-) create mode 100644 crates/weaver_forge/data/exception.yaml create mode 100644 crates/weaver_forge/data/trace-exception.yaml create mode 100644 crates/weaver_forge/expected_output/attribute_group/registry_exception.md delete mode 100644 crates/weaver_forge/expected_output/event/android_lifecycle_events.md create mode 100644 crates/weaver_forge/expected_output/event/device_app_lifecycle.md delete mode 100644 crates/weaver_forge/expected_output/event/ios_lifecycle_events.md create mode 100644 crates/weaver_forge/expected_output/event/trace_exception.md delete mode 100644 crates/weaver_forge/expected_output/group/android_lifecycle_events.md create mode 100644 crates/weaver_forge/expected_output/group/device_app_lifecycle.md delete mode 100644 crates/weaver_forge/expected_output/group/ios_lifecycle_events.md create mode 100644 crates/weaver_forge/expected_output/group/registry_exception.md create mode 100644 crates/weaver_forge/expected_output/group/trace_exception.md create mode 100644 crates/weaver_forge/templates/test/body.j2 create mode 100644 crates/weaver_resolved_schema/src/body.rs create mode 100644 crates/weaver_resolver/data/registry-test-4-events/expected-event-catalog.json create mode 100644 crates/weaver_resolver/data/registry-test-4-events/registry/browser-event.yaml create mode 100644 crates/weaver_resolver/data/registry-test-4-events/registry/browser-ref-event.yaml create mode 100644 crates/weaver_resolver/data/registry-test-4-events/registry/client-exception-event.yaml create mode 100644 crates/weaver_resolver/data/registry-test-4-events/registry/log-event.yaml create mode 100644 crates/weaver_resolver/data/registry-test-4-events/registry/ping-event.yaml create mode 100644 crates/weaver_resolver/data/registry-test-4-events/registry/referenced-attributes.yaml create mode 100644 crates/weaver_resolver/src/body.rs create mode 100644 crates/weaver_semconv/data/event.yaml create mode 100644 crates/weaver_semconv/src/body.rs diff --git a/.gitignore b/.gitignore index 18f2c1c9..a5dffafe 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,9 @@ just.zsh .vscode/ .devcontainer/ +# Visual Studio +.vs/ + # Emacs *~ \#*\# diff --git a/crates/weaver_forge/data/exception.yaml b/crates/weaver_forge/data/exception.yaml new file mode 100644 index 00000000..70dea3d5 --- /dev/null +++ b/crates/weaver_forge/data/exception.yaml @@ -0,0 +1,54 @@ +groups: + - id: registry.exception + type: attribute_group + prefix: exception + brief: > + This document defines the shared attributes used to + report a single exception associated with a span or log. + attributes: + - id: type + type: string + stability: stable + brief: > + The type of the exception (its fully-qualified class name, if applicable). + The dynamic type of the exception should be preferred over the static type + in languages that support it. + examples: ["java.net.ConnectException", "OSError"] + - id: message + type: string + stability: stable + brief: The exception message. + examples: ["Division by zero", "Can't convert 'int' object to str implicitly"] + - id: stacktrace + type: string + stability: stable + brief: > + A stacktrace as a string in the natural representation for the language runtime. + The representation is to be determined and documented by each language SIG. + examples: 'Exception in thread "main" java.lang.RuntimeException: Test exception\n + at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n + at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n + at com.example.GenerateTrace.main(GenerateTrace.java:5)' + - id: escaped + type: boolean + stability: stable + brief: > + SHOULD be set to true if the exception event is recorded at a point where + it is known that the exception is escaping the scope of the span. + note: |- + An exception is considered to have escaped (or left) the scope of a span, + if that span is ended while the exception is still logically "in flight". + This may be actually "in flight" in some languages (e.g. if the exception + is passed to a Context manager's `__exit__` method in Python) but will + usually be caught at the point of recording the exception in most languages. + + It is usually not possible to determine at the point where an exception is thrown + whether it will escape the scope of a span. + However, it is trivial to know that an exception + will escape, if one checks for an active exception just before ending the span, + as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + + It follows that an exception may still escape the scope of the span + even if the `exception.escaped` attribute was not set or set to false, + since the event might have been recorded at a time where it was not + clear whether the exception will escape. diff --git a/crates/weaver_forge/data/mobile-events.yaml b/crates/weaver_forge/data/mobile-events.yaml index 9f0af378..b1b219d5 100644 --- a/crates/weaver_forge/data/mobile-events.yaml +++ b/crates/weaver_forge/data/mobile-events.yaml @@ -1,74 +1,74 @@ groups: - - id: ios.lifecycle.events + - id: device.app.lifecycle + stability: experimental type: event - prefix: ios name: device.app.lifecycle brief: > - This event represents an occurrence of a lifecycle transition on the iOS platform. - attributes: - - id: state - stability: experimental - requirement_level: "required" - note: > - The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), - and from which the `OS terminology` column values are derived. - brief: > - This attribute represents the state the application has transitioned into at the occurrence of the event. - type: - allow_custom_values: false - members: - - id: active - value: 'active' - brief: > - The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`. - - id: inactive - value: 'inactive' - brief: > - The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`. - - id: background - value: 'background' - brief: > - The app is now in the background. - This value is associated with UIKit notification `applicationDidEnterBackground`. - - id: foreground - value: 'foreground' - brief: > - The app is now in the foreground. - This value is associated with UIKit notification `applicationWillEnterForeground`. - - id: terminate - value: 'terminate' - brief: > - The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`. - - id: android.lifecycle.events - type: event - prefix: android - name: device.app.lifecycle - brief: > - This event represents an occurrence of a lifecycle transition on the Android platform. - attributes: - - id: state - stability: experimental - requirement_level: required - brief: > - This attribute represents the state the application has transitioned into at the occurrence of the event. - note: > - The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), - and from which the `OS identifiers` are derived. - type: - allow_custom_values: false - members: - - id: created - value: 'created' - brief: > - Any time before Activity.onResume() or, if the app has no Activity, Context.startService() - has been called in the app for the first time. - - id: background - value: 'background' - brief: > - Any time after Activity.onPause() or, if the app has no Activity, - Context.stopService() has been called when the app was in the foreground state. - - id: foreground - value: 'foreground' - brief: > - Any time after Activity.onResume() or, if the app has no Activity, - Context.startService() has been called when the app was in either the created or background states. \ No newline at end of file + This event represents an occurrence of a lifecycle transition on Android or iOS platform. + note: > + This event identifies the fields that are common to all lifecycle events for android and iOS using + the `android.state` and `ios.state` fields. The `android.state` and `ios.state` attributes are + mutually exclusive. + body: + fields: + - id: ios.state + stability: experimental + requirement_level: + conditionally_required: if and only if `os.name` is `ios` + note: > + The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), + and from which the `OS terminology` column values are derived. + brief: > + This attribute represents the state the application has transitioned into at the occurrence of the event. + type: + allow_custom_values: false + members: + - id: active + value: 'active' + brief: > + The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`. + - id: inactive + value: 'inactive' + brief: > + The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`. + - id: background + value: 'background' + brief: > + The app is now in the background. + This value is associated with UIKit notification `applicationDidEnterBackground`. + - id: foreground + value: 'foreground' + brief: > + The app is now in the foreground. + This value is associated with UIKit notification `applicationWillEnterForeground`. + - id: terminate + value: 'terminate' + brief: > + The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`. + - id: android.state + stability: experimental + requirement_level: + conditionally_required: if and only if `os.name` is `android` + brief: > + This attribute represents the state the application has transitioned into at the occurrence of the event. + note: > + The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), + and from which the `OS identifiers` are derived. + type: + allow_custom_values: false + members: + - id: created + value: 'created' + brief: > + Any time before Activity.onResume() or, if the app has no Activity, Context.startService() + has been called in the app for the first time. + - id: background + value: 'background' + brief: > + Any time after Activity.onPause() or, if the app has no Activity, + Context.stopService() has been called when the app was in the foreground state. + - id: foreground + value: 'foreground' + brief: > + Any time after Activity.onResume() or, if the app has no Activity, + Context.startService() has been called when the app was in either the created or background states. \ No newline at end of file diff --git a/crates/weaver_forge/data/trace-exception.yaml b/crates/weaver_forge/data/trace-exception.yaml new file mode 100644 index 00000000..32c53f4b --- /dev/null +++ b/crates/weaver_forge/data/trace-exception.yaml @@ -0,0 +1,16 @@ +groups: + - id: trace-exception + prefix: exception + type: event + brief: > + This document defines the attributes used to + report a single exception associated with a span. + attributes: + - ref: exception.type + requirement_level: + conditionally_required: Required if `exception.message` is not set, recommended otherwise. + - ref: exception.message + requirement_level: + conditionally_required: Required if `exception.type` is not set, recommended otherwise. + - ref: exception.stacktrace + - ref: exception.escaped diff --git a/crates/weaver_forge/expected_output/attribute_group/registry_exception.md b/crates/weaver_forge/expected_output/attribute_group/registry_exception.md new file mode 100644 index 00000000..f5181ff0 --- /dev/null +++ b/crates/weaver_forge/expected_output/attribute_group/registry_exception.md @@ -0,0 +1,85 @@ +## Group `registry_exception` (attribute_group) + +### Brief + +This document defines the shared attributes used to report a single exception associated with a span or log. + +prefix: exception + +### Attributes + + +#### Attribute `exception.type` + +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] + +- Stability: Stable + + +#### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable + + +#### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + +- Requirement Level: Recommended + +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) + +- Stability: Stable + + +#### Attribute `exception.escaped` + +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. + + +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. + +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. + +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + \ No newline at end of file diff --git a/crates/weaver_forge/expected_output/attribute_groups.md b/crates/weaver_forge/expected_output/attribute_groups.md index 38d2cf9c..1af4f43c 100644 --- a/crates/weaver_forge/expected_output/attribute_groups.md +++ b/crates/weaver_forge/expected_output/attribute_groups.md @@ -4,6 +4,95 @@ - one - two - three +## Group `registry.exception` (attribute_group) + +### Brief + +This document defines the shared attributes used to report a single exception associated with a span or log. + +prefix: exception + +### Attributes + + +#### Attribute `exception.type` + +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] + +- Stability: Stable + + +#### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable + + +#### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) + +- Stability: Stable + + +#### Attribute `exception.escaped` + +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. + + + +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. + +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. + +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + + ## Group `attributes.jvm.memory` (attribute_group) ### Brief diff --git a/crates/weaver_forge/expected_output/event/android_lifecycle_events.md b/crates/weaver_forge/expected_output/event/android_lifecycle_events.md deleted file mode 100644 index de690580..00000000 --- a/crates/weaver_forge/expected_output/event/android_lifecycle_events.md +++ /dev/null @@ -1,27 +0,0 @@ -# Group `android.lifecycle.events` (event) - -## Brief - -This event represents an occurrence of a lifecycle transition on the Android platform. - -Prefix: android -Name: device.app.lifecycle - -## Attributes - - -### Attribute `android.state` - -This attribute represents the state the application has transitioned into at the occurrence of the event. - - - -The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. - -- Requirement Level: Required - -- Type: Enum [created, background, foreground] - -- Stability: Experimental - - \ No newline at end of file diff --git a/crates/weaver_forge/expected_output/event/device_app_lifecycle.md b/crates/weaver_forge/expected_output/event/device_app_lifecycle.md new file mode 100644 index 00000000..9ae4dc77 --- /dev/null +++ b/crates/weaver_forge/expected_output/event/device_app_lifecycle.md @@ -0,0 +1,11 @@ +# Group `device.app.lifecycle` (event) + +## Brief + +This event represents an occurrence of a lifecycle transition on Android or iOS platform. + +Prefix: +Name: device.app.lifecycle + +## Attributes + diff --git a/crates/weaver_forge/expected_output/event/ios_lifecycle_events.md b/crates/weaver_forge/expected_output/event/ios_lifecycle_events.md deleted file mode 100644 index 83ecf5db..00000000 --- a/crates/weaver_forge/expected_output/event/ios_lifecycle_events.md +++ /dev/null @@ -1,27 +0,0 @@ -# Group `ios.lifecycle.events` (event) - -## Brief - -This event represents an occurrence of a lifecycle transition on the iOS platform. - -Prefix: ios -Name: device.app.lifecycle - -## Attributes - - -### Attribute `ios.state` - -This attribute represents the state the application has transitioned into at the occurrence of the event. - - - -The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived. - -- Requirement Level: Required - -- Type: Enum [active, inactive, background, foreground, terminate] - -- Stability: Experimental - - \ No newline at end of file diff --git a/crates/weaver_forge/expected_output/event/trace_exception.md b/crates/weaver_forge/expected_output/event/trace_exception.md new file mode 100644 index 00000000..7ba78822 --- /dev/null +++ b/crates/weaver_forge/expected_output/event/trace_exception.md @@ -0,0 +1,89 @@ +# Group `trace-exception` (event) + +## Brief + +This document defines the attributes used to report a single exception associated with a span. + +Prefix: exception +Name: none + +## Attributes + + +### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) + +- Stability: Stable + + +### Attribute `exception.escaped` + +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. + + + +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. + +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. + +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + +### Attribute `exception.type` + +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. + + + +- Requirement Level: Conditionally Required - Required if `exception.message` is not set, recommended otherwise. + +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] + +- Stability: Stable + + +### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Conditionally Required - Required if `exception.type` is not set, recommended otherwise. + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable + + \ No newline at end of file diff --git a/crates/weaver_forge/expected_output/events.md b/crates/weaver_forge/expected_output/events.md index 8066e504..5da17a92 100644 --- a/crates/weaver_forge/expected_output/events.md +++ b/crates/weaver_forge/expected_output/events.md @@ -1,59 +1,132 @@ # Semantic Convention Event Groups -## Group `ios.lifecycle.events` (event) +## Group `device.app.lifecycle` (event) ### Brief -This event represents an occurrence of a lifecycle transition on the iOS platform. +This event represents an occurrence of a lifecycle transition on Android or iOS platform. -Prefix: ios +Prefix: Name: device.app.lifecycle -### Attributes +### Body Fields + +#### Field `ios.state` + +This attribute represents the state the application has transitioned into at the occurrence of the event. + +The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived. +- Requirement Level: Conditionally Required - if and only if `os.name` is `ios` +- Type: Enum [active, inactive, background, foreground, terminate] +- Stability: Experimental -#### Attribute `ios.state` +#### Field `android.state` This attribute represents the state the application has transitioned into at the occurrence of the event. +The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. +- Requirement Level: Conditionally Required - if and only if `os.name` is `android` +- Type: Enum [created, background, foreground] +- Stability: Experimental + +### Attributes -The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived. -- Requirement Level: Required -- Type: Enum [active, inactive, background, foreground, terminate] +## Group `trace-exception` (event) + +### Brief + +This document defines the attributes used to report a single exception associated with a span. + +Prefix: exception +Name: none + +### Body Fields + +No event body defined. + +### Attributes + + +#### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + + +- Requirement Level: Recommended -- Stability: Experimental +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) +- Stability: Stable -## Group `android.lifecycle.events` (event) +#### Attribute `exception.escaped` -### Brief +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. -This event represents an occurrence of a lifecycle transition on the Android platform. -Prefix: android -Name: device.app.lifecycle -### Attributes +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). -#### Attribute `android.state` +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. -This attribute represents the state the application has transitioned into at the occurrence of the event. +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + +#### Attribute `exception.type` +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. -The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. -- Requirement Level: Required +- Requirement Level: Conditionally Required - Required if `exception.message` is not set, recommended otherwise. -- Type: Enum [created, background, foreground] +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] -- Stability: Experimental +- Stability: Stable + + +#### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Conditionally Required - Required if `exception.type` is not set, recommended otherwise. + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable \ No newline at end of file diff --git a/crates/weaver_forge/expected_output/group/android_lifecycle_events.md b/crates/weaver_forge/expected_output/group/android_lifecycle_events.md deleted file mode 100644 index 1eba53e2..00000000 --- a/crates/weaver_forge/expected_output/group/android_lifecycle_events.md +++ /dev/null @@ -1,31 +0,0 @@ -# Group `android.lifecycle.events` (event) - -## Brief - -This event represents an occurrence of a lifecycle transition on the Android platform. - -prefix: android - -## Attributes - - -### Attribute `android.state` - -This attribute represents the state the application has transitioned into at the occurrence of the event. - - - -The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. - -- Requirement Level: Required - -- Type: Enum [created, background, foreground] - -- Stability: Experimental - - - -## Lineage - -Source file: data/mobile-events.yaml - diff --git a/crates/weaver_forge/expected_output/group/device_app_lifecycle.md b/crates/weaver_forge/expected_output/group/device_app_lifecycle.md new file mode 100644 index 00000000..1db22557 --- /dev/null +++ b/crates/weaver_forge/expected_output/group/device_app_lifecycle.md @@ -0,0 +1,38 @@ +# Group `device.app.lifecycle` (event) + +## Brief + +This event represents an occurrence of a lifecycle transition on Android or iOS platform. + +prefix: + +### Body Fields + +#### Field `ios.state` + +This attribute represents the state the application has transitioned into at the occurrence of the event. + +The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived. + +- Requirement Level: Conditionally Required - if and only if `os.name` is `ios` +- Type: Enum [active, inactive, background, foreground, terminate] +- Stability: Experimental + +#### Field `android.state` + +This attribute represents the state the application has transitioned into at the occurrence of the event. + +The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. + +- Requirement Level: Conditionally Required - if and only if `os.name` is `android` +- Type: Enum [created, background, foreground] +- Stability: Experimental + +## Attributes + + + +## Lineage + +Source file: data/mobile-events.yaml + diff --git a/crates/weaver_forge/expected_output/group/ios_lifecycle_events.md b/crates/weaver_forge/expected_output/group/ios_lifecycle_events.md deleted file mode 100644 index b36ffa8d..00000000 --- a/crates/weaver_forge/expected_output/group/ios_lifecycle_events.md +++ /dev/null @@ -1,31 +0,0 @@ -# Group `ios.lifecycle.events` (event) - -## Brief - -This event represents an occurrence of a lifecycle transition on the iOS platform. - -prefix: ios - -## Attributes - - -### Attribute `ios.state` - -This attribute represents the state the application has transitioned into at the occurrence of the event. - - - -The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived. - -- Requirement Level: Required - -- Type: Enum [active, inactive, background, foreground, terminate] - -- Stability: Experimental - - - -## Lineage - -Source file: data/mobile-events.yaml - diff --git a/crates/weaver_forge/expected_output/group/registry_exception.md b/crates/weaver_forge/expected_output/group/registry_exception.md new file mode 100644 index 00000000..ffe4fb52 --- /dev/null +++ b/crates/weaver_forge/expected_output/group/registry_exception.md @@ -0,0 +1,93 @@ +# Group `registry.exception` (attribute_group) + +## Brief + +This document defines the shared attributes used to report a single exception associated with a span or log. + +prefix: exception + +## Attributes + + +### Attribute `exception.type` + +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] + +- Stability: Stable + + +### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable + + +### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) + +- Stability: Stable + + +### Attribute `exception.escaped` + +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. + + + +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. + +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. + +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + + +## Lineage + +Source file: data/exception.yaml + diff --git a/crates/weaver_forge/expected_output/group/trace_exception.md b/crates/weaver_forge/expected_output/group/trace_exception.md new file mode 100644 index 00000000..9c66a6ec --- /dev/null +++ b/crates/weaver_forge/expected_output/group/trace_exception.md @@ -0,0 +1,117 @@ +# Group `trace-exception` (event) + +## Brief + +This document defines the attributes used to report a single exception associated with a span. + +prefix: exception + +### Body Fields + +No event body defined. + +## Attributes + + +### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) + +- Stability: Stable + + +### Attribute `exception.escaped` + +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. + + + +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. + +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. + +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + +### Attribute `exception.type` + +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. + + + +- Requirement Level: Conditionally Required - Required if `exception.message` is not set, recommended otherwise. + +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] + +- Stability: Stable + + +### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Conditionally Required - Required if `exception.type` is not set, recommended otherwise. + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable + + + +## Lineage + +Source file: data/trace-exception.yaml + +attribute: + - source group: + - inherited fields: + - locally overridden fields: + +attribute: + - source group: + - inherited fields: + - locally overridden fields: + +attribute: + - source group: + - inherited fields: + - locally overridden fields: + +attribute: + - source group: + - inherited fields: + - locally overridden fields: + diff --git a/crates/weaver_forge/expected_output/groups.md b/crates/weaver_forge/expected_output/groups.md index bb89d4df..58efeeeb 100644 --- a/crates/weaver_forge/expected_output/groups.md +++ b/crates/weaver_forge/expected_output/groups.md @@ -1,6 +1,95 @@ # Semantic Convention Groups +## Group `registry.exception` (attribute_group) + +### Brief + +This document defines the shared attributes used to report a single exception associated with a span or log. + +prefix: exception + +### Attributes + + +#### Attribute `exception.type` + +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] + +- Stability: Stable + + +#### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Recommended + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable + + +#### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) + +- Stability: Stable + + +#### Attribute `exception.escaped` + +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. + + + +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. + +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. + +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + + ## Group `otel.scope` (resource) ### Brief @@ -478,59 +567,39 @@ prefix: -## Group `ios.lifecycle.events` (event) +## Group `device.app.lifecycle` (event) ### Brief -This event represents an occurrence of a lifecycle transition on the iOS platform. +This event represents an occurrence of a lifecycle transition on Android or iOS platform. -prefix: ios - -### Attributes +prefix: +### Body Fields -#### Attribute `ios.state` +#### Field `ios.state` This attribute represents the state the application has transitioned into at the occurrence of the event. - - The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived. -- Requirement Level: Required - +- Requirement Level: Conditionally Required - if and only if `os.name` is `ios` - Type: Enum [active, inactive, background, foreground, terminate] - - Stability: Experimental - - -## Group `android.lifecycle.events` (event) - -### Brief - -This event represents an occurrence of a lifecycle transition on the Android platform. - -prefix: android - -### Attributes - - -#### Attribute `android.state` +#### Field `android.state` This attribute represents the state the application has transitioned into at the occurrence of the event. - - The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived. -- Requirement Level: Required - +- Requirement Level: Conditionally Required - if and only if `os.name` is `android` - Type: Enum [created, background, foreground] - - Stability: Experimental - - + +### Attributes + + ## Group `registry.db` (attribute_group) @@ -5229,3 +5298,96 @@ The user-agent value is generated by SDK which is a combination of
`sdk_vers - Stability: Stable + +## Group `trace-exception` (event) + +### Brief + +This document defines the attributes used to report a single exception associated with a span. + +prefix: exception + +### Body Fields + +No event body defined. + +### Attributes + + +#### Attribute `exception.stacktrace` + +A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG. + + + +- Requirement Level: Recommended + +- Type: string +- Examples: Exception in thread "main" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5) + +- Stability: Stable + + +#### Attribute `exception.escaped` + +SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span. + + + +An exception is considered to have escaped (or left) the scope of a span, +if that span is ended while the exception is still logically "in flight". +This may be actually "in flight" in some languages (e.g. if the exception +is passed to a Context manager's `__exit__` method in Python) but will +usually be caught at the point of recording the exception in most languages. + +It is usually not possible to determine at the point where an exception is thrown +whether it will escape the scope of a span. +However, it is trivial to know that an exception +will escape, if one checks for an active exception just before ending the span, +as done in the [example for recording span exceptions](https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#recording-an-exception). + +It follows that an exception may still escape the scope of the span +even if the `exception.escaped` attribute was not set or set to false, +since the event might have been recorded at a time where it was not +clear whether the exception will escape. + +- Requirement Level: Recommended + +- Type: boolean + +- Stability: Stable + + +#### Attribute `exception.type` + +The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it. + + + +- Requirement Level: Conditionally Required - Required if `exception.message` is not set, recommended otherwise. + +- Type: string +- Examples: [ + "java.net.ConnectException", + "OSError", +] + +- Stability: Stable + + +#### Attribute `exception.message` + +The exception message. + + +- Requirement Level: Conditionally Required - Required if `exception.type` is not set, recommended otherwise. + +- Type: string +- Examples: [ + "Division by zero", + "Can't convert 'int' object to str implicitly", +] + +- Stability: Stable + + diff --git a/crates/weaver_forge/expected_output/registry.md b/crates/weaver_forge/expected_output/registry.md index e26e8cc5..e362df68 100644 --- a/crates/weaver_forge/expected_output/registry.md +++ b/crates/weaver_forge/expected_output/registry.md @@ -4,6 +4,7 @@ Url: # Attribute Groups +- [registry.exception](attribute_group/registry_exception.md) - [attributes.jvm.memory](attribute_group/attributes_jvm_memory.md) - [registry.db](attribute_group/registry_db.md) - [registry.http](attribute_group/registry_http.md) @@ -14,8 +15,8 @@ Url: # Events -- [ios.lifecycle.events](event/ios_lifecycle_events.md) -- [android.lifecycle.events](event/android_lifecycle_events.md) +- [device.app.lifecycle](event/device_app_lifecycle.md) +- [trace-exception](event/trace_exception.md) # Metrics diff --git a/crates/weaver_forge/overloaded-templates/test/group.md b/crates/weaver_forge/overloaded-templates/test/group.md index a817d676..cd813631 100644 --- a/crates/weaver_forge/overloaded-templates/test/group.md +++ b/crates/weaver_forge/overloaded-templates/test/group.md @@ -7,7 +7,25 @@ {{ ctx.brief | trim }} +{% if ctx.prefix %} prefix: {{ ctx.prefix }} +{% endif %} +{% if ctx.type == "event" %} +## Event details + +The event name MUST be `{% ctx.id %} + +{% if ctx.body %} +## Body Fields + +{% for bodyField in ctx.body.fields %} +### Field `{{ bodyField.id }}` +{% endfor %} +{% else %} +No event body defined. +{% endif %} + +{% endif %} ## Attributes diff --git a/crates/weaver_forge/src/file_loader.rs b/crates/weaver_forge/src/file_loader.rs index b9485426..e30c4cc3 100644 --- a/crates/weaver_forge/src/file_loader.rs +++ b/crates/weaver_forge/src/file_loader.rs @@ -286,9 +286,9 @@ mod tests { // Test all files let embedded_files: HashSet = embedded_loader.all_files().into_iter().collect(); - assert_eq!(embedded_files.len(), 17); + assert_eq!(embedded_files.len(), 18, "Embedded files: {:?}", embedded_files); let fs_files: HashSet = fs_loader.all_files().into_iter().collect(); - assert_eq!(fs_files.len(), 17); + assert_eq!(fs_files.len(), 18, "FS files: {:?}", fs_files); // Test that the files are the same between the embedded and file system loaders assert_eq!(embedded_files, fs_files); // Test that all the files can be loaded from the embedded loader diff --git a/crates/weaver_forge/src/registry.rs b/crates/weaver_forge/src/registry.rs index 07cc629d..88e8cac9 100644 --- a/crates/weaver_forge/src/registry.rs +++ b/crates/weaver_forge/src/registry.rs @@ -8,6 +8,7 @@ use crate::error::Error; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use weaver_resolved_schema::attribute::Attribute; +use weaver_resolved_schema::body::Body; use weaver_resolved_schema::catalog::Catalog; use weaver_resolved_schema::lineage::GroupLineage; use weaver_resolved_schema::registry::{Constraint, Group, Registry}; @@ -104,6 +105,9 @@ pub struct ResolvedGroup { /// The readable name for attribute groups used when generating registry tables. #[serde(skip_serializing_if = "Option::is_none")] pub display_name: Option, + /// The body specification used for event semantic conventions. + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, } impl ResolvedGroup { @@ -134,6 +138,8 @@ impl ResolvedGroup { }) .collect(); let lineage = group.lineage.clone(); + let body = if group_type == GroupType::Event { group.body.clone() } else { None }; + if !errors.is_empty() { return Err(Error::CompoundError(errors)); } @@ -156,6 +162,7 @@ impl ResolvedGroup { name: group.name.clone(), lineage, display_name: group.display_name.clone(), + body, }) } } @@ -196,6 +203,8 @@ impl ResolvedRegistry { }) .collect(); let lineage = group.lineage.clone(); + let body = if group_type == GroupType::Event { group.body.clone() } else { None }; + ResolvedGroup { id, r#type: group_type, @@ -215,6 +224,7 @@ impl ResolvedRegistry { name: group.name.clone(), lineage, display_name: group.display_name.clone(), + body, } }) .collect(); diff --git a/crates/weaver_forge/templates/test/body.j2 b/crates/weaver_forge/templates/test/body.j2 new file mode 100644 index 00000000..7bb8b869 --- /dev/null +++ b/crates/weaver_forge/templates/test/body.j2 @@ -0,0 +1,52 @@ +{%- if group.type == "event" -%} +### Body Fields + +{% if group.body -%} +{% for bodyField in group.body.fields -%} +#### Field `{{ bodyField.name }}` + +{{ bodyField.brief }} + +{%- if bodyField.note %} +{{ bodyField.note | trim }} +{% endif %} + +{%- if bodyField.requirement_level == "required" %} +- Requirement Level: Required +{%- elif bodyField.requirement_level.conditionally_required %} +- Requirement Level: Conditionally Required - {{ bodyField.requirement_level.conditionally_required }} +{%- elif bodyField.requirement_level == "recommended" %} +- Requirement Level: Recommended +{%- else %} +- Requirement Level: Optional +{%- endif %} + +{%- if bodyField.type is mapping %} +- Type: Enum [{{ bodyField.type.members | map(attribute="value") | join(", ") | trim }}] +{%- else %} +- Type: {{ bodyField.type }} +{%- endif %} + +{%- if bodyField.examples %} +{%- if bodyField.examples is sequence %} +- Examples: {{ bodyField.examples | pprint }} +{%- else %} +- Examples: {{ bodyField.examples }} +{%- endif %} +{%- endif %} + +{%- if bodyField.deprecated %} +- Deprecated: {{ bodyField.deprecated }} +{%- endif %} + +{%- if bodyField.stability %} +- Stability: {{ bodyField.stability | capitalize }} +{%- endif %} + +{% endfor -%} +{%- else -%} + +No event body defined. + +{% endif %} +{%- endif -%} \ No newline at end of file diff --git a/crates/weaver_forge/templates/test/events.md b/crates/weaver_forge/templates/test/events.md index eae78bc1..8ddad693 100644 --- a/crates/weaver_forge/templates/test/events.md +++ b/crates/weaver_forge/templates/test/events.md @@ -10,6 +10,10 @@ Prefix: {{ group.prefix }} Name: {{ group.name }} +{% if group.type == "event" -%} +{% include "body.j2" -%} +{% endif -%} + ### Attributes {% for attribute in group.attributes %} diff --git a/crates/weaver_forge/templates/test/group.md b/crates/weaver_forge/templates/test/group.md index 1d14bc87..db77f81c 100644 --- a/crates/weaver_forge/templates/test/group.md +++ b/crates/weaver_forge/templates/test/group.md @@ -9,6 +9,11 @@ prefix: {{ ctx.prefix }} +{% if ctx.type == "event" -%} +{%- set group = ctx -%} +{% include "body.j2" -%} +{% endif -%} + ## Attributes {% for attribute in ctx.attributes %} diff --git a/crates/weaver_forge/templates/test/groups.md b/crates/weaver_forge/templates/test/groups.md index 8b072179..8b1f29fc 100644 --- a/crates/weaver_forge/templates/test/groups.md +++ b/crates/weaver_forge/templates/test/groups.md @@ -9,6 +9,10 @@ prefix: {{ group.prefix }} +{% if group.type == "event" -%} +{% include "body.j2" -%} +{% endif -%} + ### Attributes {% for attribute in group.attributes %} diff --git a/crates/weaver_forge/whitespace_control_templates/test/expected_output/registry.md b/crates/weaver_forge/whitespace_control_templates/test/expected_output/registry.md index e2d11600..d1f6bb78 100644 --- a/crates/weaver_forge/whitespace_control_templates/test/expected_output/registry.md +++ b/crates/weaver_forge/whitespace_control_templates/test/expected_output/registry.md @@ -4,6 +4,7 @@ Url: ## Attribute Groups +- [registry.exception](attribute_group/registry_exception.md) - [attributes.jvm.memory](attribute_group/attributes_jvm_memory.md) - [registry.db](attribute_group/registry_db.md) - [registry.http](attribute_group/registry_http.md) @@ -14,8 +15,8 @@ Url: ## Events -- [ios.lifecycle.events](event/ios_lifecycle_events.md) -- [android.lifecycle.events](event/android_lifecycle_events.md) +- [device.app.lifecycle](event/device_app_lifecycle.md) +- [trace-exception](event/trace_exception.md) ## Metrics diff --git a/crates/weaver_resolved_schema/src/attribute.rs b/crates/weaver_resolved_schema/src/attribute.rs index 8326a9c1..42b75c35 100644 --- a/crates/weaver_resolved_schema/src/attribute.rs +++ b/crates/weaver_resolved_schema/src/attribute.rs @@ -91,7 +91,7 @@ pub struct UnresolvedAttribute { /// An internal reference to an attribute in the catalog. #[derive( - Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, JsonSchema, + Serialize, Deserialize, Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, JsonSchema, )] pub struct AttributeRef(pub u32); diff --git a/crates/weaver_resolved_schema/src/body.rs b/crates/weaver_resolved_schema/src/body.rs new file mode 100644 index 00000000..c5da8762 --- /dev/null +++ b/crates/weaver_resolved_schema/src/body.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 + +#![allow(rustdoc::invalid_html_tags)] + +//! Specification of a resolved body field. + +use crate::attribute::AttributeRef; +use crate::value::Value; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use weaver_semconv::body::{BodySpec, BodyFieldSpec}; +use weaver_semconv::attribute::{AttributeType, Examples, RequirementLevel}; +use weaver_semconv::stability::Stability; + +/// An attribute definition. +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct Body { + /// The body specification. + #[serde(skip_serializing_if = "Option::is_none")] + pub fields: Option>, + + /// The body value when there are no fields + #[serde(skip_serializing_if = "Option::is_none")] + pub value: Option +} + +/// An attribute definition. +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, JsonSchema)] +#[serde(deny_unknown_fields)] +pub struct BodyField { + /// Attribute name. + pub name: String, + /// A reference to an attribute definition, used to populate the relevant + /// fields of the body field, unless they are overridden by the body field. + #[serde(skip_serializing_if = "Option::is_none")] + pub r#attr: Option, + /// A alias to use for the field for the referenced attribute. + #[serde(skip_serializing_if = "Option::is_none")] + pub alias: Option, + /// Either a string literal denoting the type as a primitive or an + /// array type, a template type or an enum definition. + pub r#type: AttributeType, + /// A brief description of the attribute. + #[serde(skip_serializing_if = "String::is_empty")] + pub brief: String, + /// Sequence of example values for the attribute or single example + /// value. They are required only for string and string array + /// attributes. Example values must be of the same type of the + /// attribute. If only a single example is provided, it can directly + /// be reported without encapsulating it into a sequence/dictionary. + #[serde(skip_serializing_if = "Option::is_none")] + pub examples: Option, + /// Specifies if the attribute is mandatory. Can be "required", + /// "conditionally_required", "recommended" or "opt_in". When omitted, + /// the attribute is "recommended". When set to + /// "conditionally_required", the string provided as MUST + /// specify the conditions under which the attribute is required. + pub requirement_level: RequirementLevel, + /// A more elaborate description of the attribute. + /// It defaults to an empty string. + #[serde(skip_serializing_if = "String::is_empty")] + #[serde(default)] + pub note: String, + /// Specifies the stability of the attribute. + /// Note that, if stability is missing but deprecated is present, it will + /// automatically set the stability to deprecated. If deprecated is + /// present and stability differs from deprecated, this will result in an + /// error. + #[serde(skip_serializing_if = "Option::is_none")] + pub stability: Option, + /// Specifies if the attribute is deprecated. The string + /// provided as MUST specify why it's deprecated and/or what + /// to use instead. See also stability. + #[serde(skip_serializing_if = "Option::is_none")] + pub deprecated: Option, +} + +/// An unresolved body definition. +#[derive(Debug, Deserialize, Clone)] +pub struct UnresolvedBody { + /// The body specification. + pub spec: BodySpec, +} + +/// An unresolved body field definition. +#[derive(Debug, Deserialize, Clone)] +pub struct UnresolvedBodyField { + /// The body field specification. + pub spec: BodyFieldSpec, +} diff --git a/crates/weaver_resolved_schema/src/lib.rs b/crates/weaver_resolved_schema/src/lib.rs index 61ed0ed7..740fc5ae 100644 --- a/crates/weaver_resolved_schema/src/lib.rs +++ b/crates/weaver_resolved_schema/src/lib.rs @@ -14,6 +14,7 @@ use std::collections::HashMap; use weaver_version::Versions; pub mod attribute; +pub mod body; pub mod catalog; mod error; pub mod instrumentation_library; diff --git a/crates/weaver_resolved_schema/src/registry.rs b/crates/weaver_resolved_schema/src/registry.rs index c41fc405..604c1c90 100644 --- a/crates/weaver_resolved_schema/src/registry.rs +++ b/crates/weaver_resolved_schema/src/registry.rs @@ -13,6 +13,7 @@ use weaver_semconv::group::{GroupType, InstrumentSpec, SpanKindSpec}; use weaver_semconv::stability::Stability; use crate::attribute::{Attribute, AttributeRef}; +use crate::body::Body; use crate::catalog::Catalog; use crate::error::{handle_errors, Error}; use crate::lineage::GroupLineage; @@ -126,6 +127,10 @@ pub struct Group { /// The readable name for attribute groups used when generating registry tables. #[serde(skip_serializing_if = "Option::is_none")] pub display_name: Option, + /// The body of the event. + /// This fields is only used for event groups. + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, } /// Common statistics for a group. diff --git a/crates/weaver_resolved_schema/src/signal.rs b/crates/weaver_resolved_schema/src/signal.rs index a702191c..a5f88c90 100644 --- a/crates/weaver_resolved_schema/src/signal.rs +++ b/crates/weaver_resolved_schema/src/signal.rs @@ -6,6 +6,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::attribute::AttributeRef; +use crate::body::Body; use crate::metric::MetricRef; use crate::tags::Tags; @@ -44,17 +45,15 @@ pub struct MultivariateMetric { tags: Option, } -/// An event signal. +/// An event specification, used for both Span and Log events. #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(deny_unknown_fields)] pub struct Event { /// The name of the event. - name: String, + event_name: String, /// References to attributes defined in the catalog. #[serde(skip_serializing_if = "Vec::is_empty")] attributes: Vec, - /// The domain of the event. - domain: String, /// Brief description of the event. brief: Option, /// Longer description. @@ -63,6 +62,9 @@ pub struct Event { /// A set of tags for the event. #[serde(skip_serializing_if = "Option::is_none")] tags: Option, + /// The body of the event, not used for Span events. + #[serde(skip_serializing_if = "Option::is_none")] + body: Option, } /// A span signal. @@ -79,7 +81,7 @@ pub struct Span { kind: Option, /// The events of the span. #[serde(skip_serializing_if = "Vec::is_empty")] - events: Vec, + events: Vec, /// The links of the span. #[serde(skip_serializing_if = "Vec::is_empty")] links: Vec, @@ -108,25 +110,6 @@ pub enum SpanKind { Consumer, } -/// A span event specification. -#[derive(Serialize, Deserialize, Debug, JsonSchema)] -#[serde(deny_unknown_fields)] -pub struct SpanEvent { - /// The name of the span event. - pub event_name: String, - /// The attributes of the span event. - #[serde(skip_serializing_if = "Vec::is_empty")] - pub attributes: Vec, - /// Brief description of the span event. - pub brief: Option, - /// Longer description. - /// It defaults to an empty string. - pub note: Option, - /// A set of tags for the span event. - #[serde(skip_serializing_if = "Option::is_none")] - pub tags: Option, -} - /// A span link specification. #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(deny_unknown_fields)] diff --git a/crates/weaver_resolver/data/registry-test-4-events/expected-attribute-catalog.json b/crates/weaver_resolver/data/registry-test-4-events/expected-attribute-catalog.json index dcd9d812..b1a37fe2 100644 --- a/crates/weaver_resolver/data/registry-test-4-events/expected-attribute-catalog.json +++ b/crates/weaver_resolver/data/registry-test-4-events/expected-attribute-catalog.json @@ -1,73 +1,98 @@ [ { - "name": "ios.state", - "type": { - "allow_custom_values": false, - "members": [ - { - "id": "active", - "value": "active", - "brief": "The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`.\n", - "note": null - }, - { - "id": "inactive", - "value": "inactive", - "brief": "The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`.\n", - "note": null - }, - { - "id": "background", - "value": "background", - "brief": "The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`.\n", - "note": null - }, - { - "id": "foreground", - "value": "foreground", - "brief": "The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`.\n", - "note": null - }, - { - "id": "terminate", - "value": "terminate", - "brief": "The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`.\n", - "note": null - } - ] - }, - "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", - "requirement_level": "required", - "note": "The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived.\n" + "name": "browser.brands", + "type": "string[]", + "brief": "Array of brand name and version separated by a space", + "examples": [ + " Not A;Brand 99", + "Chromium 99", + "Chrome 99" + ], + "requirement_level": "recommended", + "note": "This value is intended to be taken from the [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) (`navigator.userAgentData.brands`).\n" + }, + { + "name": "browser.platform", + "type": "string", + "brief": "The platform on which the browser is running", + "examples": [ + "Windows", + "macOS", + "Android" + ], + "requirement_level": "recommended", + "note": "This value is intended to be taken from the [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) (`navigator.userAgentData.platform`). If unavailable, the legacy `navigator.platform` API SHOULD NOT be used instead and this attribute SHOULD be left unset in order for the values to be consistent. The list of possible values is defined in the [W3C User-Agent Client Hints specification](https://wicg.github.io/ua-client-hints/#sec-ch-ua-platform). Note that some (but not all) of these values can overlap with values in the [`os.type` and `os.name` attributes](./os.md). However, for consistency, the values in the `browser.platform` attribute should capture the exact value that the user agent provides.\n" + }, + { + "name": "browser.mobile", + "type": "boolean", + "brief": "A boolean that is true if the browser is running on a mobile device", + "requirement_level": "recommended", + "note": "This value is intended to be taken from the [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) (`navigator.userAgentData.mobile`). If unavailable, this attribute SHOULD be left unset.\n" + }, + { + "name": "browser.language", + "type": "string", + "brief": "Preferred language of the user using the browser", + "examples": [ + "en", + "en-US", + "fr", + "fr-FR" + ], + "requirement_level": "recommended", + "note": "This value is intended to be taken from the Navigator API `navigator.language`.\n" + }, + { + "name": "client.name", + "type": "string", + "brief": "The name of the client that reported the exception.\n", + "examples": [ + "myclient" + ], + "requirement_level": "recommended" }, { - "name": "android.state", - "type": { - "allow_custom_values": false, - "members": [ - { - "id": "created", - "value": "created", - "brief": "Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time.\n", - "note": null - }, - { - "id": "background", - "value": "background", - "brief": "Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state.\n", - "note": null - }, - { - "id": "foreground", - "value": "foreground", - "brief": "Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states.", - "note": null - } - ] - }, - "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", - "requirement_level": "required", - "note": "The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived.\n" + "name": "browser.platform", + "type": "string", + "brief": "The browser platform", + "examples": [ + "Windows", + "macOS", + "Android" + ], + "requirement_level": "recommended", + "note": "Test value.", + "stability": "stable" + }, + { + "name": "http.url", + "type": "string", + "brief": "The Url", + "examples": [ + "https://example.com" + ], + "requirement_level": "recommended", + "note": "Test url value.", + "stability": "stable" + }, + { + "name": "log.event.attr", + "type": "string", + "brief": "Just making sure the referenced attributes are defined", + "examples": "some value", + "requirement_level": "recommended", + "note": "Test value.", + "stability": "stable" + }, + { + "name": "session.id", + "type": "string", + "brief": "The session id", + "examples": "127836abcdef98", + "requirement_level": "recommended", + "note": "Test value.", + "stability": "stable" }, { "name": "feature_flag.key", diff --git a/crates/weaver_resolver/data/registry-test-4-events/expected-event-catalog.json b/crates/weaver_resolver/data/registry-test-4-events/expected-event-catalog.json new file mode 100644 index 00000000..bfc7ebe7 --- /dev/null +++ b/crates/weaver_resolver/data/registry-test-4-events/expected-event-catalog.json @@ -0,0 +1,140 @@ +[ + { + "name": "ios.state", + "type": { + "allow_custom_values": false, + "members": [ + { + "id": "active", + "value": "active", + "brief": "The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`.\n", + "note": null + }, + { + "id": "inactive", + "value": "inactive", + "brief": "The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`.\n", + "note": null + }, + { + "id": "background", + "value": "background", + "brief": "The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`.\n", + "note": null + }, + { + "id": "foreground", + "value": "foreground", + "brief": "The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`.\n", + "note": null + }, + { + "id": "terminate", + "value": "terminate", + "brief": "The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`.\n", + "note": null + } + ] + }, + "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", + "requirement_level": "required", + "note": "The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived.\n" + }, + { + "name": "android.state", + "type": { + "allow_custom_values": false, + "members": [ + { + "id": "created", + "value": "created", + "brief": "Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time.\n", + "note": null + }, + { + "id": "background", + "value": "background", + "brief": "Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state.\n", + "note": null + }, + { + "id": "foreground", + "value": "foreground", + "brief": "Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states.", + "note": null + } + ] + }, + "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", + "requirement_level": "required", + "note": "The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived.\n" + }, + { + "name": "feature_flag.key", + "type": "string", + "brief": "The unique identifier of the feature flag.", + "examples": [ + "logo-color" + ], + "requirement_level": "required" + }, + { + "name": "feature_flag.provider_name", + "type": "string", + "brief": "The name of the service provider that performs the flag evaluation.", + "examples": [ + "Flag Manager" + ], + "requirement_level": "recommended" + }, + { + "name": "feature_flag.variant", + "type": "string", + "brief": "SHOULD be a semantic identifier for a value. If one is unavailable, a stringified version of the value can be used.\n", + "examples": [ + "red", + "true", + "on" + ], + "requirement_level": "recommended", + "note": "A semantic identifier, commonly referred to as a variant, provides a means\nfor referring to a value without including the value itself. This can\nprovide additional context for understanding the meaning behind a value.\nFor example, the variant `red` maybe be used for the value `#c05543`.\n\nA stringified version of the value can be used in situations where a\nsemantic identifier is unavailable. String representation of the value\nshould be determined by the implementer." + }, + { + "name": "exception.type", + "type": "string", + "brief": "The type of the exception (its fully-qualified class name, if applicable). The dynamic type of the exception should be preferred over the static type in languages that support it.\n", + "examples": [ + "java.net.ConnectException", + "OSError" + ], + "requirement_level": "recommended", + "stability": "experimental" + }, + { + "name": "exception.message", + "type": "string", + "brief": "The exception message.", + "examples": [ + "Division by zero", + "Can't convert 'int' object to str implicitly" + ], + "requirement_level": "recommended", + "stability": "experimental" + }, + { + "name": "exception.stacktrace", + "type": "string", + "brief": "A stacktrace as a string in the natural representation for the language runtime. The representation is to be determined and documented by each language SIG.\n", + "examples": "Exception in thread \"main\" java.lang.RuntimeException: Test exception\\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at com.example.GenerateTrace.main(GenerateTrace.java:5)", + "requirement_level": "recommended", + "stability": "stable" + }, + { + "name": "exception.escaped", + "type": "boolean", + "brief": "SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span.\n", + "requirement_level": "recommended", + "note": "An exception is considered to have escaped (or left) the scope of a span,\nif that span is ended while the exception is still logically \"in flight\".\nThis may be actually \"in flight\" in some languages (e.g. if the exception\nis passed to a Context manager's `__exit__` method in Python) but will\nusually be caught at the point of recording the exception in most languages.\n\nIt is usually not possible to determine at the point where an exception is thrown\nwhether it will escape the scope of a span.\nHowever, it is trivial to know that an exception\nwill escape, if one checks for an active exception just before ending the span,\nas done in the [example for recording span exceptions](#recording-an-exception).\n\nIt follows that an exception may still escape the scope of the span\neven if the `exception.escaped` attribute was not set or set to false,\nsince the event might have been recorded at a time where it was not\nclear whether the exception will escape.", + "stability": "experimental" + } + ] \ No newline at end of file diff --git a/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json b/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json index d2db1ae2..0b3acd65 100644 --- a/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json +++ b/crates/weaver_resolver/data/registry-test-4-events/expected-registry.json @@ -1,15 +1,206 @@ { "registry_url": "https://127.0.0.1", "groups": [ + { + "id": "registry.browser", + "type": "event", + "brief": "The web browser attributes\n", + "prefix": "browser", + "attributes": [ + 0, + 1, + 2, + 3 + ], + "lineage": { + "source_file": "data/registry-test-4-events/registry/browser-event.yaml" + } + }, + { + "id": "browser.pageview", + "type": "event", + "brief": "This event represents an occurrence of a page view.\n", + "attributes": [ + 1, + 8 + ], + "name": "browser.pageview", + "lineage": { + "source_file": "data/registry-test-4-events/registry/browser-ref-event.yaml", + "attributes": { + "browser.platform": { + "source_group": "registry.browser", + "inherited_fields": [ + "brief", + "examples", + "note", + "requirement_level" + ] + }, + "session.id": { + "source_group": "referenced.attributes", + "inherited_fields": [ + "brief", + "examples", + "note", + "requirement_level", + "stability" + ] + } + } + }, + "body": { + "fields": [ + { + "name": "referrer", + "attr": 6, + "alias": "referrer", + "type": "string", + "brief": "This attribute represents the referring page (if available).", + "examples": [ + "https://example.com" + ], + "requirement_level": "recommended", + "note": "Referring Page URI (`document.referrer`) whenever available. The user agent SHOULD NOT send the fragment (`#` followed by more data) portion of the URI.\n", + "stability": "stable" + }, + { + "name": "title", + "type": "string", + "brief": "This attribute represents the title of the page that was viewed.\n", + "examples": [ + "Wikipedia - The Free Encyclopedia" + ], + "requirement_level": "recommended", + "note": "The document title (`document.title`) of the page that was viewed. It MAY be equal to the `url` attribute or empty if it is not applicable.\n" + }, + { + "name": "url", + "attr": 6, + "alias": "url", + "type": "string", + "brief": "This attribute represents the URL of the page that was viewed.\n", + "examples": [ + "https://en.wikipedia.org/wiki/Main_Page", + "https://en.wikipedia.org/wiki/Main_Page#foo" + ], + "requirement_level": "recommended", + "note": "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. The fragment may be used for `virtual_page`s to disambiguate pages with the same URL (e.g., due to pagination).\n", + "stability": "stable" + }, + { + "name": "type", + "type": { + "allow_custom_values": false, + "members": [ + { + "id": "physical_page", + "value": "0", + "brief": "Initial page load within the browser and will generally also precede a PageNavigationTiming event.\n", + "note": null, + "stability": null, + "deprecated": null + }, + { + "id": "virtual_page", + "value": "1", + "brief": "This is for Single Page Applications (SPA) where the framework provides the ability to perform client side only page \"navigation\", the exact definition of what a virtual page change is determined by the SPA and the framework it is using.\n", + "note": null, + "stability": null, + "deprecated": null + } + ] + }, + "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", + "requirement_level": "recommended", + "note": "This identifies the type of page view. For example, if the page view represents a user \n" + } + ] + } + }, + { + "id": "client.exception.event", + "type": "event", + "brief": "This document defines the log event used to report a client exception.\n", + "attributes": [ + 4 + ], + "name": "client.exception.event", + "lineage": { + "source_file": "data/registry-test-4-events/registry/client-exception-event.yaml" + }, + "body": { + "fields": [ + { + "name": "type", + "type": "string", + "brief": "The type of the exception.\n", + "examples": [ + "java.net.ConnectException", + "OSError" + ], + "requirement_level": "recommended" + }, + { + "name": "message", + "type": "string", + "brief": "The exception message.", + "examples": [ + "Division by zero", + "Can't convert 'int' object to str implicitly" + ], + "requirement_level": "recommended" + }, + { + "name": "stacktrace", + "type": "string", + "brief": "A stacktrace.\n", + "examples": "Exception in thread \"main\" java.lang.RuntimeException: Test exception\\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\\n at com.example.GenerateTrace.main(GenerateTrace.java:5)", + "requirement_level": "recommended" + }, + { + "name": "escaped", + "type": "boolean", + "brief": "SHOULD be set to true if the exception event is recorded at a point where it is known that the exception is escaping the scope of the span.\n", + "requirement_level": "recommended", + "note": "An exception is considered to have escaped." + } + ] + } + }, + { + "id": "log_event_attributes_only", + "type": "event", + "brief": "brief", + "attributes": [ + 7 + ], + "name": "some.event", + "lineage": { + "source_file": "data/registry-test-4-events/registry/log-event.yaml", + "attributes": { + "log.event.attr": { + "source_group": "referenced.attributes", + "inherited_fields": [ + "brief", + "examples", + "note", + "requirement_level", + "stability" + ] + } + } + } + }, { "id": "log-feature_flag", "type": "event", "brief": "This document defines attributes for feature flag evaluations represented using Log Records.\n", "prefix": "feature_flag", "attributes": [ - 2, - 3, - 4 + 9, + 10, + 11 ], "lineage": { "source_file": "data/registry-test-4-events/registry/log-feature_flag.yaml", @@ -45,29 +236,135 @@ } }, { - "id": "ios.lifecycle.events", + "id": "device.app.lifecycle", "type": "event", - "brief": "This event represents an occurrence of a lifecycle transition on the iOS platform.\n", - "prefix": "ios", - "attributes": [ - 0 - ], + "brief": "This event represents an occurrence of a lifecycle transition on Android or iOS platform.\n", + "note": "This event identifies the fields that are common to all lifecycle events for android and iOS using the `android.state` and `ios.state` fields. The `android.state` and `ios.state` attributes are mutually exclusive.\n", + "stability": "experimental", + "attributes": [], "name": "device.app.lifecycle", "lineage": { "source_file": "data/registry-test-4-events/registry/mobile-events.yaml" + }, + "body": { + "fields": [ + { + "name": "ios.state", + "type": { + "allow_custom_values": false, + "members": [ + { + "id": "active", + "value": "active", + "brief": "The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`.\n", + "note": null, + "stability": null, + "deprecated": null + }, + { + "id": "inactive", + "value": "inactive", + "brief": "The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`.\n", + "note": null, + "stability": null, + "deprecated": null + }, + { + "id": "background", + "value": "background", + "brief": "The app is now in the background. This value is associated with UIKit notification `applicationDidEnterBackground`.\n", + "note": null, + "stability": null, + "deprecated": null + }, + { + "id": "foreground", + "value": "foreground", + "brief": "The app is now in the foreground. This value is associated with UIKit notification `applicationWillEnterForeground`.\n", + "note": null, + "stability": null, + "deprecated": null + }, + { + "id": "terminate", + "value": "terminate", + "brief": "The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`.\n", + "note": null, + "stability": null, + "deprecated": null + } + ] + }, + "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", + "requirement_level": { + "conditionally_required": "if and only if `os.name` is `ios`" + }, + "note": "The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), and from which the `OS terminology` column values are derived.\n", + "stability": "experimental" + }, + { + "name": "android.state", + "type": { + "allow_custom_values": false, + "members": [ + { + "id": "created", + "value": "created", + "brief": "Any time before Activity.onResume() or, if the app has no Activity, Context.startService() has been called in the app for the first time.\n", + "note": null, + "stability": null, + "deprecated": null + }, + { + "id": "background", + "value": "background", + "brief": "Any time after Activity.onPause() or, if the app has no Activity, Context.stopService() has been called when the app was in the foreground state.\n", + "note": null, + "stability": null, + "deprecated": null + }, + { + "id": "foreground", + "value": "foreground", + "brief": "Any time after Activity.onResume() or, if the app has no Activity, Context.startService() has been called when the app was in either the created or background states.", + "note": null, + "stability": null, + "deprecated": null + } + ] + }, + "brief": "This attribute represents the state the application has transitioned into at the occurrence of the event.\n", + "requirement_level": { + "conditionally_required": "if and only if `os.name` is `android`" + }, + "note": "The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), and from which the `OS identifiers` are derived.\n", + "stability": "experimental" + } + ] } }, { - "id": "android.lifecycle.events", + "id": "log_event_empty", "type": "event", - "brief": "This event represents an occurrence of a lifecycle transition on the Android platform.\n", - "prefix": "android", + "brief": "brief", + "attributes": [], + "name": "ping.event", + "lineage": { + "source_file": "data/registry-test-4-events/registry/ping-event.yaml" + } + }, + { + "id": "referenced.attributes", + "type": "attribute_group", + "brief": "These attributes are used as references for the test below\n", "attributes": [ - 1 + 5, + 6, + 7, + 8 ], - "name": "device.app.lifecycle", "lineage": { - "source_file": "data/registry-test-4-events/registry/mobile-events.yaml" + "source_file": "data/registry-test-4-events/registry/referenced-attributes.yaml" } }, { @@ -76,9 +373,9 @@ "brief": "This semantic convention defines the attributes used to represent a feature flag evaluation as an event.\n", "prefix": "feature_flag", "attributes": [ - 2, - 3, - 4 + 9, + 10, + 11 ], "lineage": { "source_file": "data/registry-test-4-events/registry/trace-feature-flag.yaml" diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/browser-event.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/browser-event.yaml new file mode 100644 index 00000000..ef74dc3d --- /dev/null +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/browser-event.yaml @@ -0,0 +1,46 @@ +groups: + - id: registry.browser + prefix: browser + type: event + brief: > + The web browser attributes + attributes: + - id: brands + type: string[] + brief: 'Array of brand name and version separated by a space' + note: > + This value is intended to be taken from the + [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) + (`navigator.userAgentData.brands`). + examples: [ " Not A;Brand 99", "Chromium 99", "Chrome 99" ] + - id: platform + type: string + brief: 'The platform on which the browser is running' + note: > + This value is intended to be taken from the + [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) + (`navigator.userAgentData.platform`). If unavailable, the legacy + `navigator.platform` API SHOULD NOT be used instead and this attribute + SHOULD be left unset in order for the values to be consistent. + The list of possible values is defined in the + [W3C User-Agent Client Hints specification](https://wicg.github.io/ua-client-hints/#sec-ch-ua-platform). + Note that some (but not all) of these values can overlap with values + in the [`os.type` and `os.name` attributes](./os.md). + However, for consistency, the values in the `browser.platform` attribute + should capture the exact value that the user agent provides. + examples: ['Windows', 'macOS', 'Android'] + - id: mobile + type: boolean + brief: 'A boolean that is true if the browser is running on a mobile device' + note: > + This value is intended to be taken from the + [UA client hints API](https://wicg.github.io/ua-client-hints/#interface) + (`navigator.userAgentData.mobile`). If unavailable, this attribute + SHOULD be left unset. + - id: language + type: string + brief: 'Preferred language of the user using the browser' + note: > + This value is intended to be taken from the Navigator API + `navigator.language`. + examples: ["en", "en-US", "fr", "fr-FR"] \ No newline at end of file diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/browser-ref-event.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/browser-ref-event.yaml new file mode 100644 index 00000000..ef216d23 --- /dev/null +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/browser-ref-event.yaml @@ -0,0 +1,52 @@ +groups: + - id: browser.pageview + name: browser.pageview + type: event + brief: > + This event represents an occurrence of a page view. + attributes: + - ref: session.id + - ref: browser.platform + body: + fields: + - ref: http.url + alias: referrer + note: > + Referring Page URI (`document.referrer`) whenever available. The user agent SHOULD NOT send the fragment + (`#` followed by more data) portion of the URI. + brief: This attribute represents the referring page (if available). + - id: title + brief: > + This attribute represents the title of the page that was viewed. + note: > + The document title (`document.title`) of the page that was viewed. It MAY be equal to the `url` attribute + or empty if it is not applicable. + type: string + examples: ['Wikipedia - The Free Encyclopedia'] + - ref: http.url + alias: url + note: > + 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. The fragment may be + used for `virtual_page`s to disambiguate pages with the same URL (e.g., due to pagination). + brief: > + This attribute represents the URL of the page that was viewed. + examples: ['https://en.wikipedia.org/wiki/Main_Page', 'https://en.wikipedia.org/wiki/Main_Page#foo'] + - id: type + brief: > + This attribute represents the state the application has transitioned into at the occurrence of the event. + note: > + This identifies the type of page view. For example, if the page view represents a user + type: + allow_custom_values: false + members: + - id: physical_page + value: '0' + brief: > + Initial page load within the browser and will generally also precede a PageNavigationTiming event. + - id: virtual_page + value: '1' + brief: > + This is for Single Page Applications (SPA) where the framework provides the ability to perform + client side only page "navigation", the exact definition of what a virtual page change is determined + by the SPA and the framework it is using. diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/client-exception-event.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/client-exception-event.yaml new file mode 100644 index 00000000..5e6819fa --- /dev/null +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/client-exception-event.yaml @@ -0,0 +1,39 @@ +groups: + - id: client.exception.event + name: client.exception.event + type: event + brief: > + This document defines the log event used to + report a client exception. + body: + fields: + - id: type + type: string + brief: > + The type of the exception. + examples: ["java.net.ConnectException","OSError"] + - id: message + type: string + brief: The exception message. + examples: ["Division by zero","Can't convert 'int' object to str implicitly"] + - id: stacktrace + type: string + brief: > + A stacktrace. + examples: 'Exception in thread "main" java.lang.RuntimeException: Test exception\n + at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n + at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n + at com.example.GenerateTrace.main(GenerateTrace.java:5)' + - id: escaped + type: boolean + brief: > + SHOULD be set to true if the exception event is recorded at a point where + it is known that the exception is escaping the scope of the span. + note: |- + An exception is considered to have escaped. + attributes: + - id: client.name + type: string + brief: > + The name of the client that reported the exception. + examples: ["myclient"] \ No newline at end of file diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/log-event.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/log-event.yaml new file mode 100644 index 00000000..e2556e1b --- /dev/null +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/log-event.yaml @@ -0,0 +1,7 @@ +groups: + - id: log_event_attributes_only + name: some.event + type: event + brief: brief + attributes: + - ref: log.event.attr diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml index 94f93ebb..b1b219d5 100644 --- a/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/mobile-events.yaml @@ -1,72 +1,74 @@ groups: - - id: ios.lifecycle.events + - id: device.app.lifecycle + stability: experimental type: event - prefix: ios name: device.app.lifecycle brief: > - This event represents an occurrence of a lifecycle transition on the iOS platform. - attributes: - - id: state - requirement_level: "required" - note: > - The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), - and from which the `OS terminology` column values are derived. - brief: > - This attribute represents the state the application has transitioned into at the occurrence of the event. - type: - allow_custom_values: false - members: - - id: active - value: 'active' - brief: > - The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`. - - id: inactive - value: 'inactive' - brief: > - The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`. - - id: background - value: 'background' - brief: > - The app is now in the background. - This value is associated with UIKit notification `applicationDidEnterBackground`. - - id: foreground - value: 'foreground' - brief: > - The app is now in the foreground. - This value is associated with UIKit notification `applicationWillEnterForeground`. - - id: terminate - value: 'terminate' - brief: > - The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`. - - id: android.lifecycle.events - type: event - prefix: android - name: device.app.lifecycle - brief: > - This event represents an occurrence of a lifecycle transition on the Android platform. - attributes: - - id: state - requirement_level: required - brief: > - This attribute represents the state the application has transitioned into at the occurrence of the event. - note: > - The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), - and from which the `OS identifiers` are derived. - type: - allow_custom_values: false - members: - - id: created - value: 'created' - brief: > - Any time before Activity.onResume() or, if the app has no Activity, Context.startService() - has been called in the app for the first time. - - id: background - value: 'background' - brief: > - Any time after Activity.onPause() or, if the app has no Activity, - Context.stopService() has been called when the app was in the foreground state. - - id: foreground - value: 'foreground' - brief: > - Any time after Activity.onResume() or, if the app has no Activity, - Context.startService() has been called when the app was in either the created or background states. \ No newline at end of file + This event represents an occurrence of a lifecycle transition on Android or iOS platform. + note: > + This event identifies the fields that are common to all lifecycle events for android and iOS using + the `android.state` and `ios.state` fields. The `android.state` and `ios.state` attributes are + mutually exclusive. + body: + fields: + - id: ios.state + stability: experimental + requirement_level: + conditionally_required: if and only if `os.name` is `ios` + note: > + The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), + and from which the `OS terminology` column values are derived. + brief: > + This attribute represents the state the application has transitioned into at the occurrence of the event. + type: + allow_custom_values: false + members: + - id: active + value: 'active' + brief: > + The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`. + - id: inactive + value: 'inactive' + brief: > + The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`. + - id: background + value: 'background' + brief: > + The app is now in the background. + This value is associated with UIKit notification `applicationDidEnterBackground`. + - id: foreground + value: 'foreground' + brief: > + The app is now in the foreground. + This value is associated with UIKit notification `applicationWillEnterForeground`. + - id: terminate + value: 'terminate' + brief: > + The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`. + - id: android.state + stability: experimental + requirement_level: + conditionally_required: if and only if `os.name` is `android` + brief: > + This attribute represents the state the application has transitioned into at the occurrence of the event. + note: > + The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), + and from which the `OS identifiers` are derived. + type: + allow_custom_values: false + members: + - id: created + value: 'created' + brief: > + Any time before Activity.onResume() or, if the app has no Activity, Context.startService() + has been called in the app for the first time. + - id: background + value: 'background' + brief: > + Any time after Activity.onPause() or, if the app has no Activity, + Context.stopService() has been called when the app was in the foreground state. + - id: foreground + value: 'foreground' + brief: > + Any time after Activity.onResume() or, if the app has no Activity, + Context.startService() has been called when the app was in either the created or background states. \ No newline at end of file diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/ping-event.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/ping-event.yaml new file mode 100644 index 00000000..a9ea856e --- /dev/null +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/ping-event.yaml @@ -0,0 +1,5 @@ +groups: + - id: log_event_empty + name: ping.event + type: event + brief: brief \ No newline at end of file diff --git a/crates/weaver_resolver/data/registry-test-4-events/registry/referenced-attributes.yaml b/crates/weaver_resolver/data/registry-test-4-events/registry/referenced-attributes.yaml new file mode 100644 index 00000000..8dad8dd0 --- /dev/null +++ b/crates/weaver_resolver/data/registry-test-4-events/registry/referenced-attributes.yaml @@ -0,0 +1,32 @@ +groups: + + # As the tests are only loading a subset of files we need to define any referenced attributes before they can be resolved for body fields + - id: referenced.attributes + type: attribute_group + brief: > + These attributes are used as references for the test below + attributes: + - id: browser.platform + stability: stable + type: string + brief: 'The browser platform' + note: Test value. + examples: ['Windows', 'macOS', 'Android'] + - id: http.url + stability: stable + type: string + brief: 'The Url' + note: Test url value. + examples: ['https://example.com'] + - id: log.event.attr + stability: stable + type: string + brief: 'Just making sure the referenced attributes are defined' + note: Test value. + examples: some value + - id: session.id + stability: stable + type: string + brief: 'The session id' + note: Test value. + examples: 127836abcdef98 diff --git a/crates/weaver_resolver/src/attribute.rs b/crates/weaver_resolver/src/attribute.rs index bd617df5..5cb8c27d 100644 --- a/crates/weaver_resolver/src/attribute.rs +++ b/crates/weaver_resolver/src/attribute.rs @@ -2,6 +2,7 @@ //! Attribute resolution. +use core::panic; use std::collections::HashMap; use serde::Deserialize; @@ -22,13 +23,50 @@ pub struct AttributeCatalog { root_attributes: HashMap, } + #[derive(Debug, PartialEq)] -struct AttributeWithGroupId { +/// The Attribute with its group ID. +pub struct AttributeWithGroupId { + /// The attribute. pub attribute: attribute::Attribute, + /// The group ID. pub group_id: String, } +/// A reference to an attribute with its original attribute and group ID. +pub struct AttributeRefWithGroup { + /// The attribute reference. + pub attr_ref: AttributeRef, + /// The attribute with its group ID. + pub attr: AttributeWithGroupId, +} + impl AttributeCatalog { + /// Returns the given attribute from the catalog. + pub fn get_attribute(&self, name: &str) -> Option<&AttributeWithGroupId> { + self.root_attributes.get(name) + } + + /// Returns the reference of the given attribute. + pub fn get_attribute_ref(&mut self, name: &str) -> AttributeRefWithGroup { + let attr = self.get_attribute(name); + if attr.is_none() { + panic!("Attribute {} not found in the catalog", name); + } + let the_attrib = attr.unwrap(); + let attr = &the_attrib.attribute; + let group_id = the_attrib.group_id.clone(); + let attr_ref = self.attribute_refs.get(attr); + + AttributeRefWithGroup { + attr_ref: attr_ref.unwrap().clone(), + attr: AttributeWithGroupId { + attribute: attr.clone(), + group_id: group_id, + }, + } + } + /// Returns the reference of the given attribute or creates a new reference if the attribute /// does not exist in the catalog. pub fn attribute_ref(&mut self, attr: attribute::Attribute) -> AttributeRef { diff --git a/crates/weaver_resolver/src/body.rs b/crates/weaver_resolver/src/body.rs new file mode 100644 index 00000000..49320cb7 --- /dev/null +++ b/crates/weaver_resolver/src/body.rs @@ -0,0 +1,88 @@ + +// SPDX-License-Identifier: Apache-2.0 + +//! Functions to resolve a semantic convention body. + +use weaver_resolved_schema::body::{Body, BodyField}; +use weaver_semconv::body::{BodyFieldSpec, BodySpec}; + +use crate::attribute::AttributeCatalog; + +/// Resolve a body specification into a resolved body. +pub fn resolve_body( + body: &BodySpec, + attr_catalog: &mut AttributeCatalog +) -> Option { + match body { + BodySpec::Fields { fields } => { + let fields = fields.iter().map(|field| resolve_body_field(field, attr_catalog)).collect(); + Some(Body { + fields: Some(fields), + value: None, + }) + }, + BodySpec::Value { value: _ } => { + // Add as a placeholder for now of where to resolve the value. + todo!("Implement value type for body.") + } + } +} + +/// Resolve a body field specification into a resolved body field. +pub fn resolve_body_field( + field: &BodyFieldSpec, + attr_catalog: &mut AttributeCatalog +) -> BodyField { + match field { + BodyFieldSpec::Ref { + r#ref, + alias, + brief, + examples, + requirement_level, + note, + stability, + deprecated, + } => { + let attr_ref = attr_catalog.get_attribute_ref(r#ref); + + let root_attr = attr_ref.attr.attribute.clone(); + BodyField { + name: field.id(), + r#attr: Some(attr_ref.attr_ref), + alias: alias.clone(), + r#type: root_attr.r#type.clone(), + brief: if brief.is_some() { brief.clone().unwrap_or_default() } else { root_attr.brief.to_owned() }, + examples: if examples.is_some() { examples.clone() } else { root_attr.examples.to_owned() }, + requirement_level: if requirement_level.is_some() { requirement_level.as_ref().unwrap().clone() } else { root_attr.requirement_level.to_owned() }, + note: if note.is_some() { note.clone().unwrap_or_default() } else { root_attr.note.to_owned() }, + stability: if stability.is_some() { stability.clone() } else { root_attr.stability.to_owned() }, + deprecated: if deprecated.is_some() { deprecated.clone() } else { root_attr.deprecated.to_owned() }, + } + }, + BodyFieldSpec::Id { + id, + r#type, + brief, + examples, + requirement_level, + note, + stability, + deprecated, + } => { + BodyField { + name: id.clone(), + r#attr: None, + alias: None, + r#type: r#type.clone(), + brief: brief.clone().unwrap_or_default(), + examples: examples.clone(), + requirement_level: requirement_level.clone(), + note: note.clone(), + stability: stability.clone(), + deprecated: deprecated.clone() + } + } + } +} + diff --git a/crates/weaver_resolver/src/lib.rs b/crates/weaver_resolver/src/lib.rs index 6d8b1948..0d4e33e1 100644 --- a/crates/weaver_resolver/src/lib.rs +++ b/crates/weaver_resolver/src/lib.rs @@ -26,6 +26,7 @@ use crate::attribute::AttributeCatalog; use crate::registry::resolve_semconv_registry; pub mod attribute; +pub mod body; mod constraint; pub mod registry; diff --git a/crates/weaver_resolver/src/registry.rs b/crates/weaver_resolver/src/registry.rs index fa7c6241..8af04bdc 100644 --- a/crates/weaver_resolver/src/registry.rs +++ b/crates/weaver_resolver/src/registry.rs @@ -8,6 +8,7 @@ use serde::Deserialize; use weaver_common::error::handle_errors; use weaver_resolved_schema::attribute::UnresolvedAttribute; +use weaver_resolved_schema::body::UnresolvedBody; use weaver_resolved_schema::lineage::{AttributeLineage, GroupLineage}; use weaver_resolved_schema::registry::{Constraint, Group, Registry}; use weaver_semconv::attribute::AttributeSpec; @@ -15,6 +16,7 @@ use weaver_semconv::group::GroupSpecWithProvenance; use weaver_semconv::registry::SemConvRegistry; use crate::attribute::AttributeCatalog; +use crate::body::resolve_body; use crate::constraint::resolve_constraints; use crate::{Error, UnsatisfiedAnyOfConstraint}; @@ -42,6 +44,9 @@ pub struct UnresolvedGroup { /// and other signals, into the group field once they are resolved. pub attributes: Vec, + /// The unresolved body that belongs to this group + pub body: Option, + /// The provenance of the group (URL or path). pub provenance: String, } @@ -82,6 +87,8 @@ pub fn resolve_semconv_registry( resolve_include_constraints(&mut ureg)?; + resolve_body_references(&mut ureg, attr_catalog); + // Sort the attribute internal references in each group. // This is needed to ensure that the resolved registry is easy to compare // in unit tests. @@ -104,6 +111,7 @@ pub fn resolve_semconv_registry( group.constraints.clear(); } + Ok(ureg.registry) } @@ -265,8 +273,10 @@ fn group_from_spec(group: GroupSpecWithProvenance) -> UnresolvedGroup { name: group.spec.name, lineage: Some(GroupLineage::new(&group.provenance)), display_name: group.spec.display_name, + body: None, // resolve_body(&group.spec.body, &group.spec.attributes, &group.provenance), }, attributes: attrs, + body: group.spec.body.is_some().then(|| UnresolvedBody { spec: group.spec.body.unwrap() }), provenance: group.provenance, } } @@ -685,6 +695,18 @@ fn resolve_inheritance_attr( } } +fn resolve_body_references( + ureg: &mut UnresolvedRegistry, + attr_catalog: &mut AttributeCatalog, +) { + for unresolved_group in ureg.groups.iter_mut() { + let body = &unresolved_group.body; + if !body.is_none() { + unresolved_group.group.body = resolve_body(&body.as_ref().unwrap().spec, attr_catalog); + } + } +} + #[cfg(test)] mod tests { use std::collections::HashSet; diff --git a/crates/weaver_semconv/data/event.yaml b/crates/weaver_semconv/data/event.yaml new file mode 100644 index 00000000..b1b219d5 --- /dev/null +++ b/crates/weaver_semconv/data/event.yaml @@ -0,0 +1,74 @@ +groups: + - id: device.app.lifecycle + stability: experimental + type: event + name: device.app.lifecycle + brief: > + This event represents an occurrence of a lifecycle transition on Android or iOS platform. + note: > + This event identifies the fields that are common to all lifecycle events for android and iOS using + the `android.state` and `ios.state` fields. The `android.state` and `ios.state` attributes are + mutually exclusive. + body: + fields: + - id: ios.state + stability: experimental + requirement_level: + conditionally_required: if and only if `os.name` is `ios` + note: > + The iOS lifecycle states are defined in the [UIApplicationDelegate documentation](https://developer.apple.com/documentation/uikit/uiapplicationdelegate#1656902), + and from which the `OS terminology` column values are derived. + brief: > + This attribute represents the state the application has transitioned into at the occurrence of the event. + type: + allow_custom_values: false + members: + - id: active + value: 'active' + brief: > + The app has become `active`. Associated with UIKit notification `applicationDidBecomeActive`. + - id: inactive + value: 'inactive' + brief: > + The app is now `inactive`. Associated with UIKit notification `applicationWillResignActive`. + - id: background + value: 'background' + brief: > + The app is now in the background. + This value is associated with UIKit notification `applicationDidEnterBackground`. + - id: foreground + value: 'foreground' + brief: > + The app is now in the foreground. + This value is associated with UIKit notification `applicationWillEnterForeground`. + - id: terminate + value: 'terminate' + brief: > + The app is about to terminate. Associated with UIKit notification `applicationWillTerminate`. + - id: android.state + stability: experimental + requirement_level: + conditionally_required: if and only if `os.name` is `android` + brief: > + This attribute represents the state the application has transitioned into at the occurrence of the event. + note: > + The Android lifecycle states are defined in [Activity lifecycle callbacks](https://developer.android.com/guide/components/activities/activity-lifecycle#lc), + and from which the `OS identifiers` are derived. + type: + allow_custom_values: false + members: + - id: created + value: 'created' + brief: > + Any time before Activity.onResume() or, if the app has no Activity, Context.startService() + has been called in the app for the first time. + - id: background + value: 'background' + brief: > + Any time after Activity.onPause() or, if the app has no Activity, + Context.stopService() has been called when the app was in the foreground state. + - id: foreground + value: 'foreground' + brief: > + Any time after Activity.onResume() or, if the app has no Activity, + Context.startService() has been called when the app was in either the created or background states. \ No newline at end of file diff --git a/crates/weaver_semconv/src/body.rs b/crates/weaver_semconv/src/body.rs new file mode 100644 index 00000000..580c3345 --- /dev/null +++ b/crates/weaver_semconv/src/body.rs @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: Apache-2.0 + +#![allow(rustdoc::invalid_html_tags)] + +//! Body Field specification. + +use serde::{Deserialize, Serialize}; + +use crate::stability::Stability; +use crate::attribute::{AttributeType, BasicRequirementLevelSpec, Examples, RequirementLevel, ValueSpec}; + +/// A body specification +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +#[serde(rename_all = "snake_case")] +pub enum BodySpec { + /// The collection of body fields associated with a body definition + Fields { + /// The collection of body fields associated with a body definition + #[serde(skip_serializing_if = "Vec::is_empty")] + fields: Vec + }, + /// The body field value. + Value { + /// The body field value. + value: ValueSpec, + } +} + +impl BodySpec { + /// Returns true if the body field is required. + #[must_use] + pub fn has_fields(&self) -> bool { + match self { + BodySpec::Fields { fields } => !fields.is_empty(), + BodySpec::Value { value: _ } => false, + } + } +} + +/// A body field specification. +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(deny_unknown_fields)] +#[serde(untagged)] +#[serde(rename_all = "snake_case")] +pub enum BodyFieldSpec { + /// Reference to an attribute. + /// + /// ref MUST have an id of an existing attribute. + Ref { + /// Reference an existing attribute. + r#ref: String, + /// The alias to use for the referenced attribute in this body field, + /// if not specified, the id of the referenced attribute is used. + #[serde(skip_serializing_if = "Option::is_none")] + alias: Option, + /// A brief description of the attribute. + #[serde(skip_serializing_if = "Option::is_none")] + brief: Option, + /// Sequence of example values for the attribute or single example + /// value. They are required only for string and string array + /// attributes. Example values must be of the same type of the + /// attribute. If only a single example is provided, it can directly + /// be reported without encapsulating it into a sequence/dictionary. + #[serde(skip_serializing_if = "Option::is_none")] + examples: Option, + /// Specifies if the attribute is mandatory. Can be "required", + /// "conditionally_required", "recommended" or "opt_in". When omitted, + /// the attribute is "recommended". When set to + /// "conditionally_required", the string provided as MUST + /// specify the conditions under which the attribute is required. + #[serde(skip_serializing_if = "Option::is_none")] + requirement_level: Option, + /// A more elaborate description of the attribute. + /// It defaults to an empty string. + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + note: Option, + /// Specifies the stability of the attribute. + /// Note that, if stability is missing but deprecated is present, it will + /// automatically set the stability to deprecated. If deprecated is + /// present and stability differs from deprecated, this will result in an + /// error. + #[serde(skip_serializing_if = "Option::is_none")] + stability: Option, + /// Specifies if the attribute is deprecated. The string + /// provided as MUST specify why it's deprecated and/or what + /// to use instead. See also stability. + #[serde(skip_serializing_if = "Option::is_none")] + deprecated: Option, + }, + /// Body Field definition. + Id { + /// String that uniquely identifies the body field. + id: String, + /// Either a string literal denoting the type as a primitive or an + /// array type, a template type or an enum definition. + r#type: AttributeType, + /// A brief description of the body field. + brief: Option, + /// Sequence of example values for the body field or single example + /// value. They are required only for string and string array + /// attributes. Example values must be of the same type of the + /// body field. If only a single example is provided, it can directly + /// be reported without encapsulating it into a sequence/dictionary. + #[serde(skip_serializing_if = "Option::is_none")] + examples: Option, + /// Specifies if the body field is mandatory. Can be "required", + /// "conditionally_required", "recommended" or "opt_in". When omitted, + /// the body field is "recommended". When set to + /// "conditionally_required", the string provided as MUST + /// specify the conditions under which the body field is required. + #[serde(default)] + requirement_level: RequirementLevel, + /// A more elaborate description of the body field. + /// It defaults to an empty string. + #[serde(default)] + note: String, + /// Specifies the stability of the body field. + /// Note that, if stability is missing but deprecated is present, it will + /// automatically set the stability to deprecated. If deprecated is + /// present and stability differs from deprecated, this will result in an + /// error. + #[serde(skip_serializing_if = "Option::is_none")] + stability: Option, + /// Specifies if the body field is deprecated. The string + /// provided as MUST specify why it's deprecated and/or what + /// to use instead. See also stability. + #[serde(skip_serializing_if = "Option::is_none")] + deprecated: Option, + }, +} + +impl BodyFieldSpec { + /// Returns true if the body field is required. + #[must_use] + pub fn is_required(&self) -> bool { + matches!( + self, + BodyFieldSpec::Id { + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Required), + .. + } | + BodyFieldSpec::Ref { + requirement_level: Some(RequirementLevel::Basic( + BasicRequirementLevelSpec::Required + )), + .. + } + ) + } + + /// Returns the id of the body field. + #[must_use] + pub fn id(&self) -> String { + match self { + BodyFieldSpec::Ref { r#ref, alias, .. } => { + if alias.is_some() { + alias.clone().unwrap_or_default() + } else { + r#ref.clone() + } + }, + BodyFieldSpec::Id { id, .. } => id.clone(), + } + } + + /// Returns the brief of the body field. + #[must_use] + pub fn brief(&self) -> String { + match self { + BodyFieldSpec::Ref { brief, .. } => brief.clone().unwrap_or_default(), + BodyFieldSpec::Id { brief, .. } => brief.clone().unwrap_or_default(), + } + } + + /// Returns the note of the body field. + #[must_use] + pub fn note(&self) -> String { + match self { + BodyFieldSpec::Ref { note, .. } => note.clone().unwrap_or_default(), + BodyFieldSpec::Id { note, .. } => note.clone(), + } + } +} + +// /// The different types of body fields (specification). +// #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash, JsonSchema)] +// #[serde(rename_all = "snake_case")] +// #[serde(untagged)] +// pub enum BodyFieldType { +// /// Primitive or array type. +// PrimitiveOrArray(PrimitiveOrArrayTypeSpec), +// /// A template type. +// Template(TemplateTypeSpec), +// /// An enum definition type. +// Enum { +// /// Set to false to not accept values other than the specified members. +// /// It defaults to true. +// #[serde(default = "default_as_true")] +// allow_custom_values: bool, +// /// List of enum entries. +// members: Vec, +// }, +// } + +/// Implements a human readable display for BodyFieldType. +// impl Display for BodyFieldType { +// fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { +// match self { +// BodyFieldType::PrimitiveOrArray(t) => write!(f, "{}", t), +// BodyFieldType::Template(t) => write!(f, "{}", t), +// BodyFieldType::Enum { members, .. } => { +// let entries = members +// .iter() +// .map(|m| m.id.clone()) +// .collect::>() +// .join(", "); +// write!(f, "enum {{{}}}", entries) +// } +// } +// } +// } + +/// Specifies the default value for allow_custom_values. +// fn default_as_true() -> bool { +// true +// } + +#[cfg(test)] +mod tests { + use super::*; + use crate::attribute::{EnumEntriesSpec, PrimitiveOrArrayTypeSpec, TemplateTypeSpec, ValueSpec}; + + #[test] + fn test_body_field_type_display() { + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Boolean) + ), + "boolean" + ); + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Int) + ), + "int" + ); + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Double) + ), + "double" + ); + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::String) + ), + "string" + ); + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Strings) + ), + "string[]" + ); + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Ints) + ), + "int[]" + ); + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Doubles) + ), + "double[]" + ); + assert_eq!( + format!( + "{}", + AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Booleans) + ), + "boolean[]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::Boolean)), + "template[boolean]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::Int)), + "template[int]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::Double)), + "template[double]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::String)), + "template[string]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::Strings)), + "template[string[]]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::Ints)), + "template[int[]]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::Doubles)), + "template[double[]]" + ); + assert_eq!( + format!("{}", AttributeType::Template(TemplateTypeSpec::Booleans)), + "template[boolean[]]" + ); + assert_eq!( + format!( + "{}", + AttributeType::Enum { + allow_custom_values: true, + members: vec![EnumEntriesSpec { + id: "id".to_owned(), + value: ValueSpec::Int(42), + brief: Some("brief".to_owned()), + note: Some("note".to_owned()), + stability: None, + deprecated: None, + }] + } + ), + "enum {id}" + ); + } + + #[test] + fn test_body_field() { + let attr = BodyFieldSpec::Id { + id: "id".to_owned(), + r#type: AttributeType::PrimitiveOrArray(PrimitiveOrArrayTypeSpec::Int), + brief: Some("brief".to_owned()), + examples: Some(Examples::Int(42)), + requirement_level: RequirementLevel::Basic(BasicRequirementLevelSpec::Required), + note: "note".to_owned(), + stability: Some(Stability::Stable), + deprecated: Some("deprecated".to_owned()), + }; + assert_eq!(attr.id(), "id"); + assert_eq!(attr.brief(), "brief"); + assert_eq!(attr.note(), "note"); + assert!(attr.is_required()); + + let attr = BodyFieldSpec::Ref { + r#ref: "ref".to_owned(), + alias: None, + brief: Some("brief".to_owned()), + examples: Some(Examples::Int(42)), + requirement_level: Some(RequirementLevel::Basic(BasicRequirementLevelSpec::Required)), + note: Some("note".to_owned()), + stability: Some(Stability::Stable), + deprecated: Some("deprecated".to_owned()), + }; + assert_eq!(attr.id(), "ref"); + assert_eq!(attr.brief(), "brief"); + assert_eq!(attr.note(), "note"); + assert!(attr.is_required()); + + let attr = BodyFieldSpec::Ref { + r#ref: "ref".to_owned(), + alias: Some("theAlias".to_owned()), + brief: Some("brief".to_owned()), + examples: Some(Examples::Int(42)), + requirement_level: Some(RequirementLevel::Basic(BasicRequirementLevelSpec::Required)), + note: Some("note".to_owned()), + stability: Some(Stability::Stable), + deprecated: Some("deprecated".to_owned()), + }; + assert_eq!(attr.id(), "theAlias"); + assert_eq!(attr.brief(), "brief"); + assert_eq!(attr.note(), "note"); + assert!(attr.is_required()); + } +} + +/// An attribute definition with its provenance (path or URL). +#[derive(Debug, Clone)] +pub struct BodyFieldSpecWithProvenance { + /// The body field definition. + pub body_field: BodyFieldSpec, + /// The provenance of the body field (path or URL). + pub provenance: String, +} diff --git a/crates/weaver_semconv/src/group.rs b/crates/weaver_semconv/src/group.rs index e2e5e2c7..2cdf9d15 100644 --- a/crates/weaver_semconv/src/group.rs +++ b/crates/weaver_semconv/src/group.rs @@ -10,6 +10,7 @@ use std::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize}; use crate::attribute::{AttributeSpec, AttributeType, PrimitiveOrArrayTypeSpec}; +use crate::body::BodySpec; use crate::group::InstrumentSpec::{Counter, Gauge, Histogram, UpDownCounter}; use crate::stability::Stability; use crate::Error; @@ -86,6 +87,9 @@ pub struct GroupSpec { pub name: Option, /// The readable name for attribute groups used when generating registry tables. pub display_name: Option, + /// The event body definition + /// Note: only valid if type is event + pub body: Option, } impl GroupSpec { @@ -114,11 +118,22 @@ impl GroupSpec { } // Field name is required if prefix is empty and if type is event. - if self.r#type == GroupType::Event && self.prefix.is_empty() && self.name.is_none() { + if self.r#type == GroupType::Event { + if self.prefix.is_empty() && self.name.is_none() { + errors.push(Error::InvalidGroup { + path_or_url: path_or_url.to_owned(), + group_id: self.id.clone(), + error: "This group contains an event type but the prefix is empty and the name is not set.".to_owned(), + }); + } + + // TODO (MSNev): Add validation for the body field. + } else if self.body.is_some() { + // Make sure that body is only used for events errors.push(Error::InvalidGroup { path_or_url: path_or_url.to_owned(), group_id: self.id.clone(), - error: "This group contains an event type but the prefix is empty and the name is not set.".to_owned(), + error: "This group contains a body field but the type is not set to event.".to_owned(), }); } @@ -334,6 +349,7 @@ mod tests { unit: None, name: None, display_name: None, + body: None, }; assert!(group.validate("").is_ok()); @@ -434,6 +450,7 @@ mod tests { unit: None, name: None, display_name: None, + body: None, }; assert!(group.validate("").is_ok()); diff --git a/crates/weaver_semconv/src/lib.rs b/crates/weaver_semconv/src/lib.rs index 4b52ae4d..588ea114 100644 --- a/crates/weaver_semconv/src/lib.rs +++ b/crates/weaver_semconv/src/lib.rs @@ -9,6 +9,7 @@ use weaver_common::diagnostic::{DiagnosticMessage, DiagnosticMessages}; use weaver_common::error::{format_errors, WeaverError}; pub mod attribute; +pub mod body; pub mod group; pub mod metric; pub mod path; @@ -139,6 +140,7 @@ mod tests { "data/cloudevents.yaml", "data/database.yaml", "data/database-metrics.yaml", + "data/event.yaml", "data/exception.yaml", "data/faas.yaml", "data/faas-common.yaml", diff --git a/crates/weaver_semconv/src/registry.rs b/crates/weaver_semconv/src/registry.rs index 651aa4ae..83699794 100644 --- a/crates/weaver_semconv/src/registry.rs +++ b/crates/weaver_semconv/src/registry.rs @@ -261,6 +261,7 @@ mod tests { events: vec![], name: None, display_name: Some("Group 1".to_owned()), + body: None, }], }, ), @@ -285,6 +286,7 @@ mod tests { events: vec![], name: None, display_name: Some("Group 2".to_owned()), + body: None, }], }, ),