opentelemetry-config 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6920e5ca17ba99ef990626f22fb6d1d5b09fd0f07a02039c48c7fb420e59d2b
4
- data.tar.gz: 3cf56fdd238b6903c3209b150860e5998ad7dbcd26e8e8588b18ea0699115543
3
+ metadata.gz: e6246820b0b2b5312be2c696c49f6def8f666425b3b5365a44e811fb455a3003
4
+ data.tar.gz: 38e2b961380f9394cdfa067590e40230e597436d5a349bbc28df3b8aaf58999c
5
5
  SHA512:
6
- metadata.gz: 677ce312429f98ce5c1555c4d53172ae8ee4fc0c44bff5052844f3afd735d576299306f5d36964afdf1b41d4b115d5c2540a35b7c38d615df33fd8125125e37f
7
- data.tar.gz: 60eb6aa2db9e5efec14a2e6c23c2c9fa93d1f6f02ad22998f064836d0a2532ca8b585c8f2a36072f5d52a20093f1eea1b666b9e29d1aec650eb0ec33189363c2
6
+ metadata.gz: 2dbb1307ce50f1bc2390b1ed90741ae07f74499fade71f1de4540cf5ca930d09e6eca24eb34ca728723a930b3327d0fd9da9b799fb7ad63359e324c9773284cf
7
+ data.tar.gz: 7e0aa84b67fa17b591343b5cafb9ec58dc5cdff3507dcbbefbfe044bfcbc63f56b8d1603f08317221f759e9a8a0bbf71d2f8ddb93c73a81bd11235386d308d35
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Release History: opentelemetry-config
2
2
 
3
+ ### v0.2.0 / 2026-10-20
4
+
5
+ * ADDED: Add declarative configuration for logger_provider (#2439)
6
+ * ADDED: Substitute environment variables in configuration files (#2468)
7
+ * FIXED: Correct schema_url unsupported warning (#2466)
8
+ * FIXED: Parse nullable objects without properties as present (#2465)
9
+
10
+ ### v0.1.1 / 2026-10-13
11
+
12
+ * FIXED: Ignore SDK env defaults in declarative config (#2429)
13
+
3
14
  ### v0.1.0 / 2026-09-29
4
15
 
5
16
  * ADDED: Declarative configuration for trace_provider (#2161)
data/README.md CHANGED
@@ -17,12 +17,13 @@ It works with:
17
17
  - `opentelemetry-sdk` — Traces
18
18
  - `opentelemetry-exporter-otlp` — OTLP HTTP exporter
19
19
  - `opentelemetry-instrumentation-all` — Instrumentation for gems
20
+ - `opentelemetry-logs-sdk` — Logs (optional, see [Logs](#logs))
21
+ - `opentelemetry-exporter-otlp-logs` — OTLP HTTP log exporter (optional)
20
22
 
21
23
  This code is still under development and is not a complete implementation of the declarative configuration specification. Until the code becomes stable, declarative configuration functionality will live outside stable OpenTelemetry libraries.
22
24
 
23
25
  Some features we still need to implement include:
24
26
 
25
- - Configuration file precedence over environment variables
26
27
  - Using declarative configuration without a call in your code
27
28
  - Declarative configuration instrumentation API and SDK
28
29
 
@@ -65,6 +66,54 @@ If you have a config file path at hand, call `configure_from_file` instead:
65
66
  sdk = OpenTelemetry::Config.configure_from_file('/path/to/otel-config.yaml')
66
67
  ```
67
68
 
69
+ ### Environment variables and SDK-specific settings
70
+
71
+ File-based configuration uses the values in the YAML file and the schema defaults
72
+ for omitted settings. It does not inherit the SDK's `OTEL_*` environment-variable
73
+ defaults.
74
+
75
+ To use an environment variable in the file, reference it in a value as
76
+ `${NAME}`, `${env:NAME}`, or `${NAME:-default}`. The default applies when the
77
+ variable is unset or empty. Use `$$` for a literal `$`. Values are typed after
78
+ substitution, so quote a reference to keep it a string.
79
+
80
+ ```yaml
81
+ resource:
82
+ attributes:
83
+ - name: service.name
84
+ value: ${OTEL_SERVICE_NAME:-unknown_service}
85
+ tracer_provider:
86
+ processors:
87
+ - batch:
88
+ exporter:
89
+ otlp_http:
90
+ endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:-http://localhost:4318}/v1/traces
91
+ ```
92
+
93
+ Some Ruby-specific SDK settings have no equivalent in the configuration schema:
94
+
95
+ - `OTEL_RUBY_BSP_START_THREAD_ON_BOOT` is ignored. A configured batch span
96
+ processor starts its worker thread during construction. Applications that need
97
+ to delay worker startup should use programmatic SDK configuration instead.
98
+ - `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_NONE` and
99
+ `OTEL_RUBY_EXPORTER_OTLP_SSL_VERIFY_PEER` are ignored. The OTLP HTTP exporter
100
+ verifies the server certificate. For a private certificate authority, set
101
+ `tls.ca_file` rather than disabling verification. For mutual TLS, also set
102
+ `tls.cert_file` and `tls.key_file`.
103
+
104
+ ```yaml
105
+ tracer_provider:
106
+ processors:
107
+ - batch:
108
+ exporter:
109
+ otlp_http:
110
+ endpoint: https://collector.example.com:4318/v1/traces
111
+ tls:
112
+ ca_file: /path/to/ca.pem
113
+ cert_file: /path/to/client.pem
114
+ key_file: /path/to/client-key.pem
115
+ ```
116
+
68
117
  ### Parse, create, and install
69
118
 
70
119
  `configure` is a convenience wrapper around the three [SDK operations][sdk-operations]. Call them individually when you need to inspect the configuration model or delay installing the global state:
@@ -156,6 +205,35 @@ propagator:
156
205
 
157
206
  Supported propagator names: `tracecontext`, `baggage`, `b3`, `b3multi`, `jaeger`, `ottrace`, `xray`, `google_cloud_trace_context`.
158
207
 
208
+ ### Logs
209
+
210
+ Logs support is optional. Add `opentelemetry-logs-sdk` to your Gemfile, along with `opentelemetry-exporter-otlp-logs` if you use the `otlp_http` exporter, and require them before calling `configure`. If a required gem isn't loaded, the logger provider or processor that needs it is skipped with a warning.
211
+
212
+ ```ruby
213
+ require 'opentelemetry-sdk'
214
+ require 'opentelemetry-logs-sdk'
215
+ require 'opentelemetry-exporter-otlp-logs'
216
+ require 'opentelemetry-config'
217
+ ```
218
+
219
+ The configured logger provider is installed as `OpenTelemetry.logger_provider`.
220
+
221
+ ```yaml
222
+ logger_provider:
223
+ limits:
224
+ attribute_count_limit: 128
225
+ processors:
226
+ - batch:
227
+ exporter:
228
+ otlp_http:
229
+ endpoint: http://localhost:4318/v1/logs
230
+ - simple:
231
+ exporter:
232
+ console:
233
+ ```
234
+
235
+ Supported processors: `batch`, `simple`. Supported exporters: `console`, `otlp_http`.
236
+
159
237
  ## Examples
160
238
 
161
239
  A runnable example application is available in the [`example/`][example-dir] directory. It demonstrates traces configured from YAML with console output.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Config
9
+ # Schema defaults for omitted or null properties. Keys use schema names;
10
+ # durations are in milliseconds and are converted by the builders.
11
+ module Defaults
12
+ BATCH_SPAN_PROCESSOR = {
13
+ schedule_delay: 5_000,
14
+ export_timeout: 30_000,
15
+ max_queue_size: 2048,
16
+ max_export_batch_size: 512
17
+ }.freeze
18
+
19
+ SPAN_LIMITS = {
20
+ attribute_count_limit: 128,
21
+ attribute_value_length_limit: nil,
22
+ event_count_limit: 128,
23
+ link_count_limit: 128,
24
+ event_attribute_count_limit: 128,
25
+ link_attribute_count_limit: 128
26
+ }.freeze
27
+
28
+ OTLP_HTTP_EXPORTER = {
29
+ compression: 'none',
30
+ timeout: 10_000
31
+ }.freeze
32
+
33
+ OTLP_HTTP_TRACES_ENDPOINT = 'http://localhost:4318/v1/traces'
34
+ end
35
+
36
+ private_constant :Defaults
37
+ end
38
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ #
5
+ # SPDX-License-Identifier: Apache-2.0
6
+
7
+ module OpenTelemetry
8
+ module Config
9
+ # Loads configuration file YAML with environment variable substitution.
10
+ #
11
+ # References of the form +${NAME}+, +${env:NAME}+, and +${NAME:-default}+ are
12
+ # replaced in scalar values, and +$$+ escapes a literal +$+. Mapping keys are
13
+ # left untouched. Types are resolved after substitution, so a plain
14
+ # +${PORT}+ loads as an Integer while a quoted +"${PORT}"+ stays a String.
15
+ module EnvSubstitution
16
+ # Raised when a configuration file contains an invalid substitution reference.
17
+ class Error < StandardError; end
18
+
19
+ # A colon followed by "-" starts a default value, not a prefix.
20
+ REFERENCE = /\$\{(?:(?<prefix>[a-zA-Z][a-zA-Z0-9_]*):(?!-))?(?<content>[^}]+)\}/
21
+ ENV_REFERENCE = /\A(?<name>[a-zA-Z_][a-zA-Z0-9_]*)(?::-(?<default>[^\n]*))?\z/
22
+ PERMITTED_CLASSES = %w[Date Time].freeze
23
+ private_constant :REFERENCE, :ENV_REFERENCE, :PERMITTED_CLASSES
24
+
25
+ module_function
26
+
27
+ # Parses YAML and substitutes environment variables in its scalar values.
28
+ #
29
+ # @param content [String] YAML document
30
+ # @param env [Hash{String => String}] environment variables to substitute
31
+ # @return [Object, nil] the loaded document, or nil when it is empty
32
+ # @raise [Error] when a substitution reference is invalid
33
+ # @raise [Psych::Exception] when the YAML is invalid or uses a disallowed
34
+ # class or alias
35
+ def load(content, env = ENV)
36
+ document = Psych.parse(content)
37
+ return unless document
38
+
39
+ substitute_node(document, env)
40
+
41
+ class_loader = Psych::ClassLoader::Restricted.new(PERMITTED_CLASSES, [])
42
+ scanner = Psych::ScalarScanner.new(class_loader)
43
+ Psych::Visitors::NoAliasRuby.new(scanner, class_loader).accept(document)
44
+ end
45
+
46
+ # Substitutes environment variable references in a string.
47
+ #
48
+ # @param value [String]
49
+ # @param env [Hash{String => String}] environment variables to substitute
50
+ # @return [String]
51
+ # @raise [Error] when a substitution reference is invalid
52
+ def substitute(value, env = ENV)
53
+ # Split on escapes first so a "$" they produce never starts a reference.
54
+ value.split('$$', -1).map do |segment|
55
+ segment.gsub(REFERENCE) { resolve(Regexp.last_match, env) }
56
+ end.join('$')
57
+ end
58
+
59
+ # Substitutes scalar values in a node tree, skipping mapping keys.
60
+ def substitute_node(node, env)
61
+ case node
62
+ when Psych::Nodes::Scalar
63
+ node.value = substitute(node.value, env)
64
+ when Psych::Nodes::Mapping
65
+ node.children.each_slice(2) { |_key, value| substitute_node(value, env) }
66
+ else
67
+ Array(node.children).each { |child| substitute_node(child, env) }
68
+ end
69
+ end
70
+
71
+ # Returns the replacement for a matched reference.
72
+ def resolve(match, env)
73
+ reference = ENV_REFERENCE.match(match[:content]) if match[:prefix].nil? || match[:prefix] == 'env'
74
+ raise Error, "invalid substitution reference #{match[0]}" unless reference
75
+
76
+ value = env[reference[:name]]
77
+ return value unless value.nil? || value.empty?
78
+
79
+ reference[:default].to_s
80
+ end
81
+ private_class_method :substitute_node, :resolve
82
+ end
83
+ end
84
+ end
@@ -12,6 +12,15 @@ module OpenTelemetry
12
12
  module Config
13
13
  # Generated struct definitions mirroring the OpenTelemetry configuration schema.
14
14
  module Model
15
+ # Parses the nullable object at +key+ with +klass+. An explicit null
16
+ # parses as an object with every property omitted.
17
+ def self.nullable_from_hash(klass, h, key)
18
+ return nil unless h.key?(key)
19
+
20
+ value = h[key]
21
+ klass.from_hash(value.nil? ? {} : value)
22
+ end
23
+
15
24
  Aggregation = Struct.new(
16
25
  :default,
17
26
  :drop,
@@ -27,8 +36,8 @@ module OpenTelemetry
27
36
  new(
28
37
  default: h.key?('default'),
29
38
  drop: h.key?('drop'),
30
- explicit_bucket_histogram: ExplicitBucketHistogramAggregation.from_hash(h['explicit_bucket_histogram']),
31
- base2_exponential_bucket_histogram: Base2ExponentialBucketHistogramAggregation.from_hash(h['base2_exponential_bucket_histogram']),
39
+ explicit_bucket_histogram: Model.nullable_from_hash(ExplicitBucketHistogramAggregation, h, 'explicit_bucket_histogram'),
40
+ base2_exponential_bucket_histogram: Model.nullable_from_hash(Base2ExponentialBucketHistogramAggregation, h, 'base2_exponential_bucket_histogram'),
32
41
  last_value: h.key?('last_value'),
33
42
  sum: h.key?('sum')
34
43
  )
@@ -276,8 +285,8 @@ module OpenTelemetry
276
285
  always_off: h.key?('always_off'),
277
286
  always_on: h.key?('always_on'),
278
287
  parent_threshold: ExperimentalComposableParentThresholdSampler.from_hash(h['parent_threshold']),
279
- probability: ExperimentalComposableProbabilitySampler.from_hash(h['probability']),
280
- rule_based: ExperimentalComposableRuleBasedSampler.from_hash(h['rule_based']),
288
+ probability: Model.nullable_from_hash(ExperimentalComposableProbabilitySampler, h, 'probability'),
289
+ rule_based: Model.nullable_from_hash(ExperimentalComposableRuleBasedSampler, h, 'rule_based'),
281
290
  additional_properties: h.except('always_off', 'always_on', 'parent_threshold', 'probability', 'rule_based')
282
291
  )
283
292
  end
@@ -736,9 +745,9 @@ module OpenTelemetry
736
745
  return nil unless h.is_a?(Hash)
737
746
 
738
747
  new(
739
- otlp_http: OtlpHttpExporter.from_hash(h['otlp_http']),
740
- otlp_grpc: OtlpGrpcExporter.from_hash(h['otlp_grpc']),
741
- otlp_file_development: ExperimentalOtlpFileExporter.from_hash(h['otlp_file/development']),
748
+ otlp_http: Model.nullable_from_hash(OtlpHttpExporter, h, 'otlp_http'),
749
+ otlp_grpc: Model.nullable_from_hash(OtlpGrpcExporter, h, 'otlp_grpc'),
750
+ otlp_file_development: Model.nullable_from_hash(ExperimentalOtlpFileExporter, h, 'otlp_file/development'),
742
751
  console: h.key?('console'),
743
752
  additional_properties: h.except('otlp_http', 'otlp_grpc', 'otlp_file/development', 'console')
744
753
  )
@@ -907,7 +916,7 @@ module OpenTelemetry
907
916
 
908
917
  new(
909
918
  endpoint: h['endpoint'],
910
- tls: GrpcTls.from_hash(h['tls']),
919
+ tls: Model.nullable_from_hash(GrpcTls, h, 'tls'),
911
920
  headers: Array(h['headers']).filter_map { |e| NameStringValuePair.from_hash(e) },
912
921
  headers_list: h['headers_list'],
913
922
  compression: h['compression'],
@@ -932,7 +941,7 @@ module OpenTelemetry
932
941
 
933
942
  new(
934
943
  endpoint: h['endpoint'],
935
- tls: GrpcTls.from_hash(h['tls']),
944
+ tls: Model.nullable_from_hash(GrpcTls, h, 'tls'),
936
945
  headers: Array(h['headers']).filter_map { |e| NameStringValuePair.from_hash(e) },
937
946
  headers_list: h['headers_list'],
938
947
  compression: h['compression'],
@@ -958,7 +967,7 @@ module OpenTelemetry
958
967
 
959
968
  new(
960
969
  endpoint: h['endpoint'],
961
- tls: HttpTls.from_hash(h['tls']),
970
+ tls: Model.nullable_from_hash(HttpTls, h, 'tls'),
962
971
  headers: Array(h['headers']).filter_map { |e| NameStringValuePair.from_hash(e) },
963
972
  headers_list: h['headers_list'],
964
973
  compression: h['compression'],
@@ -985,7 +994,7 @@ module OpenTelemetry
985
994
 
986
995
  new(
987
996
  endpoint: h['endpoint'],
988
- tls: HttpTls.from_hash(h['tls']),
997
+ tls: Model.nullable_from_hash(HttpTls, h, 'tls'),
989
998
  headers: Array(h['headers']).filter_map { |e| NameStringValuePair.from_hash(e) },
990
999
  headers_list: h['headers_list'],
991
1000
  compression: h['compression'],
@@ -1063,7 +1072,7 @@ module OpenTelemetry
1063
1072
  return nil unless h.is_a?(Hash)
1064
1073
 
1065
1074
  new(
1066
- prometheus_development: ExperimentalPrometheusMetricExporter.from_hash(h['prometheus/development']),
1075
+ prometheus_development: Model.nullable_from_hash(ExperimentalPrometheusMetricExporter, h, 'prometheus/development'),
1067
1076
  additional_properties: h.except('prometheus/development')
1068
1077
  )
1069
1078
  end
@@ -1098,10 +1107,10 @@ module OpenTelemetry
1098
1107
  return nil unless h.is_a?(Hash)
1099
1108
 
1100
1109
  new(
1101
- otlp_http: OtlpHttpMetricExporter.from_hash(h['otlp_http']),
1102
- otlp_grpc: OtlpGrpcMetricExporter.from_hash(h['otlp_grpc']),
1103
- otlp_file_development: ExperimentalOtlpFileMetricExporter.from_hash(h['otlp_file/development']),
1104
- console: ConsoleMetricExporter.from_hash(h['console']),
1110
+ otlp_http: Model.nullable_from_hash(OtlpHttpMetricExporter, h, 'otlp_http'),
1111
+ otlp_grpc: Model.nullable_from_hash(OtlpGrpcMetricExporter, h, 'otlp_grpc'),
1112
+ otlp_file_development: Model.nullable_from_hash(ExperimentalOtlpFileMetricExporter, h, 'otlp_file/development'),
1113
+ console: Model.nullable_from_hash(ConsoleMetricExporter, h, 'console'),
1105
1114
  additional_properties: h.except('otlp_http', 'otlp_grpc', 'otlp_file/development', 'console')
1106
1115
  )
1107
1116
  end
@@ -1144,10 +1153,10 @@ module OpenTelemetry
1144
1153
  always_off: h.key?('always_off'),
1145
1154
  always_on: h.key?('always_on'),
1146
1155
  composite_development: ExperimentalComposableSampler.from_hash(h['composite/development']),
1147
- jaeger_remote_development: ExperimentalJaegerRemoteSampler.from_hash(h['jaeger_remote/development']),
1148
- parent_based: ParentBasedSampler.from_hash(h['parent_based']),
1149
- probability_development: ExperimentalProbabilitySampler.from_hash(h['probability/development']),
1150
- trace_id_ratio_based: TraceIdRatioBasedSampler.from_hash(h['trace_id_ratio_based']),
1156
+ jaeger_remote_development: Model.nullable_from_hash(ExperimentalJaegerRemoteSampler, h, 'jaeger_remote/development'),
1157
+ parent_based: Model.nullable_from_hash(ParentBasedSampler, h, 'parent_based'),
1158
+ probability_development: Model.nullable_from_hash(ExperimentalProbabilitySampler, h, 'probability/development'),
1159
+ trace_id_ratio_based: Model.nullable_from_hash(TraceIdRatioBasedSampler, h, 'trace_id_ratio_based'),
1151
1160
  additional_properties: h.except('always_off', 'always_on', 'composite/development', 'jaeger_remote/development', 'parent_based', 'probability/development', 'trace_id_ratio_based')
1152
1161
  )
1153
1162
  end
@@ -1191,9 +1200,9 @@ module OpenTelemetry
1191
1200
  return nil unless h.is_a?(Hash)
1192
1201
 
1193
1202
  new(
1194
- otlp_http: OtlpHttpExporter.from_hash(h['otlp_http']),
1195
- otlp_grpc: OtlpGrpcExporter.from_hash(h['otlp_grpc']),
1196
- otlp_file_development: ExperimentalOtlpFileExporter.from_hash(h['otlp_file/development']),
1203
+ otlp_http: Model.nullable_from_hash(OtlpHttpExporter, h, 'otlp_http'),
1204
+ otlp_grpc: Model.nullable_from_hash(OtlpGrpcExporter, h, 'otlp_grpc'),
1205
+ otlp_file_development: Model.nullable_from_hash(ExperimentalOtlpFileExporter, h, 'otlp_file/development'),
1197
1206
  console: h.key?('console'),
1198
1207
  additional_properties: h.except('otlp_http', 'otlp_grpc', 'otlp_file/development', 'console')
1199
1208
  )
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright The OpenTelemetry Authors
4
+ # SPDX-License-Identifier: Apache-2.0
5
+
6
+ module OpenTelemetry
7
+ module Config
8
+ # Logs component builder for configuring LoggerProvider from declarative config.
9
+ module Logs
10
+ module_function
11
+
12
+ # Builds a LoggerProvider from the parsed YAML logger_provider config.
13
+ # Returns nil if config is nil or opentelemetry-logs-sdk isn't loaded.
14
+ def build_logger_provider(config, resource)
15
+ return unless config
16
+
17
+ unless defined?(OpenTelemetry::SDK::Logs::LoggerProvider)
18
+ OpenTelemetry.logger.warn('logger_provider is configured but opentelemetry-logs-sdk is not loaded. ' \
19
+ 'Add `gem "opentelemetry-logs-sdk"` to your Gemfile.')
20
+ return
21
+ end
22
+
23
+ lp = OpenTelemetry::SDK::Logs::LoggerProvider.new(
24
+ resource: resource,
25
+ log_record_limits: build_log_record_limits(config.limits)
26
+ )
27
+
28
+ Array(config.processors).each do |proc_cfg|
29
+ lp.add_log_record_processor(build_log_record_processor(proc_cfg))
30
+ rescue StandardError => e
31
+ OpenTelemetry.logger.warn("Failed to build log record processor: #{e.message}")
32
+ end
33
+
34
+ lp
35
+ end
36
+
37
+ # Builds a log record processor (simple or batch) from config.
38
+ def build_log_record_processor(proc_cfg)
39
+ raise ArgumentError, 'must not specify multiple log record processor types' if proc_cfg.batch && proc_cfg.simple
40
+
41
+ if proc_cfg.batch
42
+ build_batch_log_record_processor(proc_cfg.batch)
43
+ elsif proc_cfg.simple
44
+ build_simple_log_record_processor(proc_cfg.simple)
45
+ else
46
+ raise ArgumentError, 'unsupported log record processor type, must be one of simple or batch'
47
+ end
48
+ end
49
+
50
+ # Builds a BatchLogRecordProcessor with exporter and optional tuning options.
51
+ def build_batch_log_record_processor(cfg)
52
+ exporter = build_log_record_exporter(cfg.exporter)
53
+ opts = {
54
+ schedule_delay: cfg.schedule_delay&.to_f,
55
+ exporter_timeout: cfg.export_timeout&.to_f,
56
+ max_queue_size: cfg.max_queue_size&.to_i,
57
+ max_export_batch_size: cfg.max_export_batch_size&.to_i
58
+ }.compact
59
+
60
+ OpenTelemetry::SDK::Logs::Export::BatchLogRecordProcessor.new(exporter, **opts)
61
+ end
62
+
63
+ # Builds a SimpleLogRecordProcessor wrapping the configured exporter.
64
+ def build_simple_log_record_processor(cfg)
65
+ OpenTelemetry::SDK::Logs::Export::SimpleLogRecordProcessor.new(build_log_record_exporter(cfg.exporter))
66
+ end
67
+
68
+ # Builds a log record exporter from config; supports console and otlp_http.
69
+ def build_log_record_exporter(exp_cfg)
70
+ raise ArgumentError, 'no exporter config' unless exp_cfg
71
+
72
+ configured = 0
73
+ exporter = nil
74
+
75
+ if exp_cfg.console
76
+ configured += 1
77
+ exporter = OpenTelemetry::SDK::Logs::Export::ConsoleLogRecordExporter.new
78
+ end
79
+
80
+ if exp_cfg.otlp_http
81
+ configured += 1
82
+ exporter = build_otlp_http_log_record_exporter(exp_cfg.otlp_http)
83
+ end
84
+
85
+ raise ArgumentError, 'must not specify multiple exporters' if configured > 1
86
+ raise ArgumentError, 'no valid log record exporter' if exporter.nil?
87
+
88
+ exporter
89
+ end
90
+
91
+ # Builds an OTLP HTTP log record exporter from the given endpoint, headers, and TLS config.
92
+ def build_otlp_http_log_record_exporter(cfg)
93
+ unless defined?(OpenTelemetry::Exporter::OTLP::Logs::LogsExporter)
94
+ raise ArgumentError, 'otlp_http requires opentelemetry-exporter-otlp-logs. ' \
95
+ 'Add `gem "opentelemetry-exporter-otlp-logs"` to your Gemfile.'
96
+ end
97
+
98
+ headers = Trace.headers_to_hash(cfg)
99
+ opts = {
100
+ endpoint: cfg.endpoint,
101
+ certificate_file: cfg.tls&.ca_file,
102
+ client_certificate_file: cfg.tls&.cert_file,
103
+ client_key_file: cfg.tls&.key_file,
104
+ headers: headers.empty? ? nil : headers,
105
+ compression: cfg.compression,
106
+ timeout: cfg.timeout && (cfg.timeout / 1000.0)
107
+ }.compact
108
+
109
+ OpenTelemetry::Exporter::OTLP::Logs::LogsExporter.new(**opts)
110
+ end
111
+
112
+ # Builds LogRecordLimits from config; returns the SDK default when config is nil.
113
+ def build_log_record_limits(limits_cfg)
114
+ return OpenTelemetry::SDK::Logs::LogRecordLimits::DEFAULT unless limits_cfg
115
+
116
+ opts = {
117
+ attribute_count_limit: limits_cfg.attribute_count_limit,
118
+ attribute_length_limit: limits_cfg.attribute_value_length_limit
119
+ }.compact
120
+
121
+ OpenTelemetry::SDK::Logs::LogRecordLimits.new(**opts)
122
+ end
123
+ end
124
+
125
+ private_constant :Logs
126
+ end
127
+ end
@@ -11,7 +11,7 @@ module OpenTelemetry
11
11
 
12
12
  # Priority: attributes > attribute_list > detected > base
13
13
  def build_resource(resource_cfg)
14
- base = OpenTelemetry::SDK::Resources::Resource.default
14
+ base = default_resource
15
15
 
16
16
  return base unless resource_cfg
17
17
 
@@ -31,13 +31,27 @@ module OpenTelemetry
31
31
  end
32
32
  end
33
33
 
34
- OpenTelemetry.logger.warn('Config: schema_url is supported; ignoring.') if resource_cfg.schema_url
34
+ OpenTelemetry.logger.warn('Config: schema_url is not supported; ignoring.') if resource_cfg.schema_url
35
35
 
36
36
  attrs = detected.merge(explicit)
37
37
  custom = OpenTelemetry::SDK::Resources::Resource.create(attrs)
38
38
  base.merge(custom)
39
39
  end
40
40
 
41
+ # Builds the intrinsic SDK resource without consulting OTEL_* environment
42
+ # variables. File-based configuration and environment configuration are
43
+ # intentionally separate sources.
44
+ def default_resource
45
+ resource_class = OpenTelemetry::SDK::Resources::Resource
46
+
47
+ resource_class.create(
48
+ OpenTelemetry::SemanticConventions::Resource::SERVICE_NAME => 'unknown_service',
49
+ OpenTelemetry::SemanticConventions::Resource::TELEMETRY_SDK_NAME => 'opentelemetry',
50
+ OpenTelemetry::SemanticConventions::Resource::TELEMETRY_SDK_LANGUAGE => 'ruby',
51
+ OpenTelemetry::SemanticConventions::Resource::TELEMETRY_SDK_VERSION => OpenTelemetry::SDK::VERSION
52
+ ).merge(resource_class.process)
53
+ end
54
+
41
55
  # type coercion
42
56
  def coerce_attribute_value(value, type)
43
57
  case type
@@ -3,6 +3,8 @@
3
3
  # Copyright The OpenTelemetry Authors
4
4
  # SPDX-License-Identifier: Apache-2.0
5
5
 
6
+ require 'openssl'
7
+
6
8
  module OpenTelemetry
7
9
  module Config
8
10
  # Trace component builder for configuring TracerProvider from declarative config.
@@ -46,15 +48,17 @@ module OpenTelemetry
46
48
  end
47
49
  end
48
50
 
49
- # Builds a BatchSpanProcessor with exporter and optional tuning options.
51
+ # Builds a BatchSpanProcessor with exporter and schema defaults. Passing
52
+ # environment-backed options explicitly prevents it from reading OTEL_*.
50
53
  def build_batch_span_processor(cfg)
51
54
  exporter = build_span_exporter(cfg.exporter)
52
55
  opts = {
53
- schedule_delay: cfg.schedule_delay&.to_f,
54
- exporter_timeout: cfg.export_timeout&.to_f,
55
- max_queue_size: cfg.max_queue_size&.to_i,
56
- max_export_batch_size: cfg.max_export_batch_size&.to_i
57
- }.compact
56
+ schedule_delay: (cfg.schedule_delay || Defaults::BATCH_SPAN_PROCESSOR[:schedule_delay]).to_f,
57
+ exporter_timeout: (cfg.export_timeout || Defaults::BATCH_SPAN_PROCESSOR[:export_timeout]).to_f,
58
+ max_queue_size: (cfg.max_queue_size || Defaults::BATCH_SPAN_PROCESSOR[:max_queue_size]).to_i,
59
+ max_export_batch_size: (cfg.max_export_batch_size || Defaults::BATCH_SPAN_PROCESSOR[:max_export_batch_size]).to_i,
60
+ start_thread_on_boot: true
61
+ }
58
62
 
59
63
  OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter, **opts)
60
64
  end
@@ -88,17 +92,19 @@ module OpenTelemetry
88
92
  exporter
89
93
  end
90
94
 
91
- # Builds an OTLP HTTP span exporter from the given endpoint/headers config.
95
+ # Builds an OTLP HTTP span exporter from declarative values and schema
96
+ # defaults, without falling back to exporter environment variables.
92
97
  def build_otlp_http_span_exporter(cfg)
93
- headers = headers_to_hash(cfg)
94
- opts = {
95
- endpoint: cfg.endpoint,
96
- headers: headers.empty? ? nil : headers,
97
- compression: cfg.compression,
98
- timeout: cfg.timeout && (cfg.timeout / 1000.0) # YAML ms → Ruby seconds
99
- }.compact
100
-
101
- OpenTelemetry::Exporter::OTLP::Exporter.new(**opts)
98
+ OpenTelemetry::Exporter::OTLP::Exporter.new(
99
+ endpoint: cfg.endpoint || Defaults::OTLP_HTTP_TRACES_ENDPOINT,
100
+ certificate_file: cfg.tls&.ca_file,
101
+ client_certificate_file: cfg.tls&.cert_file,
102
+ client_key_file: cfg.tls&.key_file,
103
+ ssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
104
+ headers: headers_to_hash(cfg),
105
+ compression: cfg.compression || Defaults::OTLP_HTTP_EXPORTER[:compression],
106
+ timeout: (cfg.timeout || Defaults::OTLP_HTTP_EXPORTER[:timeout]) / 1000.0
107
+ )
102
108
  end
103
109
 
104
110
  # Builds a sampler from config; defaults to ParentBased(ALWAYS_ON).
@@ -139,20 +145,17 @@ module OpenTelemetry
139
145
  s.parent_based(**opts)
140
146
  end
141
147
 
142
- # Builds SpanLimits from config; returns the SDK default when config is nil.
148
+ # Builds SpanLimits from declarative values and schema defaults.
143
149
  def build_span_limits(limits_cfg)
144
- return OpenTelemetry::SDK::Trace::SpanLimits::DEFAULT unless limits_cfg
145
-
146
- opts = {
147
- attribute_count_limit: limits_cfg.attribute_count_limit,
148
- attribute_length_limit: limits_cfg.attribute_value_length_limit,
149
- event_count_limit: limits_cfg.event_count_limit,
150
- link_count_limit: limits_cfg.link_count_limit,
151
- event_attribute_count_limit: limits_cfg.event_attribute_count_limit,
152
- link_attribute_count_limit: limits_cfg.link_attribute_count_limit
153
- }.compact
154
-
155
- OpenTelemetry::SDK::Trace::SpanLimits.new(**opts)
150
+ OpenTelemetry::SDK::Trace::SpanLimits.new(
151
+ attribute_count_limit: limits_cfg&.attribute_count_limit || Defaults::SPAN_LIMITS[:attribute_count_limit],
152
+ attribute_length_limit: limits_cfg&.attribute_value_length_limit || Defaults::SPAN_LIMITS[:attribute_value_length_limit],
153
+ event_count_limit: limits_cfg&.event_count_limit || Defaults::SPAN_LIMITS[:event_count_limit],
154
+ link_count_limit: limits_cfg&.link_count_limit || Defaults::SPAN_LIMITS[:link_count_limit],
155
+ event_attribute_count_limit: limits_cfg&.event_attribute_count_limit || Defaults::SPAN_LIMITS[:event_attribute_count_limit],
156
+ event_attribute_length_limit: nil,
157
+ link_attribute_count_limit: limits_cfg&.link_attribute_count_limit || Defaults::SPAN_LIMITS[:link_attribute_count_limit]
158
+ )
156
159
  end
157
160
 
158
161
  # Converts the OTLP exporter headers (array of NameStringValuePair structs)
@@ -5,6 +5,6 @@
5
5
 
6
6
  module OpenTelemetry
7
7
  module Config
8
- VERSION = '0.1.0'
8
+ VERSION = '0.2.0'
9
9
  end
10
10
  end
@@ -6,6 +6,9 @@
6
6
  require 'date'
7
7
  require 'yaml'
8
8
 
9
+ require_relative 'config/defaults'
10
+ require_relative 'config/env_substitution'
11
+ require_relative 'config/logs'
9
12
  require_relative 'config/propagation'
10
13
  require_relative 'config/resource'
11
14
  require_relative 'config/constants'
@@ -47,13 +50,16 @@ module OpenTelemetry
47
50
  end
48
51
 
49
52
  content = File.read(path)
50
- OpenTelemetry::Config::Model::OpenTelemetryConfiguration.from_hash(YAML.safe_load(content, permitted_classes: [Date, Time]))
53
+ OpenTelemetry::Config::Model::OpenTelemetryConfiguration.from_hash(EnvSubstitution.load(content))
51
54
  rescue Errno::ENOENT => e
52
55
  OpenTelemetry.logger.error("Config file not found: #{e.message}")
53
56
  nil
54
57
  rescue Psych::SyntaxError => e
55
58
  OpenTelemetry.logger.error("YAML parse error: #{e.message}")
56
59
  nil
60
+ rescue EnvSubstitution::Error => e
61
+ OpenTelemetry.logger.error("Config file substitution error: #{e.message}")
62
+ nil
57
63
  end
58
64
 
59
65
  # Creates the components described by a configuration model without
@@ -80,11 +86,13 @@ module OpenTelemetry
80
86
 
81
87
  # tracer_provider will be noop if opentelemetry-sdk is not installed
82
88
  tracer_provider = Trace.build_tracer_provider(config.tracer_provider, resource)
89
+ logger_provider = Logs.build_logger_provider(config.logger_provider, resource)
83
90
 
84
91
  propagators = configure_propagation(config.propagator)
85
92
 
86
93
  RubySDK.new(
87
94
  tracer_provider: tracer_provider,
95
+ logger_provider: logger_provider,
88
96
  propagator: propagators,
89
97
  resource: resource
90
98
  )
@@ -99,9 +107,21 @@ module OpenTelemetry
99
107
  return ruby_sdk if ruby_sdk.equal?(NOOP_SDK)
100
108
 
101
109
  OpenTelemetry.tracer_provider = ruby_sdk.tracer_provider if ruby_sdk.tracer_provider
110
+ install_logger_provider(ruby_sdk.logger_provider) if ruby_sdk.logger_provider
102
111
  OpenTelemetry.propagation = ruby_sdk.propagator if ruby_sdk.propagator
103
112
  ruby_sdk
104
113
  end
114
+
115
+ private
116
+
117
+ def install_logger_provider(provider)
118
+ if OpenTelemetry.respond_to?(:logger_provider=)
119
+ OpenTelemetry.logger_provider = provider
120
+ else
121
+ OpenTelemetry.logger.warn('Cannot install the configured logger provider: OpenTelemetry.logger_provider= ' \
122
+ 'is not defined. Require opentelemetry-logs-sdk.')
123
+ end
124
+ end
105
125
  end
106
126
  end
107
127
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opentelemetry-config
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - OpenTelemetry Authors
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: 0.34.1
46
+ version: '0.34'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: 0.34.1
53
+ version: '0.34'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: opentelemetry-sdk
56
56
  requirement: !ruby/object:Gem::Requirement
@@ -78,7 +78,10 @@ files:
78
78
  - lib/opentelemetry-config.rb
79
79
  - lib/opentelemetry/config.rb
80
80
  - lib/opentelemetry/config/constants.rb
81
+ - lib/opentelemetry/config/defaults.rb
82
+ - lib/opentelemetry/config/env_substitution.rb
81
83
  - lib/opentelemetry/config/generated_constants.rb
84
+ - lib/opentelemetry/config/logs.rb
82
85
  - lib/opentelemetry/config/propagation.rb
83
86
  - lib/opentelemetry/config/resource.rb
84
87
  - lib/opentelemetry/config/trace.rb
@@ -87,10 +90,10 @@ homepage: https://github.com/open-telemetry/opentelemetry-ruby
87
90
  licenses:
88
91
  - Apache-2.0
89
92
  metadata:
90
- changelog_uri: https://rubydoc.info/gems/opentelemetry-config/0.1.0/file/CHANGELOG.md
91
- source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-config/v0.1.0/config
93
+ changelog_uri: https://rubydoc.info/gems/opentelemetry-config/0.2.0/file/CHANGELOG.md
94
+ source_code_uri: https://github.com/open-telemetry/opentelemetry-ruby/tree/opentelemetry-config/v0.2.0/config
92
95
  bug_tracker_uri: https://github.com/open-telemetry/opentelemetry-ruby/issues
93
- documentation_uri: https://rubydoc.info/gems/opentelemetry-config/0.1.0
96
+ documentation_uri: https://rubydoc.info/gems/opentelemetry-config/0.2.0
94
97
  rdoc_options: []
95
98
  require_paths:
96
99
  - lib