Skip to content

Commit 463406b

Browse files
feat: semantic conventions via Weaver (#1)
* feat: add OpenTelemetry semantic conventions support * chore: rename to PROJECT_DIR * port types and restart attributes Signed-off-by: inge4pres <[email protected]> * apply rich types to metrics and resource Signed-off-by: inge4pres <[email protected]> * apply rich types to trace Signed-off-by: inge4pres <[email protected]> * zig fmt and delete older folders Signed-off-by: inge4pres <[email protected]> --------- Signed-off-by: inge4pres <[email protected]> Co-authored-by: inge4pres <[email protected]>
1 parent d14a1d9 commit 463406b

File tree

136 files changed

+20564
-6811
lines changed

Some content is hidden

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

136 files changed

+20564
-6811
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
.DS_Store
12
.zig-cache/
23
zig-out/
34
*.o
5+
6+
semantic-conventions/

build.zig

Lines changed: 3 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,31 @@
11
const std = @import("std");
22

3-
// Although this function looks imperative, note that its job is to
4-
// declaratively construct a build graph that will be executed by an external
5-
// runner.
63
pub fn build(b: *std.Build) !void {
74
const target = b.standardTargetOptions(.{});
85
const optimize = b.standardOptimizeOption(.{});
96

10-
const test_filter = b.option([]const u8, "test-filter", "filter for tests") orelse &.{};
11-
12-
// This creates a "module", which represents a collection of source files alongside
13-
// some compilation options, such as optimization mode and linked system libraries.
14-
// Every executable or library we compile will be based on one or more modules.
157
const lib_mod = b.addModule("opentelemetry-semconv", .{
16-
// `root_source_file` is the Zig "entry point" of the module. If a module
17-
// only contains e.g. external object files, you can make this `null`.
18-
// In this case the main source file is merely a path, however, in more
19-
// complicated build scripts, this could be a generated file.
20-
.root_source_file = b.path("src/root.zig"),
8+
.root_source_file = b.path("src/lib.zig"),
219
.target = target,
2210
.optimize = optimize,
2311
});
2412

25-
// Now, we will create a static library based on the module we created above.
26-
// This creates a `std.Build.Step.Compile`, which is the build step responsible
27-
// for actually invoking the compiler.
2813
const lib = b.addLibrary(.{
2914
.linkage = .static,
30-
.name = "opentelemetry_semconv",
15+
.name = "opentelemetry-semconv",
3116
.root_module = lib_mod,
3217
});
3318

34-
// This declares intent for the library to be installed into the standard
35-
// location when the user invokes the "install" step (the default step when
36-
// running `zig build`).
3719
b.installArtifact(lib);
3820

39-
// Creates a step for unit testing. This only builds the test executable
40-
// but does not run it.
4121
const lib_unit_tests = b.addTest(.{
4222
.root_module = lib_mod,
43-
.filters = &.{test_filter},
4423
});
4524

4625
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
47-
48-
// Similar to creating the run step earlier, this exposes a `test` step to
49-
// the `zig build --help` menu, providing a way for the user to request
50-
// running the unit tests.
51-
const test_step = b.step("test", "Run unit tests for generated code and tooling");
26+
const test_step = b.step("test", "Run library tests");
5227
test_step.dependOn(&run_lib_unit_tests.step);
5328

54-
// Automation tool executable
55-
const automation_exe = b.addExecutable(.{
56-
.name = "semconv-generator",
57-
.root_source_file = b.path("tools/generator/main.zig"),
58-
.target = target,
59-
.optimize = optimize,
60-
});
61-
62-
// Add yaml dependency to the automation tool
63-
const yaml_dep = b.dependency("zig_yaml", .{
64-
.target = target,
65-
.optimize = optimize,
66-
});
67-
automation_exe.root_module.addImport("yaml", yaml_dep.module("yaml"));
68-
69-
b.installArtifact(automation_exe);
70-
71-
// Run command
72-
const run_cmd = b.addRunArtifact(automation_exe);
73-
run_cmd.step.dependOn(b.getInstallStep());
74-
75-
if (b.args) |args| {
76-
run_cmd.addArgs(args);
77-
}
78-
79-
const run_step = b.step("generate", "Run the automation tool to generate semantic convention files");
80-
run_step.dependOn(&run_cmd.step);
81-
82-
// Tests
83-
const tool_unit_tests = b.addTest(.{
84-
.root_source_file = b.path("tools/generator/test.zig"),
85-
.target = target,
86-
.optimize = .Debug,
87-
.filters = &.{test_filter},
88-
});
89-
90-
// Add yaml dependency to tests too
91-
tool_unit_tests.root_module.addImport("yaml", yaml_dep.module("yaml"));
92-
93-
const run_unit_tests = b.addRunArtifact(tool_unit_tests);
94-
test_step.dependOn(&run_unit_tests.step);
95-
9629
// Examples step
9730
const examples_step = b.step("examples", "Build and run all example executables");
9831

build.zig.zon

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,16 @@
11
.{
2-
// This is the default name used by packages depending on this one. For
3-
// example, when a user runs `zig fetch --save <url>`, this field is used
4-
// as the key in the `dependencies` table. Although the user can choose a
5-
// different name, most users will stick with this provided value.
6-
//
7-
// It is redundant to include "zig" in this name because it is already
8-
// within the Zig package namespace.
92
.name = .opentelemetry_semconv,
10-
11-
// This is a [Semantic Version](https://semver.org/).
12-
// In a future version of Zig it will be used for package deduplication.
13-
.version = "0.0.0",
14-
15-
// Together with name, this represents a globally unique package
16-
// identifier. This field is generated by the Zig toolchain when the
17-
// package is first created, and then *never changes*. This allows
18-
// unambiguous detection of one package being an updated version of
19-
// another.
20-
//
21-
// When forking a Zig project, this id should be regenerated (delete the
22-
// field and run `zig build`) if the upstream project is still maintained.
23-
// Otherwise, the fork is *hostile*, attempting to take control over the
24-
// original project's identity. Thus it is recommended to leave the comment
25-
// on the following line intact, so that it shows up in code reviews that
26-
// modify the field.
27-
.fingerprint = 0x87fe0fe4bb512c4, // Changing this has security and trust implications.
28-
29-
// Tracks the earliest Zig version that the package considers to be a
30-
// supported use case.
31-
.minimum_zig_version = "0.14.1",
32-
33-
// This field is optional.
34-
// Each dependency must either provide a `url` and `hash`, or a `path`.
35-
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
36-
// Once all dependencies are fetched, `zig build` no longer requires
37-
// internet connectivity.
38-
.dependencies = .{
39-
.zig_yaml = .{
40-
.url = "https://github.com/kubkon/zig-yaml/archive/refs/tags/0.1.1.tar.gz",
41-
.hash = "zig_yaml-0.1.0-C1161miEAgBCwL3YAEQZwV_4GyaaT2Xqj9nKB6hNe_TL",
42-
},
43-
},
3+
.version = "0.1.0",
4+
.minimum_zig_version = "0.15.1",
5+
.dependencies = .{},
446
.paths = .{
457
"build.zig",
468
"build.zig.zon",
479
"src",
10+
"examples",
11+
"scripts",
4812
"LICENSE",
4913
"README.md",
5014
},
15+
.fingerprint = 0x87fe0fed413e058,
5116
}

examples/basic_usage.zig

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,19 @@ pub fn main() !void {
1111
std.debug.print("--------------------------\n", .{});
1212

1313
// Access HTTP request method attribute
14-
const http_method = semconv.http.Registry.http_request_method;
14+
const http_method = semconv.attribute.http_request_method;
1515
std.debug.print("HTTP method attribute: {s}\n", .{http_method.base.name});
1616
std.debug.print(" Brief: {s}\n", .{http_method.base.brief});
1717
std.debug.print(" Stability: {s}\n", .{@tagName(http_method.base.stability)});
1818

1919
// Show available HTTP method values
2020
std.debug.print(" Available method values:\n", .{});
21-
std.debug.print(" GET: {s}\n", .{semconv.http.Registry.requestMethodValue.get.toString()});
22-
std.debug.print(" POST: {s}\n", .{semconv.http.Registry.requestMethodValue.post.toString()});
23-
std.debug.print(" PUT: {s}\n", .{semconv.http.Registry.requestMethodValue.put.toString()});
21+
std.debug.print(" GET: {s}\n", .{semconv.attribute.http_request_methodValue.get.toString()});
22+
std.debug.print(" POST: {s}\n", .{semconv.attribute.http_request_methodValue.post.toString()});
23+
std.debug.print(" PUT: {s}\n", .{semconv.attribute.http_request_methodValue.put.toString()});
2424

2525
// Access HTTP status code attribute
26-
const http_status = semconv.http.Registry.http_response_status_code;
26+
const http_status = semconv.attribute.http_response_status_code;
2727
std.debug.print("\nHTTP status code attribute: {s}\n", .{http_status.name});
2828
std.debug.print(" Brief: {s}\n", .{http_status.brief});
2929
std.debug.print(" Stability: {s}\n", .{@tagName(http_status.stability)});
@@ -35,16 +35,16 @@ pub fn main() !void {
3535
std.debug.print("-------------------------\n", .{});
3636

3737
// Access JVM memory type attribute
38-
const jvm_memory_type = semconv.jvm.Registry.jvm_memory_type;
38+
const jvm_memory_type = semconv.attribute.jvm_memory_type;
3939
std.debug.print("JVM memory type attribute: {s}\n", .{jvm_memory_type.base.name});
4040
std.debug.print(" Brief: {s}\n", .{jvm_memory_type.base.brief});
4141
std.debug.print(" Stability: {s}\n", .{@tagName(jvm_memory_type.base.stability)});
4242
std.debug.print(" Available values:\n", .{});
43-
std.debug.print(" Heap: {s}\n", .{semconv.jvm.Registry.memoryTypeValue.heap.toString()});
44-
std.debug.print(" Non-heap: {s}\n", .{semconv.jvm.Registry.memoryTypeValue.non_heap.toString()});
43+
std.debug.print(" Heap: {s}\n", .{semconv.attribute.jvm_memory_typeValue.heap.toString()});
44+
std.debug.print(" Non-heap: {s}\n", .{semconv.attribute.jvm_memory_typeValue.non_heap.toString()});
4545

4646
// Access JVM GC name attribute
47-
const jvm_gc_name = semconv.jvm.Registry.jvm_gc_name;
47+
const jvm_gc_name = semconv.attribute.jvm_gc_name;
4848
std.debug.print("\nJVM GC name attribute: {s}\n", .{jvm_gc_name.name});
4949
std.debug.print(" Brief: {s}\n", .{jvm_gc_name.brief});
5050
std.debug.print(" Stability: {s}\n", .{@tagName(jvm_gc_name.stability)});
@@ -66,4 +66,4 @@ pub fn main() !void {
6666
std.debug.print(" - Stability level\n", .{});
6767
std.debug.print(" - Requirement level\n", .{});
6868
std.debug.print(" - Well-known values (for enum attributes)\n", .{});
69-
}
69+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
PROJECT_DIR="${SCRIPT_DIR}/.."
6+
7+
# freeze the spec version and generator version to make generation reproducible
8+
SPEC_VERSION=1.36.0
9+
WEAVER_VERSION=v0.16.1
10+
11+
cd "$PROJECT_DIR"
12+
13+
rm -rf semantic-conventions || true
14+
mkdir semantic-conventions
15+
cd semantic-conventions
16+
17+
git init
18+
git remote add origin https://github.com/open-telemetry/semantic-conventions.git
19+
git fetch origin "v$SPEC_VERSION"
20+
git reset --hard FETCH_HEAD
21+
cd "$PROJECT_DIR"
22+
23+
SED=(sed -i)
24+
if [[ "$(uname)" = "Darwin" ]]; then
25+
SED=(sed -i "")
26+
fi
27+
28+
# Keep `SCHEMA_URL` key in sync with spec version
29+
"${SED[@]}" "s/\(opentelemetry.io\/schemas\/\)[^\"]*\"/\1$SPEC_VERSION\"/" scripts/templates/registry/zig/weaver.yaml
30+
31+
docker run --rm \
32+
--mount type=bind,source=$PROJECT_DIR/semantic-conventions/model,target=/home/weaver/source,readonly \
33+
--mount type=bind,source=$PROJECT_DIR/scripts/templates,target=/home/weaver/templates,readonly \
34+
--mount type=bind,source=$PROJECT_DIR/src,target=/home/weaver/target \
35+
otel/weaver:$WEAVER_VERSION \
36+
registry generate \
37+
--registry=/home/weaver/source \
38+
--templates=/home/weaver/templates \
39+
zig \
40+
/home/weaver/target/
41+
42+
# handle doc generation failures - remove trailing [2] from doc comments
43+
"${SED[@]}" 's/\[2\]\.$//' src/attribute.zig
44+
45+
# handle escaping ranges like [0,n] / [0.0, ...] in descriptions/notes which will cause broken links
46+
# unescape any mistakenly escaped ranges which actually contained a link
47+
expression='
48+
s/\[([a-zA-Z0-9\.\s]+,[a-zA-Z0-9\.\s]+)\]/\\[\1\\]/g
49+
s/\\\[([^\]]+)\]\(([^)]+)\)/[\1](\2)/g
50+
'
51+
52+
"${SED[@]}" -E "${expression}" src/metric.zig
53+
"${SED[@]}" -E "${expression}" src/attribute.zig
54+
55+
# Fix unclosed HTML tag warnings for <key> in doc comments.
56+
# We replace <key> with Markdown code formatting `key` to prevent the error.
57+
"${SED[@]}" 's/<key>/`key`/g' src/attribute.zig
58+
59+
zig fmt .
60+
61+
echo "Zig semantic conventions generated successfully!"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Generated from OpenTelemetry semantic conventions specification v{{ params.schema_url | replace('https://opentelemetry.io/schemas/', '') }}
2+
//! This file contains semantic convention attribute definitions.
3+
4+
const std = @import("std");
5+
const types = @import("types.zig");
6+
7+
{% for root_ns in ctx -%}
8+
{%- for attr in root_ns.attributes | rejectattr("name", "in", params.excluded_attributes) %}
9+
{%- set attr_name = attr.name | replace('.', '_') | replace('-', '_') %}
10+
11+
{%- if attr.type is mapping and attr.type.members is defined %}
12+
pub const {{ attr_name }}Value = enum {
13+
{%- for member in attr.type.members %}
14+
{%- set member_id = member.id | replace('-', '_') | replace('.', '_') %}
15+
{%- if member_id in ['type', 'align', 'async', 'await', 'break', 'const', 'continue', 'defer', 'else', 'enum', 'error', 'export', 'extern', 'fn', 'for', 'if', 'inline', 'noalias', 'null', 'or', 'orelse', 'packed', 'pub', 'resume', 'return', 'struct', 'suspend', 'switch', 'test', 'threadlocal', 'try', 'union', 'unreachable', 'usingnamespace', 'var', 'volatile', 'while'] or '.' in member.id %}
16+
@"{{ member_id }}",
17+
{%- else %}
18+
{{ member_id }},
19+
{%- endif %}
20+
{%- endfor %}
21+
22+
pub fn toString(self: @This()) []const u8 {
23+
return switch (self) {
24+
{%- for member in attr.type.members %}
25+
{%- set member_id = member.id | replace('-', '_') | replace('.', '_') %}
26+
{%- if member_id in ['type', 'align', 'async', 'await', 'break', 'const', 'continue', 'defer', 'else', 'enum', 'error', 'export', 'extern', 'fn', 'for', 'if', 'inline', 'noalias', 'null', 'or', 'orelse', 'packed', 'pub', 'resume', 'return', 'struct', 'suspend', 'switch', 'test', 'threadlocal', 'try', 'union', 'unreachable', 'usingnamespace', 'var', 'volatile', 'while'] or '.' in member.id %}
27+
.@"{{ member_id }}" => "{{ member.value }}",
28+
{%- else %}
29+
.{{ member_id }} => "{{ member.value }}",
30+
{%- endif %}
31+
{%- endfor %}
32+
};
33+
}
34+
};
35+
36+
/// {{ attr.brief }}
37+
pub const {{ attr_name }} = types.EnumAttribute({{ attr_name }}Value){
38+
.base = types.StringAttribute{
39+
.name = "{{ attr.name }}",
40+
.brief = "{{ attr.brief | replace('"', '\\"') | replace('\n', ' ') | trim }}",
41+
{%- if attr.note %}
42+
.note = {{ '"' + attr.note | replace('\\', '\\\\') | replace('<', '[') | replace('>', ']') | replace('"', '\\"') | replace('\n', ' ') | trim + '"' }},
43+
{%- endif %}
44+
.stability = {{ '.stable' if attr.stability == 'stable' else '.development' }},
45+
.requirement_level = {{ '.required' if attr.requirement_level == 'required' else '.recommended' if attr.requirement_level == 'recommended' else '.opt_in' if attr.requirement_level == 'opt_in' else '.conditionally_required' if attr.requirement_level == 'conditionally_required' else '.recommended' }},
46+
},
47+
.well_known_values = {{ attr_name }}Value.{% set first_member = attr.type.members[0].id | replace('-', '_') | replace('.', '_') %}{% if first_member in ['type', 'align', 'async', 'await', 'break', 'const', 'continue', 'defer', 'else', 'enum', 'error', 'export', 'extern', 'fn', 'for', 'if', 'inline', 'noalias', 'null', 'or', 'orelse', 'packed', 'pub', 'resume', 'return', 'struct', 'suspend', 'switch', 'test', 'threadlocal', 'try', 'union', 'unreachable', 'usingnamespace', 'var', 'volatile', 'while'] or '.' in attr.type.members[0].id %}@"{{ first_member }}"{% else %}{{ first_member }}{% endif %},
48+
};
49+
50+
{%- else %}
51+
52+
/// {{ attr.brief }}
53+
pub const {{ attr_name }} = types.StringAttribute{
54+
.name = "{{ attr.name }}",
55+
.brief = "{{ attr.brief | replace('"', '\\"') | replace('\n', ' ') | trim }}",
56+
{%- if attr.note %}
57+
.note = {{ '"' + attr.note | replace('\\', '\\\\') | replace('<', '[') | replace('>', ']') | replace('"', '\\"') | replace('\n', ' ') | trim + '"' }},
58+
{%- endif %}
59+
.stability = {{ '.stable' if attr.stability == 'stable' else '.development' }},
60+
.requirement_level = {{ '.required' if attr.requirement_level == 'required' else '.recommended' if attr.requirement_level == 'recommended' else '.opt_in' if attr.requirement_level == 'opt_in' else '.conditionally_required' if attr.requirement_level == 'conditionally_required' else '.recommended' }},
61+
};
62+
63+
{%- endif %}
64+
65+
{%- endfor %}
66+
{%- endfor %}
67+
68+
test "semantic attributes" {
69+
std.testing.refAllDecls(@This());
70+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//! OpenTelemetry semantic conventions are agreed standardized naming patterns
2+
//! for OpenTelemetry things. This module aims to be the centralized place to
3+
//! interact with these conventions.
4+
//!
5+
//! Generated from OpenTelemetry semantic conventions specification.
6+
7+
pub const attribute = @import("attribute.zig");
8+
pub const metric = @import("metric.zig");
9+
pub const resource = @import("resource.zig");
10+
pub const trace = @import("trace.zig");
11+
12+
/// The schema URL that matches the version of the semantic conventions that
13+
/// this module defines.
14+
pub const SCHEMA_URL: []const u8 = "{{ params.schema_url }}";
15+
16+
test "semantic conventions" {
17+
@import("std").testing.refAllDecls(@This());
18+
}

0 commit comments

Comments
 (0)