Claude Skill

otel-semantic-conventions

OpenTelemetry semantic convention lookup and naming guidance. Use when selecting released semantic convention groups, attributes, or span naming rules, when checking semantic convention compliance, or checking which semantic-conventions version a language SDK's semconv package ex

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download ollygarden-opentelemetry-agent-skills-skills_otel-semantic-conventions-9671795.zip · 10 KB
Part of ollygarden/opentelemetry-agent-skills — 19 skills

Install

skills CLI npx skills add https://github.com/ollygarden/opentelemetry-agent-skills/tree/main/skills/otel-semantic-conventions
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install ollygarden-opentelemetry-agent-skills@llmmart
Git git clone https://github.com/ollygarden/opentelemetry-agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole ollygarden/opentelemetry-agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

OpenTelemetry Semantic Conventions

Use this skill when you need released semantic convention guidance for naming, attributes, or compliance checks.

Workflow

  1. Start with released semantic conventions, not model memory.
  • do not load the full semantic convention spec into context
  • use the bundled lookup script to query only the needed released group or attribute
  • for local verification against a checked-out upstream repo, set OTEL_SEMCONV_REPO=/path/to/semantic-conventions
  1. Choose the closest released group before inventing custom keys.
  • identify the boundary type such as http, db, messaging, rpc, or network
  • pick one primary group first, then add related groups only when they add needed context
  • for gen_ai.*, OpenAI, or MCP conventions, use the dedicated OpenTelemetry GenAI semantic conventions repository; the core repository keeps only deprecated GenAI-era stubs after v1.42.0
  • see references/semconv-selection.md
  • to target a version a language's semconv package actually exposes (works offline, dated), see references/sdk-semconv-packages.md
  1. Query only the released guidance you need.
  • list groups: ./scripts/query-otel-semantic-conventions.sh --groups
  • inspect one group: ./scripts/query-otel-semantic-conventions.sh http
  • inspect one kind: ./scripts/query-otel-semantic-conventions.sh http spans
  • inspect one exact attribute or entry: ./scripts/query-otel-semantic-conventions.sh http http.request.method
  • see references/otel-semantic-conventions.md
  • after changing the parser, run OTEL_SEMCONV_REPO=/path/to/semantic-conventions bash ./scripts/test-query-otel-semantic-conventions.sh
  1. Apply the released naming and attribute rules directly.
  • use required and recommended attributes before optional ones
  • derive semconv-governed span names directly from the released naming rule
  • do not prepend or append protocol labels, hostnames, product names, business hints, or other custom prose to semconv-governed span names
  • if the released naming rule does not provide a low-cardinality target, use the simpler fallback allowed by that convention
  • if no released key exists, use a stable custom namespace and keep values bounded
  1. Return the result with exact source context.
  • include the group name, released version, and source URL from the script output
  • call out any concrete compatibility limitation if the implementation cannot fully match the released guidance
Files (opentelemetry-agent-skills)
  • evals
    • evals.json 1.2 KB
      {
        "skill": "otel-semantic-conventions",
        "cases": [
          {
            "id": "offline-sdk-semconv-package-versions",
            "prompt": "No web lookups please, just your knowledge and skills. We run services in Go (go.opentelemetry.io/otel v1.47.0), Java, JavaScript, and Python, and want every service to emit the same semantic-conventions version. Which semconv version does each language's semconv package expose, and which version should we standardize on?",
            "files": [],
            "expectations": [
              "Says the newest Go package in otel v1.47.0 is go.opentelemetry.io/otel/semconv/v1.43.0 and that there is no v1.44.0 package.",
              "Says @opentelemetry/semantic-conventions 1.43.0 exposes semconv 1.43.0.",
              "Says Java opentelemetry-semconv 1.44.0 and Python opentelemetry-semantic-conventions 0.66b1 expose semconv 1.44.0.",
              "Recommends 1.43.0, the lowest version all four packages expose, as the shared version.",
              "Frames the versions as verified on the recorded date rather than as currently latest, and suggests re-checking the package registries before pinning.",
              "Does not perform any web search, web fetch, or network command."
            ]
          }
        ]
      }
      
  • references
    • otel-semantic-conventions.md 2 KB
      ---
      title: OpenTelemetry Semantic Conventions
      read_when: Choosing released semantic convention attributes for instrumentation
      ---
      
      Do not load the full semantic convention spec into context. Query only the needed released group with `./scripts/query-otel-semantic-conventions.sh`.
      
      Use:
      - list groups: `./scripts/query-otel-semantic-conventions.sh --groups`
      - group lookup: `./scripts/query-otel-semantic-conventions.sh http`
      - kind lookup: `./scripts/query-otel-semantic-conventions.sh http spans`
      - exact entry: `./scripts/query-otel-semantic-conventions.sh http http.request.method`
      - local checkout mode: `OTEL_SEMCONV_REPO=/path/to/semantic-conventions ./scripts/query-otel-semantic-conventions.sh http`
      
      Rules:
      - use `--groups` first if you do not already know the right group
      - start with one group
      - use the one-argument form first to discover the current released attribute ids and available kinds
      - pass a listed kind as the second argument to list the released entries of that kind
      - pass an exact entry id as the second argument to return its upstream definition block
      - the script resolves the latest released semantic conventions version and reads released YAML model files under `model/<group>/` from that tag
      - definition/2 registry attributes, signal definitions, and refinements introduced in v1.44 are parsed by their schema entry keys; messaging provider files are included under their signal kind even when their filenames do not contain `spans`, `metrics`, or `events`
      - deprecated model files and directories are excluded from lookup results
      - the core semantic-conventions repository added `model/manifest.yaml` in v1.43.0; use it as release metadata, not as a convention group
      - `gen_ai.*`, OpenAI, and MCP conventions moved to the dedicated OpenTelemetry GenAI semantic conventions repository in v1.42.0; do not treat deprecated stubs under the core repository as current guidance
      
      Common groups:
      `http`, `db`, `messaging`, `rpc`, `network`, `url`, `server`, `error`, `user-agent`, `service`, `cloud`, `k8s`, `process`, `otel`, `log`, `exceptions`
      
    • sdk-semconv-packages.md 2.3 KB
      # SDK semantic-convention packages
      
      Which semantic-conventions release each language's semconv package exposes. Packages trail the
      spec, so pick the version your SDK can emit, not the newest spec release.
      
      ## Verified 2026-10-15
      
      Offline, cite this date, never call a value currently latest, and confirm it against the evidence before deploying. Online, re-check the package registry first.
      
      Latest spec release: the `otel-sdk-versions` index if it lists semantic-conventions, else the GitHub releases.
      
      | Language | Package and version | Newest semconv exposed |
      |---|---|---|
      | Go | `go.opentelemetry.io/otel` v1.47.0, packages `go.opentelemetry.io/otel/semconv/vX` | `v1.43.0` (added in v1.45.0; no `v1.44.0` package) |
      | Java | `io.opentelemetry.semconv:opentelemetry-semconv` 1.44.0 (`-incubating` 1.44.0-alpha) | 1.44.0 |
      | JavaScript | `@opentelemetry/semantic-conventions` 1.43.0 | 1.43.0 |
      | Python | `opentelemetry-semantic-conventions` 0.66b1 | 1.44.0 |
      | Ruby | `opentelemetry-semantic_conventions` 1.44.0 | 1.44.0 |
      | PHP | `open-telemetry/sem-conv` 1.44.0 | 1.44.0 |
      | C++ | `opentelemetry-cpp` v1.29.0 headers | 1.44.0 |
      | Rust | `opentelemetry-semantic-conventions` 0.33.1 | 1.42.0 |
      | .NET | `OpenTelemetry.SemanticConventions`: only the unlisted 1.0.0-rc9.9 on NuGet; unreleased dotnet-contrib source tracks 1.44.0 | - (no current release) |
      
      Evidence, at each release tag or in the published artifact:
      
      - Go: `semconv/` directories at tag `v1.47.0`; CHANGELOG `1.45.0` adds `semconv/v1.43.0`.
      - Java: the jar's `SchemaUrls` highest constant is `schemas/1.44.0`.
      - JavaScript: package `VERSION = '1.43.0'`; its CHANGELOG `1.43.0` cites Semantic Conventions v1.43.0.
      - Python: the wheel's `opentelemetry/semconv/schemas.py` highest member is `V1_44_0`.
      - Ruby: the gem's `semantic_conventions/version.rb` `SPEC_VERSION = '1.44.0'`.
      - PHP: `Version.php` at tag `1.44.0` defines `VERSION_1_44_0`.
      - C++: `api/include/opentelemetry/semconv/schema_url.h` at `v1.29.0`.
      - Rust: the crate's `SCHEMA_URL` is `schemas/1.42.0`.
      - .NET: NuGet registration (`listed: false`); dotnet-contrib `src/OpenTelemetry.SemanticConventions/CHANGELOG.md` Unreleased (#5121).
      
      For services in several languages, the shared version is the lowest "Newest semconv exposed" value
      among the languages in use (1.43.0 for Go plus JavaScript, 1.42.0 with Rust). .NET has no value, so
      it cannot join a shared version.
      
    • semconv-selection.md 2.2 KB
      # Semantic Convention Selection
      
      Pick the closest released semantic convention group before inventing custom keys.
      
      ## Common Starting Groups
      
      - `http` for HTTP client and server operations
      - `db` for database operations
      - `messaging` for queues, streams, and pub-sub
      - `rpc` for remote procedure calls
      - `network` for transport-level details
      - `url` for URL components
      - `server` for server endpoint metadata
      - `error` for error classification
      - `user-agent` for client user-agent details
      - `service` and `cloud` for service and cloud resource metadata
      - `k8s` and `process` for Kubernetes and process entities or metrics
      - `otel`, `log`, and `exceptions` for OpenTelemetry SDK telemetry, log records, and exception telemetry
      
      ## Selection Rules
      
      1. Start with one primary group that matches the boundary.
      2. Add related groups only when they add needed context.
      3. Prefer required and recommended attributes before optional ones.
      4. If no released key exists, use a stable custom namespace and keep values bounded.
      5. For `gen_ai.*`, OpenAI, or MCP conventions, use the dedicated OpenTelemetry GenAI semantic conventions repository; do not use deprecated GenAI-era stubs from the core repository as current guidance.
      
      ## Lookup Workflow
      
      Use the bundled script instead of loading large spec files:
      
      1. `./scripts/query-otel-semantic-conventions.sh --groups`
      2. `./scripts/query-otel-semantic-conventions.sh <group>`
      3. `./scripts/query-otel-semantic-conventions.sh <group> <kind-or-entry-id>`
      
      Use the one-argument form first. Pass a listed kind to enumerate its entries, or pass an exact entry id to return its released upstream definition.
      To verify against a local upstream checkout, set `OTEL_SEMCONV_REPO=/path/to/semantic-conventions`.
      
      ## Typical Group Pairings
      
      - HTTP server: `http`, then maybe `url`, `server`, `network`, `user-agent`, `error`
      - HTTP client: `http`, then maybe `url`, `server`, `network`, `error`
      - Database: `db`, then maybe `server`, `error`
      - Messaging: `messaging`, then maybe `network`, `server`, `error`
      - RPC: `rpc`, then maybe `server`, `network`, `error`
      
      If a domain is not listed by `./scripts/query-otel-semantic-conventions.sh --groups`, treat it as not available in the currently released semantic conventions and use a stable custom namespace.
      
  • scripts
    • query-otel-semantic-conventions.sh 17.7 KB
      #!/usr/bin/env bash
      set -euo pipefail
      
      readonly RELEASE_API_URL="https://api.github.com/repos/open-telemetry/semantic-conventions/releases/latest"
      readonly SEMCONV_REPO="${OTEL_SEMCONV_REPO:-}"
      readonly SEMCONV_TAG="${OTEL_SEMCONV_TAG:-}"
      
      blob_url() {
        local tag="$1"
        local path="$2"
        local line="$3"
        printf 'https://github.com/open-telemetry/semantic-conventions/blob/%s/%s#L%s' "$tag" "$path" "$line"
      }
      
      group_url() {
        local tag="$1"
        local group="$2"
        printf 'https://github.com/open-telemetry/semantic-conventions/tree/%s/model/%s' "$tag" "$group"
      }
      
      usage() {
        cat <<'EOF' >&2
      Usage:
        ./scripts/query-otel-semantic-conventions.sh --groups
        ./scripts/query-otel-semantic-conventions.sh <group>
        ./scripts/query-otel-semantic-conventions.sh <group> <kind>
        ./scripts/query-otel-semantic-conventions.sh <group> <id>
      
      Examples:
        ./scripts/query-otel-semantic-conventions.sh --groups
        ./scripts/query-otel-semantic-conventions.sh http
        ./scripts/query-otel-semantic-conventions.sh http spans
        ./scripts/query-otel-semantic-conventions.sh http span.http.client
        ./scripts/query-otel-semantic-conventions.sh http http.request.method
      EOF
      }
      
      require_bin() {
        command -v "$1" >/dev/null 2>&1 || {
          echo "missing dependency: $1" >&2
          exit 1
        }
      }
      
      normalize_group() {
        printf '%s' "$1" |
          tr '[:upper:]' '[:lower:]' |
          tr ' ' '-' |
          tr -d '.'
      }
      
      is_supported_kind() {
        case "$1" in
          common|entities|events|logs|metrics|registry|spans) return 0 ;;
          *) return 1 ;;
        esac
      }
      
      validate_release_tag() {
        local tag="$1"
        if [[ ! "$tag" =~ ^v[0-9]+(\.[0-9]+)*$ ]]; then
          echo "not a stable semantic conventions release tag: ${tag}" >&2
          return 1
        fi
      }
      
      fetch_latest_tag() {
        if [[ -n "$SEMCONV_TAG" ]]; then
          validate_release_tag "$SEMCONV_TAG" || return 1
          printf '%s\n' "$SEMCONV_TAG"
          return 0
        fi
      
        if [[ -n "$SEMCONV_REPO" ]]; then
          local tag
          tag="$(
            git -C "$SEMCONV_REPO" tag --list 'v[0-9]*' --sort=-version:refname |
              grep -E '^v[0-9]+(\.[0-9]+)*$' |
              sed -n '1p' ||
              true
          )"
          if [[ -z "$tag" ]]; then
            echo "failed to find a stable semantic conventions release tag in ${SEMCONV_REPO}" >&2
            return 1
          fi
          validate_release_tag "$tag" || return 1
          printf '%s\n' "$tag"
          return 0
        fi
      
        local tag
        tag="$(curl -fsSL "$RELEASE_API_URL" | jq -er '.tag_name')"
        validate_release_tag "$tag" || return 1
        printf '%s\n' "$tag"
      }
      
      fetch_model_paths() {
        local tag="$1"
      
        if [[ -n "$SEMCONV_REPO" ]]; then
          if ! git -C "$SEMCONV_REPO" ls-tree -r --name-only "$tag" model 2>/dev/null; then
            echo "failed to read semantic convention model tree from ${SEMCONV_REPO} at ${tag}" >&2
            return 1
          fi
          return 0
        fi
      
        local url="https://api.github.com/repos/open-telemetry/semantic-conventions/git/trees/${tag}?recursive=1"
        if ! curl -fsSL "$url" 2>/dev/null | jq -r '.tree[] | select(.type == "blob") | .path'; then
          echo "failed to fetch semantic convention model tree from ${url}" >&2
          return 1
        fi
      }
      
      fetch_group_file() {
        local tag="$1"
        local path="$2"
      
        if [[ -n "$SEMCONV_REPO" ]]; then
          if ! git -C "$SEMCONV_REPO" show "${tag}:${path}" 2>/dev/null; then
            echo "failed to read semantic convention file ${path} from ${SEMCONV_REPO} at ${tag}" >&2
            return 1
          fi
          return 0
        fi
      
        local url="https://raw.githubusercontent.com/open-telemetry/semantic-conventions/${tag}/${path}"
        if ! curl -fsSL "$url" 2>/dev/null; then
          echo "failed to fetch semantic convention file from ${url}" >&2
          return 1
        fi
      }
      
      model_records() {
        local model_paths="$1"
      
        awk '
          function kind_for_file(file) {
            if (index(file, "registry")) return "registry"
            if (index(file, "spans")) return "spans"
            if (index(file, "events")) return "events"
            if (index(file, "metrics")) return "metrics"
            if (index(file, "entities")) return "entities"
            if (index(file, "apphub")) return "entities"
            if (index(file, "common")) return "common"
            if (index(file, "logs")) return "logs"
            return ""
          }
      
          $0 ~ "^model/[^/]+/.+\\.ya?ml$" {
            path = $0
            if (path ~ /\/deprecated\//) next
            count = split(path, parts, "/")
            file = tolower(parts[count])
            if (index(file, "deprecated")) next
            kind = kind_for_file(file)
            if (kind != "") {
              printf "%s\t%s\t%s\t%s\n", parts[2], kind, path, parts[count]
            } else if (count == 3 && parts[1] == "model" && parts[2] == "messaging") {
              # v1.44 definition/2 provider files can contain both span definitions
              # and cross-signal attribute groups, despite neutral filenames.
              printf "%s\tspans\t%s\t%s\n", parts[2], path, parts[count]
              printf "%s\tcommon\t%s\t%s\n", parts[2], path, parts[count]
            }
          }
        ' <<<"$model_paths"
      }
      
      list_available_groups() {
        local records="$1"
        awk -F '\t' '{ print $1 }' <<<"$records" | sort -u
      }
      
      group_records() {
        local records="$1"
        local normalized_group="$2"
        awk -F '\t' -v group="$normalized_group" '
          function comparable(value) {
            gsub(/_/, "-", value)
            return value
          }
      
          $1 == group || comparable($1) == comparable(group)
        ' <<<"$records"
      }
      
      list_group_kinds() {
        local records="$1"
        awk -F '\t' '
          {
            key = $2
            if (files[key] == "") {
              files[key] = $4
            } else {
              files[key] = files[key] ", " $4
            }
          }
          END {
            for (key in files) {
              printf "%s\t%s\n", key, files[key]
            }
          }
        ' <<<"$records" | sort -t $'\t' -k1,1
      }
      
      find_kind_files() {
        local records="$1"
        local requested_kind="$2"
        awk -F '\t' -v kind="$requested_kind" '$2 == kind { printf "%s\t%s\n", $3, $4 }' <<<"$records"
      }
      
      find_registry_path() {
        local records="$1"
        awk -F '\t' '$2 == "registry" { print $3; exit }' <<<"$records"
      }
      
      is_definition_v2() {
        local content="$1"
        [[ "$content" == file_format:\ definition/2* ||
           "$content" == *$'\nfile_format: definition/2'* ]]
      }
      
      print_available_groups() {
        local version="$1"
        local groups="$2"
      
        printf 'version: %s\n' "$version"
        printf 'groups:\n'
        sed 's/^/- /' <<<"$groups"
      }
      
      print_kinds_section() {
        local kinds="$1"
        printf 'kinds:\n'
        awk -F '\t' '{ printf "- %s (%s)\n", $1, $2 }' <<<"$kinds"
      }
      
      extract_entries() {
        local content="$1"
        local id_prefix="$2"
        local field_prefix="$3"
        local width="$4"
      
        awk \
          -v id_prefix="$id_prefix" \
          -v field_prefix="$field_prefix" \
          -v width="$width" '
          function trim(value) {
            sub(/^[[:space:]]+/, "", value)
            sub(/[[:space:]]+$/, "", value)
            return value
          }
      
          function squish(value) {
            gsub(/[[:space:]]+/, " ", value)
            return trim(value)
          }
      
          function unquote(value) {
            value = trim(value)
            if (length(value) >= 2) {
              if ((substr(value, 1, 1) == "\"" && substr(value, length(value), 1) == "\"") ||
                  (substr(value, 1, 1) == "'"'"'" && substr(value, length(value), 1) == "'"'"'")) {
                value = substr(value, 2, length(value) - 2)
              }
            }
            return value
          }
      
          function indentation(value) {
            match(value, /[^ ]/)
            return RSTART == 0 ? length(value) : RSTART - 1
          }
      
          function emit_record() {
            local_brief = squish(brief)
            local_stability = stability == "" ? "-" : stability
            if (id != "") {
              printf "%-*s %-12s %s\n", width, id, local_stability, local_brief
            }
            id = ""
            stability = ""
            brief = ""
            collecting_brief = 0
          }
      
          index($0, id_prefix) == 1 {
            emit_record()
            id = $0
            sub("^" id_prefix, "", id)
            next
          }
      
          id == "" {
            next
          }
      
          collecting_brief && $0 !~ /^[[:space:]]*$/ && indentation($0) < length(field_prefix) {
            collecting_brief = 0
            next
          }
      
          collecting_brief && $0 ~ ("^" field_prefix "[A-Za-z_][A-Za-z0-9_]*:") {
            collecting_brief = 0
          }
      
          collecting_brief {
            line = $0
            sub(/^[[:space:]]+/, "", line)
            brief = brief " " line
            next
          }
      
          index($0, field_prefix "stability: ") == 1 {
            stability = $0
            sub("^" field_prefix "stability: ", "", stability)
            sub(/[[:space:]]+#.*/, "", stability)
            stability = unquote(stability)
            next
          }
      
          index($0, field_prefix "brief:") == 1 {
            line = $0
            sub("^" field_prefix "brief:[[:space:]]*", "", line)
            if (line == "" || line == ">" || line == "|" || line == ">-" || line == "|-" || line == ">+" || line == "|+") {
              collecting_brief = 1
              next
            }
            brief = brief " " unquote(line)
            next
          }
      
          END {
            emit_record()
          }
        ' <<<"$content"
      }
      
      extract_v2_entries() {
        local content="$1"
        local requested_kind="$2"
        local width="$3"
      
        awk -v requested_kind="$requested_kind" -v width="$width" '
          function trim(value) {
            sub(/^[[:space:]]+/, "", value)
            sub(/[[:space:]]+$/, "", value)
            return value
          }
          function squish(value) {
            gsub(/[[:space:]]+/, " ", value)
            return trim(value)
          }
          function section_kind(value) {
            if (value == "attributes") return "registry"
            if (value == "spans" || value == "span_refinements") return "spans"
            if (value == "events" || value == "event_refinements") return "events"
            if (value == "metrics" || value == "metric_refinements") return "metrics"
            if (value == "entities" || value == "entity_refinements") return "entities"
            if (value == "logs" || value == "log_refinements") return "logs"
            if (value == "attribute_groups") return "common"
            return ""
          }
          function emit() {
            if (id != "") {
              printf "%-*s %-12s %s\n", width, id, (stability == "" ? "-" : stability), squish(brief)
            }
            id = stability = brief = ""
            collecting_brief = 0
          }
          /^[A-Za-z_][A-Za-z0-9_]*:$/ {
            emit()
            section = $0
            sub(/:$/, "", section)
            active = section_kind(section) == requested_kind
            next
          }
          active && /^  - (id|key|name|type): / {
            emit()
            id = $0
            sub(/^  - (id|key|name|type): /, "", id)
            next
          }
          active && id != "" && /^    stability: / {
            stability = $0
            sub(/^    stability: /, "", stability)
            sub(/[[:space:]]+#.*/, "", stability)
            next
          }
          active && id != "" && /^    brief:/ {
            line = $0
            sub(/^    brief:[[:space:]]*/, "", line)
            if (line == "" || line ~ /^[>|][-+]?$/) collecting_brief = 1
            else brief = line
            next
          }
          collecting_brief {
            if ($0 ~ /^      /) {
              line = $0
              sub(/^      /, "", line)
              brief = brief " " line
            } else {
              collecting_brief = 0
            }
          }
          END { emit() }
        ' <<<"$content"
      }
      
      extract_exact_v2_entry() {
        local content="$1"
        local entry_id="$2"
        local tag="$3"
        local path="$4"
      
        awk -v entry_id="$entry_id" -v tag="$tag" -v path="$path" '
          /^  - (id|key|name|type): / {
            if (found) exit
            candidate = $0
            sub(/^  - (id|key|name|type): /, "", candidate)
            if (candidate == entry_id) {
              found = 1
              start_line = NR
              print "source: https://github.com/open-telemetry/semantic-conventions/blob/" tag "/" path "#L" start_line
            }
          }
          found {
            if (NR > start_line && $0 ~ /^[A-Za-z_][A-Za-z0-9_]*:$/) exit
            print
          }
          END { if (!found) exit 1 }
        ' <<<"$content"
      }
      
      extract_exact_entry() {
        local content="$1"
        local id_prefix="$2"
        local entry_id="$3"
        local tag="$4"
        local path="$5"
      
        awk -v id_prefix="$id_prefix" -v entry_id="$entry_id" -v tag="$tag" -v path="$path" '
          BEGIN {
            entry_indent = match(id_prefix, /[^ ]/) - 1
          }
      
          index($0, id_prefix) == 1 {
            if (found) {
              exit
            }
            if ($0 == id_prefix entry_id) {
              found = 1
              start_line = NR
              print "source: https://github.com/open-telemetry/semantic-conventions/blob/" tag "/" path "#L" start_line
            }
          }
      
          found {
            if (NR > start_line && $0 !~ /^[[:space:]]*$/) {
              current_indent = match($0, /[^ ]/) - 1
              if (current_indent <= entry_indent) {
                exit
              }
            }
            print
          }
      
          END {
            if (!found) {
              exit 1
            }
          }
        ' <<<"$content"
      }
      
      print_group_summary() {
        local requested_group="$1"
        local version="$2"
        local kinds="$3"
        local source_url="$4"
        local registry_entries="${5:-}"
      
        printf 'group: %s\n' "$requested_group"
        printf 'version: %s\n' "$version"
        printf 'source: %s\n' "$source_url"
        print_kinds_section "$kinds"
        printf '\n'
        if [[ -n "$registry_entries" ]]; then
          printf '%s' "$registry_entries"
        else
          printf 'note: no registry file available for this group in the latest release\n'
        fi
      }
      
      print_kind_listing() {
        local requested_group="$1"
        local version="$2"
        local kinds="$3"
        local source_url="$4"
        local requested_kind="$5"
        local kind_entries="$6"
      
        printf 'group: %s\n' "$requested_group"
        printf 'version: %s\n' "$version"
        printf 'source: %s\n' "$source_url"
        print_kinds_section "$kinds"
        printf '\nkind: %s\n\n%s' "$requested_kind" "$kind_entries"
      }
      
      main() {
        require_bin awk
        if [[ -n "$SEMCONV_REPO" ]]; then
          require_bin git
        else
          require_bin curl
          require_bin jq
        fi
      
        case "${1:-}" in
          ""|-h|--help)
            usage
            [[ $# -gt 0 ]] && return 0
            return 1
            ;;
        esac
      
        local tag version model_paths records groups
      
        tag="$(fetch_latest_tag)"
        version="${tag#v}"
        model_paths="$(fetch_model_paths "$tag")"
        records="$(model_records "$model_paths")"
      
        case "${1:-}" in
          --groups)
            [[ $# -eq 1 ]] || { usage; return 1; }
            groups="$(list_available_groups "$records")"
            print_available_groups "$version" "$groups"
            return 0
            ;;
        esac
      
        [[ $# -le 2 ]] || { usage; return 1; }
      
        local requested_group="$1"
        local second_arg="${2:-}"
        local normalized_group resolved_group group_records_output kinds registry_path registry_content registry_entries source_url
        local requested_kind kind_files file_path file_content kind_entries matched_entry
      
        normalized_group="$(normalize_group "$requested_group")"
        group_records_output="$(group_records "$records" "$normalized_group")"
        [[ -n "$group_records_output" ]] || {
          echo "no supported semantic convention files found for group '${requested_group}'" >&2
          return 1
        }
      
        resolved_group="$(awk -F '\t' 'NR == 1 { print $1 }' <<<"$group_records_output")"
        kinds="$(list_group_kinds "$group_records_output")"
        registry_path="$(find_registry_path "$group_records_output")"
        source_url="$(group_url "$tag" "$resolved_group")"
      
        if [[ -z "$second_arg" ]]; then
          if [[ -n "$registry_path" ]]; then
            registry_content="$(fetch_group_file "$tag" "$registry_path")"
            if is_definition_v2 "$registry_content"; then
              registry_entries="$(extract_v2_entries "$registry_content" registry 32)"
            else
              registry_entries="$(extract_entries "$registry_content" "      - id: " "        " 32)"
            fi
          else
            registry_entries=""
          fi
          print_group_summary "$resolved_group" "$version" "$kinds" "$source_url" "$registry_entries"
          return 0
        fi
      
        if is_supported_kind "$second_arg"; then
          requested_kind="$second_arg"
          kind_files="$(find_kind_files "$group_records_output" "$requested_kind")"
          [[ -n "$kind_files" ]] || {
            echo "semantic convention kind '${requested_kind}' is not available for group '${requested_group}'" >&2
            return 1
          }
      
          if [[ "$requested_kind" == "registry" ]]; then
            [[ -n "$registry_path" ]] || {
              echo "semantic convention kind 'registry' is not available for group '${requested_group}'" >&2
              return 1
            }
            registry_content="$(fetch_group_file "$tag" "$registry_path")"
            if is_definition_v2 "$registry_content"; then
              registry_entries="$(extract_v2_entries "$registry_content" registry 32)"
            else
              registry_entries="$(extract_entries "$registry_content" "      - id: " "        " 32)"
            fi
            print_kind_listing "$resolved_group" "$version" "$kinds" "$source_url" "$requested_kind" "$registry_entries"
            return 0
          fi
      
          kind_entries=""
          while IFS=$'\t' read -r file_path _; do
            [[ -n "$file_path" ]] || continue
            file_content="$(fetch_group_file "$tag" "$file_path")"
            if is_definition_v2 "$file_content"; then
              kind_entries+="$(extract_v2_entries "$file_content" "$requested_kind" 36)"$'\n'
            else
              kind_entries+="$(extract_entries "$file_content" "  - id: " "    " 36)"$'\n'
            fi
          done <<<"$kind_files"
      
          print_kind_listing "$resolved_group" "$version" "$kinds" "$source_url" "$requested_kind" "$kind_entries"
          return 0
        fi
      
        if [[ -n "$registry_path" ]]; then
          registry_content="$(fetch_group_file "$tag" "$registry_path")"
          if is_definition_v2 "$registry_content" &&
              matched_entry="$(extract_exact_v2_entry "$registry_content" "$second_arg" "$tag" "$registry_path")"; then
            printf '%s\n' "$matched_entry"
            return 0
          fi
          if matched_entry="$(extract_exact_entry "$registry_content" "      - id: " "$second_arg" "$tag" "$registry_path")"; then
            printf '%s\n' "$matched_entry"
            return 0
          fi
        fi
      
        while IFS=$'\t' read -r requested_kind _; do
          [[ -n "$requested_kind" ]] || continue
          [[ "$requested_kind" == "registry" ]] && continue
          kind_files="$(find_kind_files "$group_records_output" "$requested_kind")"
          while IFS=$'\t' read -r file_path _; do
            [[ -n "$file_path" ]] || continue
            file_content="$(fetch_group_file "$tag" "$file_path")"
            if is_definition_v2 "$file_content" &&
                matched_entry="$(extract_exact_v2_entry "$file_content" "$second_arg" "$tag" "$file_path")"; then
              printf '%s\n' "$matched_entry"
              return 0
            fi
            if matched_entry="$(extract_exact_entry "$file_content" "  - id: " "$second_arg" "$tag" "$file_path")"; then
              printf '%s\n' "$matched_entry"
              return 0
            fi
          done <<<"$kind_files"
        done <<<"$kinds"
      
        echo "semantic convention entry not found: ${second_arg}" >&2
        return 1
      }
      
      main "$@"
      
    • test-query-otel-semantic-conventions.sh 1.8 KB
      #!/usr/bin/env bash
      
      set -euo pipefail
      
      script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
      repo=${OTEL_SEMCONV_REPO:-}
      
      if [[ -z "$repo" ]]; then
        printf 'OTEL_SEMCONV_REPO must point to a semantic-conventions checkout\n' >&2
        exit 2
      fi
      
      query=(env OTEL_SEMCONV_REPO="$repo" OTEL_SEMCONV_TAG=v1.44.0 "$script_dir/query-otel-semantic-conventions.sh")
      
      messaging_spans=$("${query[@]}" messaging spans)
      grep -q '^span[.]messaging[.]aws[.]sqs[.]send[.]producer' <<<"$messaging_spans"
      
      messaging_common=$("${query[@]}" messaging common)
      grep -q '^messaging[.]kafka[.]attributes[.]common' <<<"$messaging_common"
      grep -q '^messaging[.]rabbitmq[.]attributes[.]destination' <<<"$messaging_common"
      
      exact_entry=$("${query[@]}" messaging span.messaging.aws.sqs.send.producer)
      grep -q '^source: .*semantic-conventions/blob/v1[.]44[.]0/model/messaging/aws[.]yaml#L' <<<"$exact_entry"
      
      client_registry=$("${query[@]}" client registry)
      grep -q '^client[.]address' <<<"$client_registry"
      
      client_exact=$("${query[@]}" client client.address)
      grep -q '^source: .*semantic-conventions/blob/v1[.]44[.]0/model/client/registry[.]yaml#L' <<<"$client_exact"
      
      hardware_metrics=$("${query[@]}" hardware metrics)
      grep -q '^hw[.]battery[.]charge ' <<<"$hardware_metrics"
      
      hardware_exact=$("${query[@]}" hardware hw.battery.charge)
      grep -q '^source: .*semantic-conventions/blob/v1[.]44[.]0/model/hardware/battery-metrics[.]yaml#L' <<<"$hardware_exact"
      
      legacy_http=$(env OTEL_SEMCONV_REPO="$repo" OTEL_SEMCONV_TAG=v1.43.0 "$script_dir/query-otel-semantic-conventions.sh" http spans)
      grep -q '^span[.]http[.]client' <<<"$legacy_http"
      
      legacy_messaging=$(env OTEL_SEMCONV_REPO="$repo" OTEL_SEMCONV_TAG=v1.43.0 "$script_dir/query-otel-semantic-conventions.sh" messaging spans)
      grep -q '^messaging[.]kafka' <<<"$legacy_messaging"
      
  • SKILL.md 2.7 KB
    ---
    name: otel-semantic-conventions
    description: OpenTelemetry semantic convention lookup and naming guidance. Use when selecting released semantic convention groups, attributes, or span naming rules, when checking semantic convention compliance, or checking which semantic-conventions version a language SDK's semconv package exposes.
    ---
    
    # OpenTelemetry Semantic Conventions
    
    Use this skill when you need released semantic convention guidance for naming, attributes, or compliance checks.
    
    
    ## Workflow
    
    1. Start with released semantic conventions, not model memory.
    - do not load the full semantic convention spec into context
    - use the bundled lookup script to query only the needed released group or attribute
    - for local verification against a checked-out upstream repo, set `OTEL_SEMCONV_REPO=/path/to/semantic-conventions`
    
    2. Choose the closest released group before inventing custom keys.
    - identify the boundary type such as `http`, `db`, `messaging`, `rpc`, or `network`
    - pick one primary group first, then add related groups only when they add needed context
    - for `gen_ai.*`, OpenAI, or MCP conventions, use the dedicated OpenTelemetry GenAI semantic conventions repository; the core repository keeps only deprecated GenAI-era stubs after v1.42.0
    - see `references/semconv-selection.md`
    - to target a version a language's semconv package actually exposes (works offline, dated), see `references/sdk-semconv-packages.md`
    
    3. Query only the released guidance you need.
    - list groups: `./scripts/query-otel-semantic-conventions.sh --groups`
    - inspect one group: `./scripts/query-otel-semantic-conventions.sh http`
    - inspect one kind: `./scripts/query-otel-semantic-conventions.sh http spans`
    - inspect one exact attribute or entry: `./scripts/query-otel-semantic-conventions.sh http http.request.method`
    - see `references/otel-semantic-conventions.md`
    - after changing the parser, run `OTEL_SEMCONV_REPO=/path/to/semantic-conventions bash ./scripts/test-query-otel-semantic-conventions.sh`
    
    4. Apply the released naming and attribute rules directly.
    - use required and recommended attributes before optional ones
    - derive semconv-governed span names directly from the released naming rule
    - do not prepend or append protocol labels, hostnames, product names, business hints, or other custom prose to semconv-governed span names
    - if the released naming rule does not provide a low-cardinality target, use the simpler fallback allowed by that convention
    - if no released key exists, use a stable custom namespace and keep values bounded
    
    5. Return the result with exact source context.
    - include the group name, released version, and source URL from the script output
    - call out any concrete compatibility limitation if the implementation cannot fully match the released guidance
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related