Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.DS_Store
.zig-cache/
zig-out/
*.o

semantic-conventions/
73 changes: 3 additions & 70 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,98 +1,31 @@
const std = @import("std");

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

const test_filter = b.option([]const u8, "test-filter", "filter for tests") orelse &.{};

// This creates a "module", which represents a collection of source files alongside
// some compilation options, such as optimization mode and linked system libraries.
// Every executable or library we compile will be based on one or more modules.
const lib_mod = b.addModule("opentelemetry-semconv", .{
// `root_source_file` is the Zig "entry point" of the module. If a module
// only contains e.g. external object files, you can make this `null`.
// In this case the main source file is merely a path, however, in more
// complicated build scripts, this could be a generated file.
.root_source_file = b.path("src/root.zig"),
.root_source_file = b.path("src/lib.zig"),
.target = target,
.optimize = optimize,
});

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

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

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

const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests for generated code and tooling");
const test_step = b.step("test", "Run library tests");
test_step.dependOn(&run_lib_unit_tests.step);

// Automation tool executable
const automation_exe = b.addExecutable(.{
.name = "semconv-generator",
.root_source_file = b.path("tools/generator/main.zig"),
.target = target,
.optimize = optimize,
});

// Add yaml dependency to the automation tool
const yaml_dep = b.dependency("zig_yaml", .{
.target = target,
.optimize = optimize,
});
automation_exe.root_module.addImport("yaml", yaml_dep.module("yaml"));

b.installArtifact(automation_exe);

// Run command
const run_cmd = b.addRunArtifact(automation_exe);
run_cmd.step.dependOn(b.getInstallStep());

if (b.args) |args| {
run_cmd.addArgs(args);
}

const run_step = b.step("generate", "Run the automation tool to generate semantic convention files");
run_step.dependOn(&run_cmd.step);

// Tests
const tool_unit_tests = b.addTest(.{
.root_source_file = b.path("tools/generator/test.zig"),
.target = target,
.optimize = .Debug,
.filters = &.{test_filter},
});

// Add yaml dependency to tests too
tool_unit_tests.root_module.addImport("yaml", yaml_dep.module("yaml"));

const run_unit_tests = b.addRunArtifact(tool_unit_tests);
test_step.dependOn(&run_unit_tests.step);

// Examples step
const examples_step = b.step("examples", "Build and run all example executables");

Expand Down
47 changes: 6 additions & 41 deletions build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,51 +1,16 @@
.{
// This is the default name used by packages depending on this one. For
// example, when a user runs `zig fetch --save <url>`, this field is used
// as the key in the `dependencies` table. Although the user can choose a
// different name, most users will stick with this provided value.
//
// It is redundant to include "zig" in this name because it is already
// within the Zig package namespace.
.name = .opentelemetry_semconv,

// This is a [Semantic Version](https://semver.org/).
// In a future version of Zig it will be used for package deduplication.
.version = "0.0.0",

// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
// package is first created, and then *never changes*. This allows
// unambiguous detection of one package being an updated version of
// another.
//
// When forking a Zig project, this id should be regenerated (delete the
// field and run `zig build`) if the upstream project is still maintained.
// Otherwise, the fork is *hostile*, attempting to take control over the
// original project's identity. Thus it is recommended to leave the comment
// on the following line intact, so that it shows up in code reviews that
// modify the field.
.fingerprint = 0x87fe0fe4bb512c4, // Changing this has security and trust implications.

// Tracks the earliest Zig version that the package considers to be a
// supported use case.
.minimum_zig_version = "0.14.1",

// This field is optional.
// Each dependency must either provide a `url` and `hash`, or a `path`.
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
// Once all dependencies are fetched, `zig build` no longer requires
// internet connectivity.
.dependencies = .{
.zig_yaml = .{
.url = "https://github.com/kubkon/zig-yaml/archive/refs/tags/0.1.1.tar.gz",
.hash = "zig_yaml-0.1.0-C1161miEAgBCwL3YAEQZwV_4GyaaT2Xqj9nKB6hNe_TL",
},
},
.version = "0.1.0",
.minimum_zig_version = "0.15.1",
.dependencies = .{},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
"examples",
"scripts",
"LICENSE",
"README.md",
},
.fingerprint = 0x87fe0fed413e058,
}
20 changes: 10 additions & 10 deletions examples/basic_usage.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ pub fn main() !void {
std.debug.print("--------------------------\n", .{});

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

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

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

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

// Access JVM GC name attribute
const jvm_gc_name = semconv.jvm.Registry.jvm_gc_name;
const jvm_gc_name = semconv.attribute.jvm_gc_name;
std.debug.print("\nJVM GC name attribute: {s}\n", .{jvm_gc_name.name});
std.debug.print(" Brief: {s}\n", .{jvm_gc_name.brief});
std.debug.print(" Stability: {s}\n", .{@tagName(jvm_gc_name.stability)});
Expand All @@ -66,4 +66,4 @@ pub fn main() !void {
std.debug.print(" - Stability level\n", .{});
std.debug.print(" - Requirement level\n", .{});
std.debug.print(" - Well-known values (for enum attributes)\n", .{});
}
}
61 changes: 61 additions & 0 deletions scripts/generate-consts-from-spec.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/bash
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="${SCRIPT_DIR}/.."

# freeze the spec version and generator version to make generation reproducible
SPEC_VERSION=1.36.0
WEAVER_VERSION=v0.16.1

cd "$PROJECT_DIR"

rm -rf semantic-conventions || true
mkdir semantic-conventions
cd semantic-conventions

git init
git remote add origin https://github.com/open-telemetry/semantic-conventions.git
git fetch origin "v$SPEC_VERSION"
git reset --hard FETCH_HEAD
cd "$PROJECT_DIR"

SED=(sed -i)
if [[ "$(uname)" = "Darwin" ]]; then
SED=(sed -i "")
fi

# Keep `SCHEMA_URL` key in sync with spec version
"${SED[@]}" "s/\(opentelemetry.io\/schemas\/\)[^\"]*\"/\1$SPEC_VERSION\"/" scripts/templates/registry/zig/weaver.yaml

docker run --rm \
--mount type=bind,source=$PROJECT_DIR/semantic-conventions/model,target=/home/weaver/source,readonly \
--mount type=bind,source=$PROJECT_DIR/scripts/templates,target=/home/weaver/templates,readonly \
--mount type=bind,source=$PROJECT_DIR/src,target=/home/weaver/target \
otel/weaver:$WEAVER_VERSION \
registry generate \
--registry=/home/weaver/source \
--templates=/home/weaver/templates \
zig \
/home/weaver/target/

# handle doc generation failures - remove trailing [2] from doc comments
"${SED[@]}" 's/\[2\]\.$//' src/attribute.zig

# handle escaping ranges like [0,n] / [0.0, ...] in descriptions/notes which will cause broken links
# unescape any mistakenly escaped ranges which actually contained a link
expression='
s/\[([a-zA-Z0-9\.\s]+,[a-zA-Z0-9\.\s]+)\]/\\[\1\\]/g
s/\\\[([^\]]+)\]\(([^)]+)\)/[\1](\2)/g
'

"${SED[@]}" -E "${expression}" src/metric.zig
"${SED[@]}" -E "${expression}" src/attribute.zig

# Fix unclosed HTML tag warnings for <key> in doc comments.
# We replace <key> with Markdown code formatting `key` to prevent the error.
"${SED[@]}" 's/<key>/`key`/g' src/attribute.zig

zig fmt .

echo "Zig semantic conventions generated successfully!"
70 changes: 70 additions & 0 deletions scripts/templates/registry/zig/attribute.zig.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//! Generated from OpenTelemetry semantic conventions specification v{{ params.schema_url | replace('https://opentelemetry.io/schemas/', '') }}
//! This file contains semantic convention attribute definitions.

const std = @import("std");
const types = @import("types.zig");

{% for root_ns in ctx -%}
{%- for attr in root_ns.attributes | rejectattr("name", "in", params.excluded_attributes) %}
{%- set attr_name = attr.name | replace('.', '_') | replace('-', '_') %}

{%- if attr.type is mapping and attr.type.members is defined %}
pub const {{ attr_name }}Value = enum {
{%- for member in attr.type.members %}
{%- set member_id = member.id | replace('-', '_') | replace('.', '_') %}
{%- 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 %}
@"{{ member_id }}",
{%- else %}
{{ member_id }},
{%- endif %}
{%- endfor %}

pub fn toString(self: @This()) []const u8 {
return switch (self) {
{%- for member in attr.type.members %}
{%- set member_id = member.id | replace('-', '_') | replace('.', '_') %}
{%- 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 %}
.@"{{ member_id }}" => "{{ member.value }}",
{%- else %}
.{{ member_id }} => "{{ member.value }}",
{%- endif %}
{%- endfor %}
};
}
};

/// {{ attr.brief }}
pub const {{ attr_name }} = types.EnumAttribute({{ attr_name }}Value){
.base = types.StringAttribute{
.name = "{{ attr.name }}",
.brief = "{{ attr.brief | replace('"', '\\"') | replace('\n', ' ') | trim }}",
{%- if attr.note %}
.note = {{ '"' + attr.note | replace('\\', '\\\\') | replace('<', '[') | replace('>', ']') | replace('"', '\\"') | replace('\n', ' ') | trim + '"' }},
{%- endif %}
.stability = {{ '.stable' if attr.stability == 'stable' else '.development' }},
.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' }},
},
.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 %},
};

{%- else %}

/// {{ attr.brief }}
pub const {{ attr_name }} = types.StringAttribute{
.name = "{{ attr.name }}",
.brief = "{{ attr.brief | replace('"', '\\"') | replace('\n', ' ') | trim }}",
{%- if attr.note %}
.note = {{ '"' + attr.note | replace('\\', '\\\\') | replace('<', '[') | replace('>', ']') | replace('"', '\\"') | replace('\n', ' ') | trim + '"' }},
{%- endif %}
.stability = {{ '.stable' if attr.stability == 'stable' else '.development' }},
.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' }},
};

{%- endif %}

{%- endfor %}
{%- endfor %}

test "semantic attributes" {
std.testing.refAllDecls(@This());
}
18 changes: 18 additions & 0 deletions scripts/templates/registry/zig/lib.zig.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//! OpenTelemetry semantic conventions are agreed standardized naming patterns
//! for OpenTelemetry things. This module aims to be the centralized place to
//! interact with these conventions.
//!
//! Generated from OpenTelemetry semantic conventions specification.

pub const attribute = @import("attribute.zig");
pub const metric = @import("metric.zig");
pub const resource = @import("resource.zig");
pub const trace = @import("trace.zig");

/// The schema URL that matches the version of the semantic conventions that
/// this module defines.
pub const SCHEMA_URL: []const u8 = "{{ params.schema_url }}";

test "semantic conventions" {
@import("std").testing.refAllDecls(@This());
}
Loading