Skip to content

Commit efb0ce7

Browse files
feat(semconv): autoload namespaces so a single require exposes all constants
Consuming apps previously had to require each SemConv namespace by hand (`require 'opentelemetry/semconv/http'`, ...), which got verbose. Register every stable and incubating namespace with Ruby's `autoload` instead, so a single `require 'opentelemetry-semantic_conventions'` makes them all referenceable while still loading a namespace's file only on first use -- preserving the goal of not pulling in constants an app never touches. - Generate lib/opentelemetry/semconv.rb (autoload manifest) via a new `generate_autoload_manifest` Rake task wired into `rake generate`; it reads the existing rollup files, so it needs no Docker/weaver. - Require the manifest from the gem entrypoint. Backward compatible: manual per-namespace requires and the legacy SemanticConventions path still work. - Document the single-require pattern in the README. - Add tests covering lazy loading (in a clean subprocess) and a drift guard asserting every generated rollup has an autoload entry.
1 parent 8236b5a commit efb0ce7

5 files changed

Lines changed: 231 additions & 5 deletions

File tree

semantic_conventions/README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,19 @@ These constants will be preserved to avoid breaking changes for users who rely
5555
on the old constants. These constants do not differentiate between stable and
5656
unstable constants. New constants will not be added to this namespace.
5757

58-
We recommend you require only the files that contain the constants you are going
59-
to use. For example, if you were creating instrumentation for an HTTP Client
60-
that emits only stable conventions, you would likely require:
58+
Require the gem once and reference any constant directly. Each namespace is
59+
registered with Ruby's `autoload`, so the file backing a namespace is only loaded
60+
the first time you reference one of its constants — you don't pay to load
61+
conventions you never use.
6162

6263
```rb
63-
require 'opentelemetry/semconv/http'
64+
require 'opentelemetry-semantic_conventions'
65+
66+
# Stable
67+
OpenTelemetry::SemConv::HTTP::HTTP_REQUEST_METHOD # => 'http.request.method'
68+
69+
# Incubating (experimental/deprecated)
70+
OpenTelemetry::SemConv::Incubating::GEN_AI::GEN_AI_SYSTEM
6471
```
6572

6673
If you want to require all of the 1.11.0 constants, you can use:

semantic_conventions/Rakefile

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ default_tasks =
4545
task default: default_tasks
4646

4747
desc 'update semantic conventions'
48-
task generate: %i[update_spec_version_constant generate_require_rollups]
48+
task generate: %i[update_spec_version_constant generate_require_rollups generate_autoload_manifest]
4949

5050
SPEC_VERSION = '1.41.1'
5151
OTEL_WEAVER_VERSION = 'v0.22.1'
@@ -122,6 +122,48 @@ task generate_require_rollups: %i[generate_semconv rubocop_autocorrect] do
122122
end
123123
end
124124

125+
# Render the autoload declarations for the given rollup files, indented for the
126+
# manifest. The module constant is the screaming_snake_case of the namespace,
127+
# which for these snake_case file names is just the upcased basename
128+
# (e.g. gen_ai.rb => GEN_AI).
129+
def semconv_autoloads(rollups, require_prefix, indent)
130+
rollups.sort.map do |rollup|
131+
namespace = rollup.basename('.rb').to_s
132+
"#{' ' * indent}autoload :#{namespace.upcase}, '#{require_prefix}/#{namespace}'"
133+
end.join("\n")
134+
end
135+
136+
desc 'Generate the autoload manifest so a single require lazily exposes every namespace.'
137+
task :generate_autoload_manifest do
138+
puts "\n+++ Generating autoload manifest for the SemConv namespace.\n"
139+
140+
# The per-namespace rollup files sit directly under semconv_output_dir (stable)
141+
# and under its incubating/ subdirectory.
142+
stable = semconv_autoloads(semconv_output_dir.glob('*.rb'), 'opentelemetry/semconv', 4)
143+
incubating = semconv_autoloads(semconv_output_dir.join('incubating').glob('*.rb'), 'opentelemetry/semconv/incubating', 6)
144+
145+
manifest_filename = semconv_output_dir.dirname + "#{semconv_output_dir.basename}.rb"
146+
File.write(manifest_filename, <<~MANIFEST)
147+
# frozen_string_literal: true
148+
149+
#{COPYRIGHT_NOTICE}
150+
# This file was autogenerated. Do not edit it by hand.
151+
152+
module OpenTelemetry
153+
# Stable OpenTelemetry semantic convention constants, autoloaded per namespace.
154+
module SemConv
155+
#{stable}
156+
157+
# Experimental, development, or deprecated semantic convention constants.
158+
module Incubating
159+
#{incubating}
160+
end
161+
end
162+
end
163+
MANIFEST
164+
puts "✅ Generated file \"#{manifest_filename}\""
165+
end
166+
125167
desc 'Bump the semantic_conventions gem version to match the spec'
126168
task :update_spec_version_constant do
127169
puts "\n+++ Updating library SPEC_VERSION constant to #{SPEC_VERSION}.\n"

semantic_conventions/lib/opentelemetry-semantic_conventions.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
# SPDX-License-Identifier: Apache-2.0
66

77
require_relative 'opentelemetry/semantic_conventions'
8+
require_relative 'opentelemetry/semconv'
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
# SPDX-License-Identifier: Apache-2.0
18+
19+
# This file was autogenerated. Do not edit it by hand.
20+
21+
module OpenTelemetry
22+
# Stable OpenTelemetry semantic convention constants, autoloaded per namespace.
23+
module SemConv
24+
autoload :ASPNETCORE, 'opentelemetry/semconv/aspnetcore'
25+
autoload :CLIENT, 'opentelemetry/semconv/client'
26+
autoload :CODE, 'opentelemetry/semconv/code'
27+
autoload :DB, 'opentelemetry/semconv/db'
28+
autoload :DOTNET, 'opentelemetry/semconv/dotnet'
29+
autoload :ERROR, 'opentelemetry/semconv/error'
30+
autoload :EXCEPTION, 'opentelemetry/semconv/exception'
31+
autoload :HTTP, 'opentelemetry/semconv/http'
32+
autoload :JVM, 'opentelemetry/semconv/jvm'
33+
autoload :KESTREL, 'opentelemetry/semconv/kestrel'
34+
autoload :NETWORK, 'opentelemetry/semconv/network'
35+
autoload :OTEL, 'opentelemetry/semconv/otel'
36+
autoload :SERVER, 'opentelemetry/semconv/server'
37+
autoload :SERVICE, 'opentelemetry/semconv/service'
38+
autoload :SIGNALR, 'opentelemetry/semconv/signalr'
39+
autoload :TELEMETRY, 'opentelemetry/semconv/telemetry'
40+
autoload :URL, 'opentelemetry/semconv/url'
41+
autoload :USER_AGENT, 'opentelemetry/semconv/user_agent'
42+
43+
# Experimental, development, or deprecated semantic convention constants.
44+
module Incubating
45+
autoload :ANDROID, 'opentelemetry/semconv/incubating/android'
46+
autoload :APP, 'opentelemetry/semconv/incubating/app'
47+
autoload :ARTIFACT, 'opentelemetry/semconv/incubating/artifact'
48+
autoload :ASPNETCORE, 'opentelemetry/semconv/incubating/aspnetcore'
49+
autoload :AWS, 'opentelemetry/semconv/incubating/aws'
50+
autoload :AZ, 'opentelemetry/semconv/incubating/az'
51+
autoload :AZURE, 'opentelemetry/semconv/incubating/azure'
52+
autoload :BROWSER, 'opentelemetry/semconv/incubating/browser'
53+
autoload :CASSANDRA, 'opentelemetry/semconv/incubating/cassandra'
54+
autoload :CICD, 'opentelemetry/semconv/incubating/cicd'
55+
autoload :CLIENT, 'opentelemetry/semconv/incubating/client'
56+
autoload :CLOUD, 'opentelemetry/semconv/incubating/cloud'
57+
autoload :CLOUDEVENTS, 'opentelemetry/semconv/incubating/cloudevents'
58+
autoload :CLOUDFOUNDRY, 'opentelemetry/semconv/incubating/cloudfoundry'
59+
autoload :CODE, 'opentelemetry/semconv/incubating/code'
60+
autoload :CONTAINER, 'opentelemetry/semconv/incubating/container'
61+
autoload :CPU, 'opentelemetry/semconv/incubating/cpu'
62+
autoload :CPYTHON, 'opentelemetry/semconv/incubating/cpython'
63+
autoload :DB, 'opentelemetry/semconv/incubating/db'
64+
autoload :DEPLOYMENT, 'opentelemetry/semconv/incubating/deployment'
65+
autoload :DESTINATION, 'opentelemetry/semconv/incubating/destination'
66+
autoload :DEVICE, 'opentelemetry/semconv/incubating/device'
67+
autoload :DISK, 'opentelemetry/semconv/incubating/disk'
68+
autoload :DNS, 'opentelemetry/semconv/incubating/dns'
69+
autoload :DOTNET, 'opentelemetry/semconv/incubating/dotnet'
70+
autoload :ELASTICSEARCH, 'opentelemetry/semconv/incubating/elasticsearch'
71+
autoload :ENDUSER, 'opentelemetry/semconv/incubating/enduser'
72+
autoload :ERROR, 'opentelemetry/semconv/incubating/error'
73+
autoload :EVENT, 'opentelemetry/semconv/incubating/event'
74+
autoload :EXCEPTION, 'opentelemetry/semconv/incubating/exception'
75+
autoload :FAAS, 'opentelemetry/semconv/incubating/faas'
76+
autoload :FEATURE_FLAG, 'opentelemetry/semconv/incubating/feature_flag'
77+
autoload :FILE, 'opentelemetry/semconv/incubating/file'
78+
autoload :GCP, 'opentelemetry/semconv/incubating/gcp'
79+
autoload :GEN_AI, 'opentelemetry/semconv/incubating/gen_ai'
80+
autoload :GEO, 'opentelemetry/semconv/incubating/geo'
81+
autoload :GO, 'opentelemetry/semconv/incubating/go'
82+
autoload :GRAPHQL, 'opentelemetry/semconv/incubating/graphql'
83+
autoload :HEROKU, 'opentelemetry/semconv/incubating/heroku'
84+
autoload :HOST, 'opentelemetry/semconv/incubating/host'
85+
autoload :HTTP, 'opentelemetry/semconv/incubating/http'
86+
autoload :HW, 'opentelemetry/semconv/incubating/hw'
87+
autoload :IOS, 'opentelemetry/semconv/incubating/ios'
88+
autoload :JVM, 'opentelemetry/semconv/incubating/jvm'
89+
autoload :K8S, 'opentelemetry/semconv/incubating/k8s'
90+
autoload :KESTREL, 'opentelemetry/semconv/incubating/kestrel'
91+
autoload :LINUX, 'opentelemetry/semconv/incubating/linux'
92+
autoload :LOG, 'opentelemetry/semconv/incubating/log'
93+
autoload :MAINFRAME, 'opentelemetry/semconv/incubating/mainframe'
94+
autoload :MESSAGE, 'opentelemetry/semconv/incubating/message'
95+
autoload :MESSAGING, 'opentelemetry/semconv/incubating/messaging'
96+
autoload :NET, 'opentelemetry/semconv/incubating/net'
97+
autoload :NETWORK, 'opentelemetry/semconv/incubating/network'
98+
autoload :NODEJS, 'opentelemetry/semconv/incubating/nodejs'
99+
autoload :OCI, 'opentelemetry/semconv/incubating/oci'
100+
autoload :OPENAI, 'opentelemetry/semconv/incubating/openai'
101+
autoload :OPENTRACING, 'opentelemetry/semconv/incubating/opentracing'
102+
autoload :OS, 'opentelemetry/semconv/incubating/os'
103+
autoload :OTEL, 'opentelemetry/semconv/incubating/otel'
104+
autoload :OTHER, 'opentelemetry/semconv/incubating/other'
105+
autoload :PEER, 'opentelemetry/semconv/incubating/peer'
106+
autoload :POOL, 'opentelemetry/semconv/incubating/pool'
107+
autoload :PROCESS, 'opentelemetry/semconv/incubating/process'
108+
autoload :PROFILE, 'opentelemetry/semconv/incubating/profile'
109+
autoload :RPC, 'opentelemetry/semconv/incubating/rpc'
110+
autoload :SECURITY_RULE, 'opentelemetry/semconv/incubating/security_rule'
111+
autoload :SERVER, 'opentelemetry/semconv/incubating/server'
112+
autoload :SERVICE, 'opentelemetry/semconv/incubating/service'
113+
autoload :SESSION, 'opentelemetry/semconv/incubating/session'
114+
autoload :SIGNALR, 'opentelemetry/semconv/incubating/signalr'
115+
autoload :SOURCE, 'opentelemetry/semconv/incubating/source'
116+
autoload :SYSTEM, 'opentelemetry/semconv/incubating/system'
117+
autoload :TELEMETRY, 'opentelemetry/semconv/incubating/telemetry'
118+
autoload :TEST, 'opentelemetry/semconv/incubating/test'
119+
autoload :THREAD, 'opentelemetry/semconv/incubating/thread'
120+
autoload :TLS, 'opentelemetry/semconv/incubating/tls'
121+
autoload :URL, 'opentelemetry/semconv/incubating/url'
122+
autoload :USER, 'opentelemetry/semconv/incubating/user'
123+
autoload :USER_AGENT, 'opentelemetry/semconv/incubating/user_agent'
124+
autoload :V8JS, 'opentelemetry/semconv/incubating/v8js'
125+
autoload :VCS, 'opentelemetry/semconv/incubating/vcs'
126+
autoload :WEBENGINE, 'opentelemetry/semconv/incubating/webengine'
127+
autoload :ZOS, 'opentelemetry/semconv/incubating/zos'
128+
end
129+
end
130+
end
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright The OpenTelemetry Authors
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
require 'test_helper'
8+
9+
# test_helper eager-requires every file, which would mask autoload, so the
10+
# lazy-loading behaviour is exercised in a clean subprocess.
11+
describe 'OpenTelemetry::SemConv autoload' do
12+
let(:gem_root) { File.expand_path('../../..', __dir__) }
13+
14+
it 'lazily exposes stable and incubating namespaces from a single require' do
15+
script = <<~RUBY
16+
require 'opentelemetry-semantic_conventions'
17+
raise 'stable namespace not autoloaded' unless OpenTelemetry::SemConv::HTTP::HTTP_REQUEST_METHOD == 'http.request.method'
18+
raise 'incubating namespace not autoloaded' unless OpenTelemetry::SemConv::Incubating::GEN_AI::GEN_AI_SYSTEM == 'gen_ai.system'
19+
RUBY
20+
21+
lib = File.join(gem_root, 'lib')
22+
api_lib = File.expand_path('../api/lib', gem_root)
23+
ok = system(Gem.ruby, "-I#{lib}", "-I#{api_lib}", '-e', script)
24+
25+
assert ok, 'a single require should lazily load both stable and incubating constants'
26+
end
27+
28+
it 'registers an autoload for every generated namespace rollup' do
29+
manifest = File.read(File.join(gem_root, 'lib', 'opentelemetry', 'semconv.rb'))
30+
31+
namespaces_in('*.rb').each do |ns|
32+
assert_includes manifest, "autoload :#{ns.upcase}, 'opentelemetry/semconv/#{ns}'"
33+
end
34+
35+
namespaces_in('incubating', '*.rb').each do |ns|
36+
assert_includes manifest, "autoload :#{ns.upcase}, 'opentelemetry/semconv/incubating/#{ns}'"
37+
end
38+
end
39+
40+
private
41+
42+
# Namespace basenames of the rollup files matched by +glob+ under the semconv dir.
43+
def namespaces_in(*glob)
44+
Dir[File.join(gem_root, 'lib', 'opentelemetry', 'semconv', *glob)].map { |f| File.basename(f, '.rb') }
45+
end
46+
end

0 commit comments

Comments
 (0)