You've already forked opentelemetry-go
							
							
				mirror of
				https://github.com/open-telemetry/opentelemetry-go.git
				synced 2025-10-31 00:07:40 +02:00 
			
		
		
		
	Support OTEL_METRIC_EXPORT_INTERVAL and OTEL_METRIC_EXPORT_TIMEOUT (#3763)
* Support OTEL_METRIC_EXPORT_INTERVAL and OTEL_METRIC_EXPORT_TIMEOUT * Fix non-positive duration error log Co-authored-by: Tyler Yahn <MrAlias@users.noreply.github.com>
This commit is contained in:
		| @@ -33,6 +33,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm | ||||
| - Add `bridgetSpanContext.IsSampled` to `go.opentelemetry.io/otel/bridget/opentracing` to expose whether span is sampled or not. (#3570) | ||||
| - The `WithInstrumentationAttributes` option to `go.opentelemetry.io/otel/metric`. (#3738) | ||||
| - The `WithInstrumentationAttributes` option to `go.opentelemetry.io/otel/trace`. (#3739) | ||||
| - The following environment variables are supported by the `Reader`s in `go.opentelemetry.io/otel/sdk/metric`. (#3763) | ||||
|   - `OTEL_METRIC_EXPORT_INTERVAL` | ||||
|   - `OTEL_METRIC_EXPORT_TIMEOUT` | ||||
|  | ||||
| ### Changed | ||||
|  | ||||
|   | ||||
							
								
								
									
										50
									
								
								sdk/metric/env.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								sdk/metric/env.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| // Copyright The OpenTelemetry Authors | ||||
| // | ||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| // you may not use this file except in compliance with the License. | ||||
| // You may obtain a copy of the License at | ||||
| // | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0 | ||||
| // | ||||
| // Unless required by applicable law or agreed to in writing, software | ||||
| // distributed under the License is distributed on an "AS IS" BASIS, | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | ||||
|  | ||||
| package metric // import "go.opentelemetry.io/otel/sdk/metric" | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"go.opentelemetry.io/otel/internal/global" | ||||
| ) | ||||
|  | ||||
| // Environment variable names. | ||||
| const ( | ||||
| 	// The time interval (in milliseconds) between the start of two export attempts. | ||||
| 	envInterval = "OTEL_METRIC_EXPORT_INTERVAL" | ||||
| 	// Maximum allowed time (in milliseconds) to export data. | ||||
| 	envTimeout = "OTEL_METRIC_EXPORT_TIMEOUT" | ||||
| ) | ||||
|  | ||||
| // envDuration returns an environment variable's value as duration in milliseconds if it is exists, | ||||
| // or the defaultValue if the environment variable is not defined or the value is not valid. | ||||
| func envDuration(key string, defaultValue time.Duration) time.Duration { | ||||
| 	v := os.Getenv(key) | ||||
| 	if v == "" { | ||||
| 		return defaultValue | ||||
| 	} | ||||
| 	d, err := strconv.Atoi(v) | ||||
| 	if err != nil { | ||||
| 		global.Error(err, "parse duration", "environment variable", key, "value", v) | ||||
| 		return defaultValue | ||||
| 	} | ||||
| 	if d <= 0 { | ||||
| 		global.Error(errNonPositiveDuration, "non-positive duration", "environment variable", key, "value", v) | ||||
| 		return defaultValue | ||||
| 	} | ||||
| 	return time.Duration(d) * time.Millisecond | ||||
| } | ||||
| @@ -44,8 +44,8 @@ type periodicReaderConfig struct { | ||||
| // options. | ||||
| func newPeriodicReaderConfig(options []PeriodicReaderOption) periodicReaderConfig { | ||||
| 	c := periodicReaderConfig{ | ||||
| 		interval: defaultInterval, | ||||
| 		timeout:  defaultTimeout, | ||||
| 		interval: envDuration(envInterval, defaultInterval), | ||||
| 		timeout:  envDuration(envTimeout, defaultTimeout), | ||||
| 	} | ||||
| 	for _, o := range options { | ||||
| 		c = o.applyPeriodic(c) | ||||
| @@ -69,6 +69,9 @@ func (o periodicReaderOptionFunc) applyPeriodic(conf periodicReaderConfig) perio | ||||
| // WithTimeout configures the time a PeriodicReader waits for an export to | ||||
| // complete before canceling it. | ||||
| // | ||||
| // This option overrides any value set for the | ||||
| // OTEL_METRIC_EXPORT_TIMEOUT environment variable. | ||||
| // | ||||
| // If this option is not used or d is less than or equal to zero, 30 seconds | ||||
| // is used as the default. | ||||
| func WithTimeout(d time.Duration) PeriodicReaderOption { | ||||
| @@ -84,6 +87,9 @@ func WithTimeout(d time.Duration) PeriodicReaderOption { | ||||
| // WithInterval configures the intervening time between exports for a | ||||
| // PeriodicReader. | ||||
| // | ||||
| // This option overrides any value set for the | ||||
| // OTEL_METRIC_EXPORT_INTERVAL environment variable. | ||||
| // | ||||
| // If this option is not used or d is less than or equal to zero, 60 seconds | ||||
| // is used as the default. | ||||
| func WithInterval(d time.Duration) PeriodicReaderOption { | ||||
|   | ||||
| @@ -41,6 +41,54 @@ func TestWithTimeout(t *testing.T) { | ||||
| 	assert.Equal(t, defaultTimeout, test(time.Duration(-1)), "invalid timeout should use default") | ||||
| } | ||||
|  | ||||
| func TestTimeoutEnvVar(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		v    string | ||||
| 		want time.Duration | ||||
| 	}{ | ||||
| 		{ | ||||
| 			// empty value | ||||
| 			"", | ||||
| 			defaultTimeout, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// positive value | ||||
| 			"1", | ||||
| 			time.Millisecond, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// non-positive value | ||||
| 			"0", | ||||
| 			defaultTimeout, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// value with unit (not supported) | ||||
| 			"1ms", | ||||
| 			defaultTimeout, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// NaN | ||||
| 			"abc", | ||||
| 			defaultTimeout, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.v, func(t *testing.T) { | ||||
| 			t.Setenv(envTimeout, tc.v) | ||||
| 			got := newPeriodicReaderConfig(nil).timeout | ||||
| 			assert.Equal(t, tc.want, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTimeoutEnvAndOption(t *testing.T) { | ||||
| 	want := 5 * time.Millisecond | ||||
| 	t.Setenv(envTimeout, "999") | ||||
| 	opts := []PeriodicReaderOption{WithTimeout(want)} | ||||
| 	got := newPeriodicReaderConfig(opts).timeout | ||||
| 	assert.Equal(t, want, got, "option should have precedence over env var") | ||||
| } | ||||
|  | ||||
| func TestWithInterval(t *testing.T) { | ||||
| 	test := func(d time.Duration) time.Duration { | ||||
| 		opts := []PeriodicReaderOption{WithInterval(d)} | ||||
| @@ -53,6 +101,54 @@ func TestWithInterval(t *testing.T) { | ||||
| 	assert.Equal(t, defaultInterval, test(time.Duration(-1)), "invalid interval should use default") | ||||
| } | ||||
|  | ||||
| func TestIntervalEnvVar(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		v    string | ||||
| 		want time.Duration | ||||
| 	}{ | ||||
| 		{ | ||||
| 			// empty value | ||||
| 			"", | ||||
| 			defaultInterval, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// positive value | ||||
| 			"1", | ||||
| 			time.Millisecond, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// non-positive value | ||||
| 			"0", | ||||
| 			defaultInterval, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// value with unit (not supported) | ||||
| 			"1ms", | ||||
| 			defaultInterval, | ||||
| 		}, | ||||
| 		{ | ||||
| 			// NaN | ||||
| 			"abc", | ||||
| 			defaultInterval, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range testCases { | ||||
| 		t.Run(tc.v, func(t *testing.T) { | ||||
| 			t.Setenv(envInterval, tc.v) | ||||
| 			got := newPeriodicReaderConfig(nil).interval | ||||
| 			assert.Equal(t, tc.want, got) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestIntervalEnvAndOption(t *testing.T) { | ||||
| 	want := 5 * time.Millisecond | ||||
| 	t.Setenv(envInterval, "999") | ||||
| 	opts := []PeriodicReaderOption{WithInterval(want)} | ||||
| 	got := newPeriodicReaderConfig(opts).interval | ||||
| 	assert.Equal(t, want, got, "option should have precedence over env var") | ||||
| } | ||||
|  | ||||
| type fnExporter struct { | ||||
| 	temporalityFunc TemporalitySelector | ||||
| 	aggregationFunc AggregationSelector | ||||
|   | ||||
| @@ -34,6 +34,10 @@ var ErrReaderNotRegistered = fmt.Errorf("reader is not registered") | ||||
| // reader has been Shutdown once. | ||||
| var ErrReaderShutdown = fmt.Errorf("reader is shutdown") | ||||
|  | ||||
| // errNonPositiveDuration is logged when an environmental variable | ||||
| // has non-positive value. | ||||
| var errNonPositiveDuration = fmt.Errorf("non-positive duration") | ||||
|  | ||||
| // Reader is the interface used between the SDK and an | ||||
| // exporter.  Control flow is bi-directional through the | ||||
| // Reader, since the SDK initiates ForceFlush and Shutdown | ||||
|   | ||||
		Reference in New Issue
	
	Block a user