diff --git a/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs b/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs index 6cc238b8c23..e3a2c312072 100644 --- a/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs +++ b/src/OpenTelemetry.Api/Internal/ActivityHelperExtensions.cs @@ -44,7 +44,7 @@ public static bool TryGetStatus(this Activity activity, out StatusCode statusCod ActivityStatusTagEnumerator state = default; - ActivityTagsEnumeratorFactory.Enumerate(activity, ref state); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref state, null); if (!state.StatusCode.HasValue) { @@ -72,7 +72,7 @@ public static object GetTagValue(this Activity activity, string tagName) ActivitySingleTagEnumerator state = new ActivitySingleTagEnumerator(tagName); - ActivityTagsEnumeratorFactory.Enumerate(activity, ref state); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref state, null); return state.Value; } @@ -91,7 +91,7 @@ public static bool TryCheckFirstTag(this Activity activity, string tagName, out ActivityFirstTagEnumerator state = new ActivityFirstTagEnumerator(tagName); - ActivityTagsEnumeratorFactory.Enumerate(activity, ref state); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref state, null); if (state.Value == null) { @@ -109,14 +109,15 @@ public static bool TryCheckFirstTag(this Activity activity, string tagName, out /// The struct implementation to use for the enumeration. /// Activity instance. /// Tag enumerator. + /// Maximum number of tags to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateTags(this Activity activity, ref T tagEnumerator) + public static void EnumerateTags(this Activity activity, ref T tagEnumerator, int? maxTags = null) where T : struct, IActivityEnumerator> { Debug.Assert(activity != null, "Activity should not be null"); - ActivityTagsEnumeratorFactory.Enumerate(activity, ref tagEnumerator); + ActivityTagsEnumeratorFactory.Enumerate(activity, ref tagEnumerator, maxTags); } /// @@ -125,14 +126,15 @@ public static void EnumerateTags(this Activity activity, ref T tagEnumerator) /// The struct implementation to use for the enumeration. /// Activity instance. /// Link enumerator. + /// Maximum number of links to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateLinks(this Activity activity, ref T linkEnumerator) + public static void EnumerateLinks(this Activity activity, ref T linkEnumerator, int? maxLinks = null) where T : struct, IActivityEnumerator { Debug.Assert(activity != null, "Activity should not be null"); - ActivityLinksEnumeratorFactory.Enumerate(activity, ref linkEnumerator); + ActivityLinksEnumeratorFactory.Enumerate(activity, ref linkEnumerator, maxLinks); } /// @@ -141,12 +143,13 @@ public static void EnumerateLinks(this Activity activity, ref T linkEnumerato /// The struct implementation to use for the enumeration. /// ActivityLink instance. /// Tag enumerator. + /// Maximum number of tags to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateTags(this ActivityLink activityLink, ref T tagEnumerator) + public static void EnumerateTags(this ActivityLink activityLink, ref T tagEnumerator, int? maxTags = null) where T : struct, IActivityEnumerator> { - ActivityTagsEnumeratorFactory.Enumerate(activityLink, ref tagEnumerator); + ActivityTagsEnumeratorFactory.Enumerate(activityLink, ref tagEnumerator, maxTags); } /// @@ -155,14 +158,15 @@ public static void EnumerateTags(this ActivityLink activityLink, ref T tagEnu /// The struct implementation to use for the enumeration. /// Activity instance. /// Event enumerator. + /// Maximum number of events to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateEvents(this Activity activity, ref T eventEnumerator) + public static void EnumerateEvents(this Activity activity, ref T eventEnumerator, int? maxEvents = null) where T : struct, IActivityEnumerator { Debug.Assert(activity != null, "Activity should not be null"); - ActivityEventsEnumeratorFactory.Enumerate(activity, ref eventEnumerator); + ActivityEventsEnumeratorFactory.Enumerate(activity, ref eventEnumerator, maxEvents); } /// @@ -171,12 +175,13 @@ public static void EnumerateEvents(this Activity activity, ref T eventEnumera /// The struct implementation to use for the enumeration. /// ActivityEvent instance. /// Tag enumerator. + /// Maximum number of tags to enumerate. [MethodImpl(MethodImplOptions.AggressiveInlining)] [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "ActivityProcessor is hot path")] - public static void EnumerateTags(this ActivityEvent activityEvent, ref T tagEnumerator) + public static void EnumerateTags(this ActivityEvent activityEvent, ref T tagEnumerator, int? maxTags = null) where T : struct, IActivityEnumerator> { - ActivityTagsEnumeratorFactory.Enumerate(activityEvent, ref tagEnumerator); + ActivityTagsEnumeratorFactory.Enumerate(activityEvent, ref tagEnumerator, maxTags); } private struct ActivitySingleTagEnumerator : IActivityEnumerator> @@ -265,7 +270,7 @@ private static readonly DictionaryEnumerator.AllocationF private static readonly DictionaryEnumerator.ForEachDelegate ForEachTagValueCallbackRef = ForEachTagValueCallback; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(Activity activity, ref TState state) + public static void Enumerate(Activity activity, ref TState state, int? maxTags) { var tagObjects = activity.TagObjects; @@ -274,6 +279,12 @@ public static void Enumerate(Activity activity, ref TState state) return; } + if (maxTags.HasValue) + { + SkipAllocationFreeEnumeration(tagObjects, ref state, maxTags.Value); + return; + } + ActivityTagObjectsEnumerator( tagObjects, ref state, @@ -281,7 +292,7 @@ public static void Enumerate(Activity activity, ref TState state) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(ActivityLink activityLink, ref TState state) + public static void Enumerate(ActivityLink activityLink, ref TState state, int? maxTags) { var tags = activityLink.Tags; @@ -290,6 +301,12 @@ public static void Enumerate(ActivityLink activityLink, ref TState state) return; } + if (maxTags.HasValue) + { + SkipAllocationFreeEnumeration(tags, ref state, maxTags.Value); + return; + } + ActivityTagsCollectionEnumerator( tags, ref state, @@ -297,7 +314,7 @@ public static void Enumerate(ActivityLink activityLink, ref TState state) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(ActivityEvent activityEvent, ref TState state) + public static void Enumerate(ActivityEvent activityEvent, ref TState state, int? maxTags) { var tags = activityEvent.Tags; @@ -306,12 +323,31 @@ public static void Enumerate(ActivityEvent activityEvent, ref TState state) return; } + if (maxTags.HasValue) + { + SkipAllocationFreeEnumeration(tags, ref state, maxTags.Value); + return; + } + ActivityTagsCollectionEnumerator( tags, ref state, ForEachTagValueCallbackRef); } + // TODO: When a limit has been configured an allocation-free enumerator is not used. + // Need to either: + // 1) modify the dynamically generated code to only enumerate up to the max number of items, or + // 2) wait until .NET 7 is released and do this more easily with the new enumerator functions + private static void SkipAllocationFreeEnumeration(IEnumerable> tags, ref TState state, int maxTags) + { + var enumerator = tags.GetEnumerator(); + for (var i = 0; enumerator.MoveNext() && i < maxTags; ++i) + { + state.ForEach(enumerator.Current); + } + } + private static bool ForEachTagValueCallback(ref TState state, KeyValuePair item) => state.ForEach(item); } @@ -328,7 +364,7 @@ private static readonly ListEnumerator.AllocationFreeForEa private static readonly ListEnumerator.ForEachDelegate ForEachLinkCallbackRef = ForEachLinkCallback; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(Activity activity, ref TState state) + public static void Enumerate(Activity activity, ref TState state, int? maxLinks) { var activityLinks = activity.Links; @@ -337,6 +373,21 @@ public static void Enumerate(Activity activity, ref TState state) return; } + // TODO: When a limit has been configured an allocation-free enumerator is not used. + // Need to either: + // 1) modify the dynamically generated code to only enumerate up to the max number of items, or + // 2) wait until .NET 7 is released and do this more easily with the new enumerator functions + if (maxLinks.HasValue) + { + var enumerator = activityLinks.GetEnumerator(); + for (var i = 0; enumerator.MoveNext() && i < maxLinks; ++i) + { + state.ForEach(enumerator.Current); + } + + return; + } + ActivityLinksEnumerator( activityLinks, ref state, @@ -359,7 +410,7 @@ private static readonly ListEnumerator.AllocationFreeForE private static readonly ListEnumerator.ForEachDelegate ForEachEventCallbackRef = ForEachEventCallback; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Enumerate(Activity activity, ref TState state) + public static void Enumerate(Activity activity, ref TState state, int? maxEvents) { var activityEvents = activity.Events; @@ -368,6 +419,21 @@ public static void Enumerate(Activity activity, ref TState state) return; } + // TODO: When a limit has been configured an allocation-free enumerator is not used. + // Need to either: + // 1) modify the dynamically generated code to only enumerate up to the max number of items, or + // 2) wait until .NET 7 is released and do this more easily with the new enumerator functions + if (maxEvents.HasValue) + { + var enumerator = activityEvents.GetEnumerator(); + for (var i = 0; enumerator.MoveNext() && i < maxEvents; ++i) + { + state.ForEach(enumerator.Current); + } + + return; + } + ActivityEventsEnumerator( activityEvents, ref state, diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md index 6199a199208..1a4ae40ade9 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +* Adds support for limiting the length and count of attributes exported from + the OTLP exporter. These + [Attribute Limits](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits) + are configured via the environment variables defined in the specification. + ([#3376](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3376)) + * The `MetricReaderOptions` defaults can be overridden using `OTEL_METRIC_EXPORT_INTERVAL` and `OTEL_METRIC_EXPORT_TIMEOUT` environmental variables as defined in the diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/EnvironmentVariableConfiguration.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/EnvironmentVariableConfiguration.cs new file mode 100644 index 00000000000..dd6cbc7ee9e --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/EnvironmentVariableConfiguration.cs @@ -0,0 +1,46 @@ +// +// 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. +// + +using System; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Configuration; + +internal class EnvironmentVariableConfiguration +{ + public static void InitializeDefaultConfigurationFromEnvironment(SdkConfiguration sdkConfiguration) + { + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#attribute-limits + SetIntConfigValue("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", value => sdkConfiguration.AttributeValueLengthLimit = value); + SetIntConfigValue("OTEL_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.AttributeCountLimit = value); + + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md#span-limits + SetIntConfigValue("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", value => sdkConfiguration.SpanAttributeValueLengthLimit = value); + SetIntConfigValue("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.SpanAttributeCountLimit = value); + SetIntConfigValue("OTEL_SPAN_EVENT_COUNT_LIMIT", value => sdkConfiguration.SpanEventCountLimit = value); + SetIntConfigValue("OTEL_SPAN_LINK_COUNT_LIMIT", value => sdkConfiguration.SpanLinkCountLimit = value); + SetIntConfigValue("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.EventAttributeCountLimit = value); + SetIntConfigValue("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", value => sdkConfiguration.LinkAttributeCountLimit = value); + } + + private static void SetIntConfigValue(string key, Action setter) + { + if (EnvironmentVariableHelper.LoadNumeric(key, out var result)) + { + setter(result); + } + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/SdkConfiguration.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/SdkConfiguration.cs new file mode 100644 index 00000000000..a7772bcd532 --- /dev/null +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Configuration/SdkConfiguration.cs @@ -0,0 +1,69 @@ +// +// 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. +// + +namespace OpenTelemetry.Configuration; + +internal class SdkConfiguration +{ + private int? spanAttributeValueLengthLimit; + private int? spanAttributeCountLimit; + private int? eventAttributeCountLimit; + private int? linkAttributeCountLimit; + + private SdkConfiguration() + { + EnvironmentVariableConfiguration.InitializeDefaultConfigurationFromEnvironment(this); + } + + public static SdkConfiguration Instance { get; private set; } = new SdkConfiguration(); + + public int? AttributeValueLengthLimit { get; set; } + + public int? AttributeCountLimit { get; set; } + + public int? SpanAttributeValueLengthLimit + { + get => this.spanAttributeValueLengthLimit ?? this.AttributeValueLengthLimit; + set => this.spanAttributeValueLengthLimit = value; + } + + public int? SpanAttributeCountLimit + { + get => this.spanAttributeCountLimit ?? this.AttributeCountLimit; + set => this.spanAttributeCountLimit = value; + } + + public int? SpanEventCountLimit { get; set; } + + public int? SpanLinkCountLimit { get; set; } + + public int? EventAttributeCountLimit + { + get => this.eventAttributeCountLimit ?? this.SpanAttributeCountLimit; + set => this.eventAttributeCountLimit = value; + } + + public int? LinkAttributeCountLimit + { + get => this.linkAttributeCountLimit ?? this.SpanAttributeCountLimit; + set => this.linkAttributeCountLimit = value; + } + + internal static void Reset() + { + Instance = new SdkConfiguration(); + } +} diff --git a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs index 6e62e19e805..f1e36021513 100644 --- a/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs +++ b/src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/ActivityExtensions.cs @@ -24,6 +24,7 @@ using System.Runtime.CompilerServices; using Google.Protobuf; using Google.Protobuf.Collections; +using OpenTelemetry.Configuration; using OpenTelemetry.Internal; using OpenTelemetry.Trace; using OtlpCollector = Opentelemetry.Proto.Collector.Trace.V1; @@ -154,7 +155,7 @@ internal static OtlpTrace.Span ToOtlpSpan(this Activity activity) }; TagEnumerationState otlpTags = default; - activity.EnumerateTags(ref otlpTags); + activity.EnumerateTags(ref otlpTags, SdkConfiguration.Instance.SpanAttributeCountLimit); if (activity.Kind == ActivityKind.Client || activity.Kind == ActivityKind.Producer) { @@ -181,7 +182,7 @@ internal static OtlpTrace.Span ToOtlpSpan(this Activity activity) otlpSpan.Status = activity.ToOtlpStatus(ref otlpTags); EventEnumerationState otlpEvents = default; - activity.EnumerateEvents(ref otlpEvents); + activity.EnumerateEvents(ref otlpEvents, SdkConfiguration.Instance.SpanEventCountLimit); if (otlpEvents.Created) { otlpSpan.Events.AddRange(otlpEvents.Events); @@ -189,13 +190,14 @@ internal static OtlpTrace.Span ToOtlpSpan(this Activity activity) } LinkEnumerationState otlpLinks = default; - activity.EnumerateLinks(ref otlpLinks); + activity.EnumerateLinks(ref otlpLinks, SdkConfiguration.Instance.SpanLinkCountLimit); if (otlpLinks.Created) { otlpSpan.Links.AddRange(otlpLinks.Links); otlpLinks.Links.Return(); } + // TODO: The drop counts should be set when necessary. // Activity does not limit number of attributes, events, links, etc so drop counts are always zero. return otlpSpan; @@ -259,7 +261,7 @@ private static OtlpTrace.Span.Types.Link ToOtlpLink(ActivityLink activityLink) }; TagEnumerationState otlpTags = default; - activityLink.EnumerateTags(ref otlpTags); + activityLink.EnumerateTags(ref otlpTags, SdkConfiguration.Instance.LinkAttributeCountLimit); if (otlpTags.Created) { otlpLink.Attributes.AddRange(otlpTags.Tags); @@ -279,7 +281,7 @@ private static OtlpTrace.Span.Types.Event ToOtlpEvent(ActivityEvent activityEven }; TagEnumerationState otlpTags = default; - activityEvent.EnumerateTags(ref otlpTags); + activityEvent.EnumerateTags(ref otlpTags, SdkConfiguration.Instance.EventAttributeCountLimit); if (otlpTags.Created) { otlpEvent.Attributes.AddRange(otlpTags.Tags); @@ -355,7 +357,7 @@ public bool ForEach(KeyValuePair activityTag) this.Created = true; } - if (OtlpKeyValueTransformer.Instance.TryTransformTag(activityTag, out var attribute)) + if (OtlpKeyValueTransformer.Instance.TryTransformTag(activityTag, out var attribute, SdkConfiguration.Instance.AttributeValueLengthLimit)) { PooledList.Add(ref this.Tags, attribute); diff --git a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt index ecaf006aa47..791e1c4f84a 100644 --- a/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net462/PublicAPI.Unshipped.txt @@ -11,5 +11,10 @@ OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMillisecond OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action! configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder ~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt index 43156ee3c3a..a045a0ffba5 100644 --- a/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/net6.0/PublicAPI.Unshipped.txt @@ -11,5 +11,10 @@ OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder -~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder \ No newline at end of file +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt index ecaf006aa47..791e1c4f84a 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -11,5 +11,10 @@ OpenTelemetry.Logs.OpenTelemetryLoggerProvider.ForceFlush(int timeoutMillisecond OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action! configure) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions! +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder ~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt index 43156ee3c3a..a045a0ffba5 100644 --- a/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/OpenTelemetry/.publicApi/netstandard2.1/PublicAPI.Unshipped.txt @@ -11,5 +11,10 @@ OpenTelemetry.Logs.OpenTelemetryLoggerOptions.ConfigureResource(System.Action bool OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider() -> void OpenTelemetry.Logs.OpenTelemetryLoggerProvider.OpenTelemetryLoggerProvider(System.Action! configure) -> void +*REMOVED*static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure = null) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, OpenTelemetry.Logs.OpenTelemetryLoggerProvider! openTelemetryLoggerProvider, bool disposeProvider) -> Microsoft.Extensions.Logging.ILoggingBuilder! +static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AddOpenTelemetry(this Microsoft.Extensions.Logging.ILoggingBuilder! builder, System.Action? configure) -> Microsoft.Extensions.Logging.ILoggingBuilder! ~static OpenTelemetry.Metrics.MeterProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Metrics.MeterProviderBuilder meterProviderBuilder, System.Action configure) -> OpenTelemetry.Metrics.MeterProviderBuilder -~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder \ No newline at end of file +~static OpenTelemetry.Trace.TracerProviderBuilderExtensions.ConfigureResource(this OpenTelemetry.Trace.TracerProviderBuilder tracerProviderBuilder, System.Action configure) -> OpenTelemetry.Trace.TracerProviderBuilder diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md index e006a6427cd..85eee3b91e2 100644 --- a/src/OpenTelemetry/CHANGELOG.md +++ b/src/OpenTelemetry/CHANGELOG.md @@ -31,6 +31,10 @@ * Fix exact match of activity source name when `wildcard` is used. ([#3446](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3446)) +* Added AddOpenTelemetry `ILoggingBuilder` extensions which accept + `OpenTelemetryLoggerProvider` directly + ([#3489](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3489)) + ## 1.3.0 Released 2022-Jun-03 diff --git a/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs b/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs index df3f89df0aa..f6ea9783590 100644 --- a/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs +++ b/src/OpenTelemetry/Internal/EnvironmentVariableHelper.cs @@ -111,5 +111,33 @@ public static bool LoadUri(string envVarKey, out Uri result) return true; } + + /// + /// Reads an environment variable and parses it as a . + /// + /// The name of the environment variable. + /// The parsed value of the environment variable. + /// + /// Returns true when a non-empty value was read; otherwise, false. + /// + /// + /// Thrown when failed to parse the non-empty value. + /// + public static bool LoadBoolean(string envVarKey, out bool result) + { + result = default; + + if (!LoadString(envVarKey, out string value)) + { + return false; + } + + if (!bool.TryParse(value, out result)) + { + throw new FormatException($"{envVarKey} environment variable has an invalid value: '${value}'"); + } + + return true; + } } } diff --git a/src/OpenTelemetry/Internal/MathHelper.cs b/src/OpenTelemetry/Internal/MathHelper.cs index 043fcac6654..a3ffa316e8b 100644 --- a/src/OpenTelemetry/Internal/MathHelper.cs +++ b/src/OpenTelemetry/Internal/MathHelper.cs @@ -14,7 +14,6 @@ // limitations under the License. // -using System; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -136,15 +135,4 @@ public static bool IsFinite(double value) return !double.IsInfinity(value) && !double.IsNaN(value); #endif } - - public static string DoubleToString(double value) - { - var repr = Convert.ToString(BitConverter.DoubleToInt64Bits(value), 2); - return new string('0', 64 - repr.Length) + repr; - } - - public static double DoubleFromString(string value) - { - return BitConverter.Int64BitsToDouble(Convert.ToInt64(value, 2)); - } } diff --git a/src/OpenTelemetry/Internal/TagTransformer.cs b/src/OpenTelemetry/Internal/TagTransformer.cs index e63ccd623f9..f2a0cee9cc6 100644 --- a/src/OpenTelemetry/Internal/TagTransformer.cs +++ b/src/OpenTelemetry/Internal/TagTransformer.cs @@ -16,12 +16,13 @@ using System; using System.Collections.Generic; +using System.Linq; namespace OpenTelemetry.Internal; internal abstract class TagTransformer { - public bool TryTransformTag(KeyValuePair tag, out T result) + public bool TryTransformTag(KeyValuePair tag, out T result, int? maxLength = null) { if (tag.Value == null) { @@ -33,7 +34,7 @@ public bool TryTransformTag(KeyValuePair tag, out T result) { case char: case string: - result = this.TransformStringTag(tag.Key, Convert.ToString(tag.Value)); + result = this.TransformStringTag(tag.Key, TruncateString(Convert.ToString(tag.Value), maxLength)); break; case bool b: result = this.TransformBooleanTag(tag.Key, b); @@ -54,7 +55,7 @@ public bool TryTransformTag(KeyValuePair tag, out T result) case Array array: try { - result = this.TransformArrayTagInternal(tag.Key, array); + result = this.TransformArrayTagInternal(tag.Key, array, maxLength); } catch { @@ -77,7 +78,7 @@ public bool TryTransformTag(KeyValuePair tag, out T result) default: try { - result = this.TransformStringTag(tag.Key, tag.Value.ToString()); + result = this.TransformStringTag(tag.Key, TruncateString(tag.Value.ToString(), maxLength)); } catch { @@ -103,13 +104,20 @@ public bool TryTransformTag(KeyValuePair tag, out T result) protected abstract T TransformArrayTag(string key, Array array); - private T TransformArrayTagInternal(string key, Array array) + private static string TruncateString(string value, int? maxLength) + { + return maxLength.HasValue && value?.Length > maxLength + ? value.Substring(0, maxLength.Value) + : value; + } + + private T TransformArrayTagInternal(string key, Array array, int? maxStringValueLength) { // This switch ensures the values of the resultant array-valued tag are of the same type. return array switch { char[] => this.TransformArrayTag(key, array), - string[] => this.TransformArrayTag(key, array), + string[] => this.ConvertToStringArrayThenTransformArrayTag(key, array, maxStringValueLength), bool[] => this.TransformArrayTag(key, array), byte[] => this.TransformArrayTag(key, array), sbyte[] => this.TransformArrayTag(key, array), @@ -120,17 +128,25 @@ private T TransformArrayTagInternal(string key, Array array) long[] => this.TransformArrayTag(key, array), float[] => this.TransformArrayTag(key, array), double[] => this.TransformArrayTag(key, array), - _ => this.ConvertToStringArrayThenTransformArrayTag(key, array), + _ => this.ConvertToStringArrayThenTransformArrayTag(key, array, maxStringValueLength), }; } - private T ConvertToStringArrayThenTransformArrayTag(string key, Array array) + private T ConvertToStringArrayThenTransformArrayTag(string key, Array array, int? maxStringValueLength) { - var stringArray = new string[array.Length]; + string[] stringArray; - for (var i = 0; i < array.Length; ++i) + if (array is string[] arrayAsStringArray && (!maxStringValueLength.HasValue || !arrayAsStringArray.Any(s => s?.Length > maxStringValueLength))) + { + stringArray = arrayAsStringArray; + } + else { - stringArray[i] = array.GetValue(i)?.ToString(); + stringArray = new string[array.Length]; + for (var i = 0; i < array.Length; ++i) + { + stringArray[i] = TruncateString(array.GetValue(i)?.ToString(), maxStringValueLength); + } } return this.TransformArrayTag(key, stringArray); diff --git a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs index 000542b2fbd..9687d32eb1b 100644 --- a/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs +++ b/src/OpenTelemetry/Logs/OpenTelemetryLoggingExtensions.cs @@ -32,12 +32,20 @@ namespace Microsoft.Extensions.Logging public static class OpenTelemetryLoggingExtensions { /// - /// Adds a OpenTelemetry logger named 'OpenTelemetry' to the factory. + /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . + /// + /// The to use. + /// The supplied for call chaining. + public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder) + => AddOpenTelemetry(builder, configure: null); + + /// + /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . /// /// The to use. /// Optional configuration action. /// The supplied for call chaining. - public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, Action? configure = null) + public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, Action? configure) { Guard.ThrowIfNull(builder); @@ -51,5 +59,52 @@ public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, Act return builder; } + + /// + /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . + /// + /// + /// Note: The supplied will + /// automatically be disposed when the + /// built from is disposed. + /// + /// The to use. + /// . + /// The supplied for call chaining. + public static ILoggingBuilder AddOpenTelemetry(this ILoggingBuilder builder, OpenTelemetryLoggerProvider openTelemetryLoggerProvider) + => AddOpenTelemetry(builder, openTelemetryLoggerProvider, disposeProvider: true); + + /// + /// Adds an OpenTelemetry logger named 'OpenTelemetry' to the . + /// + /// The to use. + /// . + /// Controls whether or not the supplied + /// will be disposed when + /// the is disposed. + /// The supplied for call chaining. + public static ILoggingBuilder AddOpenTelemetry( + this ILoggingBuilder builder, + OpenTelemetryLoggerProvider openTelemetryLoggerProvider, + bool disposeProvider) + { + Guard.ThrowIfNull(builder); + Guard.ThrowIfNull(openTelemetryLoggerProvider); + + // Note: Currently if multiple OpenTelemetryLoggerProvider instances + // are added to the same ILoggingBuilder everything after the first + // is silently ignored. + + if (disposeProvider) + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(sp => openTelemetryLoggerProvider)); + } + else + { + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton(openTelemetryLoggerProvider)); + } + + return builder; + } } } diff --git a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs index 0c2f2843244..87edd9908b9 100644 --- a/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs +++ b/src/OpenTelemetry/Metrics/ExponentialBucketHistogram.cs @@ -35,10 +35,68 @@ internal class ExponentialBucketHistogram private int scale; private double scalingFactor; // 2 ^ scale / log(2) - public ExponentialBucketHistogram(int scale, int maxBuckets = 160) + /// + /// Initializes a new instance of the class. + /// + /// + /// The maximum number of buckets in each of the positive and negative ranges, not counting the special zero bucket. The default value is 160. + /// + public ExponentialBucketHistogram(int maxBuckets = 160) + : this(maxBuckets, 20) + { + } + + internal ExponentialBucketHistogram(int maxBuckets, int scale) { - Guard.ThrowIfOutOfRange(scale, min: -20, max: 20); // TODO: calculate the actual range - Guard.ThrowIfOutOfRange(maxBuckets, min: 1); + /* + The following table is calculated based on [ MapToIndex(double.Epsilon), MapToIndex(double.MaxValue) ]: + + | Scale | Index Range | + | ----- | ------------------------- | + | < -11 | [-1, 0] | + | -11 | [-1, 0] | + | -10 | [-2, 0] | + | -9 | [-3, 1] | + | -8 | [-5, 3] | + | -7 | [-9, 7] | + | -6 | [-17, 15] | + | -5 | [-34, 31] | + | -4 | [-68, 63] | + | -3 | [-135, 127] | + | -2 | [-269, 255] | + | -1 | [-538, 511] | + | 0 | [-1075, 1023] | + | 1 | [-2149, 2047] | + | 2 | [-4297, 4095] | + | 3 | [-8593, 8191] | + | 4 | [-17185, 16383] | + | 5 | [-34369, 32767] | + | 6 | [-68737, 65535] | + | 7 | [-137473, 131071] | + | 8 | [-274945, 262143] | + | 9 | [-549889, 524287] | + | 10 | [-1099777, 1048575] | + | 11 | [-2199553, 2097151] | + | 12 | [-4399105, 4194303] | + | 13 | [-8798209, 8388607] | + | 14 | [-17596417, 16777215] | + | 15 | [-35192833, 33554431] | + | 16 | [-70385665, 67108863] | + | 17 | [-140771329, 134217727] | + | 18 | [-281542657, 268435455] | + | 19 | [-563085313, 536870911] | + | 20 | [-1126170625, 1073741823] | + | 21 | [underflow, 2147483647] | + | > 21 | [underflow, overflow] | + */ + Guard.ThrowIfOutOfRange(scale, min: -11, max: 20); + + /* + Regardless of the scale, MapToIndex(1) will always be -1, so we need two buckets at minimum: + bucket[-1] = (1/base, 1] + bucket[0] = (1, base] + */ + Guard.ThrowIfOutOfRange(maxBuckets, min: 2); this.Scale = scale; this.PositiveBuckets = new CircularBufferBuckets(maxBuckets); @@ -88,17 +146,26 @@ public int MapToIndex(double value) Debug.Assert(value != 0, "IEEE-754 zero values should be handled by ZeroCount."); Debug.Assert(!double.IsNegative(value), "IEEE-754 negative values should be normalized before calling this method."); + var bits = BitConverter.DoubleToInt64Bits(value); + var fraction = bits & 0xFFFFFFFFFFFFFL /* fraction mask */; + if (this.Scale > 0) { + // TODO: do we really need this given the lookup table is needed for scale>0 anyways? + if (fraction == 0) + { + var exp = (int)((bits & 0x7FF0000000000000L /* exponent mask */) >> 52 /* fraction width */); + return ((exp - 1023 /* exponent bias */) << this.Scale) - 1; + } + // TODO: due to precision issue, the values that are close to the bucket // boundaries should be closely examined to avoid off-by-one. + return (int)Math.Ceiling(Math.Log(value) * this.scalingFactor) - 1; } else { - var bits = BitConverter.DoubleToInt64Bits(value); var exp = (int)((bits & 0x7FF0000000000000L /* exponent mask */) >> 52 /* fraction width */); - var fraction = bits & 0xFFFFFFFFFFFFFL /* fraction mask */; if (exp == 0) { diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Configuration/SdkConfigurationTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Configuration/SdkConfigurationTests.cs new file mode 100644 index 00000000000..dbbc9e43688 --- /dev/null +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/Configuration/SdkConfigurationTests.cs @@ -0,0 +1,133 @@ +// +// 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. +// + +using System; +using Xunit; + +namespace OpenTelemetry.Configuration.Tests +{ + [Collection("xUnitCollectionPreventingTestsThatDependOnSdkConfigurationFromRunningInParallel")] + public class SdkConfigurationTests : IDisposable + { + public SdkConfigurationTests() + { + ClearEnvVars(); + SdkConfiguration.Reset(); + } + + public void Dispose() + { + ClearEnvVars(); + SdkConfiguration.Reset(); + } + + [Fact] + public void SdkConfigurationDefaults() + { + var config = SdkConfiguration.Instance; + + Assert.Null(config.AttributeValueLengthLimit); + Assert.Null(config.AttributeCountLimit); + Assert.Null(config.SpanAttributeValueLengthLimit); + Assert.Null(config.SpanAttributeCountLimit); + Assert.Null(config.SpanEventCountLimit); + Assert.Null(config.SpanLinkCountLimit); + Assert.Null(config.EventAttributeCountLimit); + Assert.Null(config.LinkAttributeCountLimit); + } + + [Fact] + public void SdkConfigurationIsInitializedFromEnvironment() + { + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_COUNT_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "20"); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "20"); + Environment.SetEnvironmentVariable("OTEL_SPAN_EVENT_COUNT_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_SPAN_LINK_COUNT_LIMIT", "10"); + Environment.SetEnvironmentVariable("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", "30"); + Environment.SetEnvironmentVariable("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", "30"); + + SdkConfiguration.Reset(); + var config = SdkConfiguration.Instance; + + Assert.Equal(10, config.AttributeValueLengthLimit); + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeValueLengthLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(10, config.SpanEventCountLimit); + Assert.Equal(10, config.SpanLinkCountLimit); + Assert.Equal(30, config.EventAttributeCountLimit); + Assert.Equal(30, config.LinkAttributeCountLimit); + } + + [Fact] + public void SpanAttributeValueLengthLimitFallback() + { + var config = SdkConfiguration.Instance; + + config.AttributeValueLengthLimit = 10; + Assert.Equal(10, config.AttributeValueLengthLimit); + Assert.Equal(10, config.SpanAttributeValueLengthLimit); + + config.SpanAttributeValueLengthLimit = 20; + Assert.Equal(10, config.AttributeValueLengthLimit); + Assert.Equal(20, config.SpanAttributeValueLengthLimit); + } + + [Fact] + public void SpanAttributeCountLimitFallback() + { + var config = SdkConfiguration.Instance; + + config.AttributeCountLimit = 10; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(10, config.SpanAttributeCountLimit); + Assert.Equal(10, config.EventAttributeCountLimit); + Assert.Equal(10, config.LinkAttributeCountLimit); + + config.SpanAttributeCountLimit = 20; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(20, config.EventAttributeCountLimit); + Assert.Equal(20, config.LinkAttributeCountLimit); + + config.EventAttributeCountLimit = 30; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(30, config.EventAttributeCountLimit); + Assert.Equal(20, config.LinkAttributeCountLimit); + + config.LinkAttributeCountLimit = 40; + Assert.Equal(10, config.AttributeCountLimit); + Assert.Equal(20, config.SpanAttributeCountLimit); + Assert.Equal(30, config.EventAttributeCountLimit); + Assert.Equal(40, config.LinkAttributeCountLimit); + } + + private static void ClearEnvVars() + { + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_ATTRIBUTE_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_EVENT_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_SPAN_LINK_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", null); + Environment.SetEnvironmentVariable("OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", null); + } + } +} diff --git a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs index 56a8df672cd..a3a21e45c3f 100644 --- a/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpTraceExporterTests.cs @@ -18,8 +18,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Google.Protobuf.Collections; using Microsoft.Extensions.DependencyInjection; using Moq; +using OpenTelemetry.Configuration; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation; using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient; using OpenTelemetry.Resources; @@ -33,6 +35,7 @@ namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests { + [Collection("xUnitCollectionPreventingTestsThatDependOnSdkConfigurationFromRunningInParallel")] public class OtlpTraceExporterTests : Http2UnencryptedSupportTests { static OtlpTraceExporterTests() @@ -216,6 +219,79 @@ void RunTest(Batch batch) } } + [Fact] + public void SpanLimitsTest() + { + SdkConfiguration.Instance.AttributeValueLengthLimit = 4; + SdkConfiguration.Instance.AttributeCountLimit = 3; + SdkConfiguration.Instance.SpanEventCountLimit = 1; + SdkConfiguration.Instance.SpanLinkCountLimit = 1; + + var tags = new ActivityTagsCollection() + { + new KeyValuePair("TruncatedTag", "12345"), + new KeyValuePair("TruncatedStringArray", new string[] { "12345", "1234", string.Empty, null }), + new KeyValuePair("TruncatedObjectTag", new object()), + new KeyValuePair("OneTagTooMany", 1), + }; + + var links = new[] + { + new ActivityLink(default, tags), + new ActivityLink(default, tags), + }; + + using var activitySource = new ActivitySource(nameof(this.SpanLimitsTest)); + using var activity = activitySource.StartActivity("root", ActivityKind.Server, default(ActivityContext), tags, links); + + var event1 = new ActivityEvent("Event", DateTime.UtcNow, tags); + var event2 = new ActivityEvent("OneEventTooMany", DateTime.Now, tags); + + activity.AddEvent(event1); + activity.AddEvent(event2); + + var otlpSpan = activity.ToOtlpSpan(); + + Assert.NotNull(otlpSpan); + Assert.Equal(3, otlpSpan.Attributes.Count); + Assert.Equal("1234", otlpSpan.Attributes[0].Value.StringValue); + ArrayValueAsserts(otlpSpan.Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Attributes[2].Value.StringValue); + + Assert.Single(otlpSpan.Events); + Assert.Equal(3, otlpSpan.Events[0].Attributes.Count); + Assert.Equal("1234", otlpSpan.Events[0].Attributes[0].Value.StringValue); + ArrayValueAsserts(otlpSpan.Events[0].Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Events[0].Attributes[2].Value.StringValue); + + Assert.Single(otlpSpan.Links); + Assert.Equal(3, otlpSpan.Links[0].Attributes.Count); + Assert.Equal("1234", otlpSpan.Links[0].Attributes[0].Value.StringValue); + ArrayValueAsserts(otlpSpan.Links[0].Attributes[1].Value.ArrayValue.Values); + Assert.Equal(new object().ToString().Substring(0, 4), otlpSpan.Links[0].Attributes[2].Value.StringValue); + + void ArrayValueAsserts(RepeatedField values) + { + var expectedStringArray = new string[] { "1234", "1234", string.Empty, null }; + for (var i = 0; i < expectedStringArray.Length; ++i) + { + var expectedValue = expectedStringArray[i]; + var expectedValueCase = expectedValue != null + ? OtlpCommon.AnyValue.ValueOneofCase.StringValue + : OtlpCommon.AnyValue.ValueOneofCase.None; + + var actual = values[i]; + Assert.Equal(expectedValueCase, actual.ValueCase); + if (expectedValueCase != OtlpCommon.AnyValue.ValueOneofCase.None) + { + Assert.Equal(expectedValue, actual.StringValue); + } + } + } + + SdkConfiguration.Reset(); + } + [Fact] public void ToOtlpSpanTest() { diff --git a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs index 03dc2b9d6e3..208b12da777 100644 --- a/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNetCore.Tests/BasicTests.cs @@ -550,6 +550,39 @@ void ConfigureTestServices(IServiceCollection services) Assert.Equal(shouldEnrichBeCalled, enrichCalled); } + [Fact(Skip = "Changes pending on instrumentation")] + public async Task ActivitiesStartedInMiddlewareShouldNotBeUpdatedByInstrumentation() + { + var exportedItems = new List(); + + var activitySourceName = "TestMiddlewareActivitySource"; + var activityName = "TestMiddlewareActivity"; + + // Arrange + using (var client = this.factory + .WithWebHostBuilder(builder => + builder.ConfigureTestServices((IServiceCollection services) => + { + services.AddSingleton(new TestActivityMiddlewareImpl(activitySourceName, activityName)); + services.AddOpenTelemetryTracing((builder) => builder.AddAspNetCoreInstrumentation() + .AddSource(activitySourceName) + .AddInMemoryExporter(exportedItems)); + })) + .CreateClient()) + { + var response = await client.GetAsync("/api/values/2"); + response.EnsureSuccessStatusCode(); + WaitForActivityExport(exportedItems, 2); + } + + Assert.Equal(2, exportedItems.Count); + + var middlewareActivity = exportedItems[0]; + + // Middleware activity name should not be changed + Assert.Equal(activityName, middlewareActivity.DisplayName); + } + public void Dispose() { this.tracerProvider?.Dispose(); @@ -661,5 +694,28 @@ public override void OnStopActivity(Activity activity, object payload) this.OnStopActivityCallback?.Invoke(activity, payload); } } + + private class TestActivityMiddlewareImpl : ActivityMiddleware.ActivityMiddlewareImpl + { + private ActivitySource activitySource; + private Activity activity; + private string activityName; + + public TestActivityMiddlewareImpl(string activitySourceName, string activityName) + { + this.activitySource = new ActivitySource(activitySourceName); + this.activityName = activityName; + } + + public override void PreProcess(HttpContext context) + { + this.activity = this.activitySource.StartActivity(this.activityName); + } + + public override void PostProcess(HttpContext context) + { + this.activity?.Stop(); + } + } } } diff --git a/test/OpenTelemetry.Tests.Stress/README.md b/test/OpenTelemetry.Tests.Stress/README.md index 0d3cc381ec0..0013c573ad6 100644 --- a/test/OpenTelemetry.Tests.Stress/README.md +++ b/test/OpenTelemetry.Tests.Stress/README.md @@ -43,23 +43,32 @@ Running (concurrency = 1), press to stop... The stress test metrics are exposed via [Prometheus HttpListener](../../src/OpenTelemetry.Exporter.Prometheus.HttpListener/README.md), which can be accessed via -[http://localhost:9184/metrics/](http://localhost:9184/metrics/): +[http://localhost:9184/metrics/](http://localhost:9184/metrics/). -```text -# TYPE Process_NonpagedSystemMemorySize64 gauge -Process_NonpagedSystemMemorySize64 31651 1637385964580 - -# TYPE Process_PagedSystemMemorySize64 gauge -Process_PagedSystemMemorySize64 238672 1637385964580 - -# TYPE Process_PagedMemorySize64 gauge -Process_PagedMemorySize64 16187392 1637385964580 - -# TYPE Process_WorkingSet64 gauge -Process_WorkingSet64 29753344 1637385964580 +Following shows a section of the metrics exposed in prometheus format: -# TYPE Process_VirtualMemorySize64 gauge -Process_VirtualMemorySize64 2204045848576 1637385964580 +```text +# HELP OpenTelemetry_Tests_Stress_Loops The total number of `Run()` invocations that are completed. +# TYPE OpenTelemetry_Tests_Stress_Loops counter +OpenTelemetry_Tests_Stress_Loops 1844902947 1658950184752 + +# HELP OpenTelemetry_Tests_Stress_LoopsPerSecond The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds. +# TYPE OpenTelemetry_Tests_Stress_LoopsPerSecond gauge +OpenTelemetry_Tests_Stress_LoopsPerSecond 9007731.132075472 1658950184752 + +# HELP OpenTelemetry_Tests_Stress_CpuCyclesPerLoop The average CPU cycles for each `Run()` invocation, based on a small sliding window of few hundreds of milliseconds. +# TYPE OpenTelemetry_Tests_Stress_CpuCyclesPerLoop gauge +OpenTelemetry_Tests_Stress_CpuCyclesPerLoop 3008 1658950184752 + +# HELP process_runtime_dotnet_gc_collections_count Number of garbage collections that have occurred since process start. +# TYPE process_runtime_dotnet_gc_collections_count counter +process_runtime_dotnet_gc_collections_count{generation="gen2"} 0 1658950184752 +process_runtime_dotnet_gc_collections_count{generation="gen1"} 0 1658950184752 +process_runtime_dotnet_gc_collections_count{generation="gen0"} 0 1658950184752 + +# HELP process_runtime_dotnet_gc_allocations_size_bytes Count of bytes allocated on the managed GC heap since the process start. .NET objects are allocated from this heap. Object allocations from unmanaged languages such as C/C++ do not use this heap. +# TYPE process_runtime_dotnet_gc_allocations_size_bytes counter +process_runtime_dotnet_gc_allocations_size_bytes 5485192 1658950184752 ``` ## Writing your own stress test @@ -92,6 +101,13 @@ Add the [`Skeleton.cs`](./Skeleton.cs) file to your `*.csproj` file: ``` +Add the following packages to the project: + +```shell +dotnet add package OpenTelemetry.Exporter.Prometheus --prerelease +dotnet add package OpenTelemetry.Instrumentation.Runtime --prerelease +``` + Now you are ready to run your own stress test. Some useful notes: @@ -114,3 +130,4 @@ Some useful notes: sliding window of few hundreds of milliseconds. * `CPU Cycles/Loop` represents the average CPU cycles for each `Run()` invocation, based on a small sliding window of few hundreds of milliseconds. +* `Runaway Time` represents the runaway time (seconds) since the test started. diff --git a/test/OpenTelemetry.Tests.Stress/Skeleton.cs b/test/OpenTelemetry.Tests.Stress/Skeleton.cs index be7493e2809..e372b108710 100644 --- a/test/OpenTelemetry.Tests.Stress/Skeleton.cs +++ b/test/OpenTelemetry.Tests.Stress/Skeleton.cs @@ -63,11 +63,8 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0) () => dLoopsPerSecond, description: "The rate of `Run()` invocations based on a small sliding window of few hundreds of milliseconds."); var dCpuCyclesPerLoop = 0D; -#if NET462 - if (Environment.OSVersion.Platform == PlatformID.Win32NT) -#else + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) -#endif { meter.CreateObservableGauge( "OpenTelemetry.Tests.Stress.CpuCyclesPerLoop", @@ -143,7 +140,7 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0) dLoopsPerSecond = (double)nLoops / ((double)watch.ElapsedMilliseconds / 1000.0); dCpuCyclesPerLoop = nLoops == 0 ? 0 : nCpuCycles / nLoops; - output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}"; + output = $"Loops: {cntLoopsTotal:n0}, Loops/Second: {dLoopsPerSecond:n0}, CPU Cycles/Loop: {dCpuCyclesPerLoop:n0}, RunwayTime (Seconds): {watchForTotal.Elapsed.TotalSeconds:n0} "; Console.Title = output; } }, @@ -166,6 +163,7 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0) var cntCpuCyclesTotal = GetCpuCycles(); var cpuCyclesPerLoopTotal = cntLoopsTotal == 0 ? 0 : cntCpuCyclesTotal / cntLoopsTotal; Console.WriteLine("Stopping the stress test..."); + Console.WriteLine($"* Total Runaway Time (seconds) {watchForTotal.Elapsed.TotalSeconds:n0}"); Console.WriteLine($"* Total Loops: {cntLoopsTotal:n0}"); Console.WriteLine($"* Average Loops/Second: {totalLoopsPerSecond:n0}"); Console.WriteLine($"* Average CPU Cycles/Loop: {cpuCyclesPerLoopTotal:n0}"); @@ -177,11 +175,7 @@ public static void Stress(int concurrency = 0, int prometheusPort = 0) private static ulong GetCpuCycles() { -#if NET462 - if (Environment.OSVersion.Platform != PlatformID.Win32NT) -#else if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) -#endif { return 0; } diff --git a/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs b/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs index 290e5ceb77b..738fa27b9b3 100644 --- a/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs +++ b/test/OpenTelemetry.Tests/Internal/EnvironmentVariableHelperTests.cs @@ -55,6 +55,44 @@ public void LoadString_NoValue() Assert.Null(actualValue); } + [Theory] + [InlineData("true", true)] + [InlineData("TRUE", true)] + [InlineData("false", false)] + [InlineData("FALSE", false)] + [InlineData(" true ", true)] + [InlineData(" false ", false)] + public void LoadBoolean(string value, bool expectedValue) + { + Environment.SetEnvironmentVariable(EnvVar, value); + + bool actualBool = EnvironmentVariableHelper.LoadBoolean(EnvVar, out bool actualValue); + + Assert.True(actualBool); + Assert.Equal(expectedValue, actualValue); + } + + [Fact] + public void LoadBoolean_NoValue() + { + bool actualBool = EnvironmentVariableHelper.LoadBoolean(EnvVar, out bool actualValue); + + Assert.False(actualBool); + Assert.False(actualValue); + } + + [Theory] + [InlineData("something")] // non true/false + [InlineData(" ")] // whitespaces + [InlineData("0")] // 0 + [InlineData("1")] // 1 + public void LoadBoolean_Invalid(string value) + { + Environment.SetEnvironmentVariable(EnvVar, value); + + Assert.Throws(() => EnvironmentVariableHelper.LoadBoolean(EnvVar, out bool _)); + } + [Theory] [InlineData("123", 123)] [InlineData("0", 0)] diff --git a/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs new file mode 100644 index 00000000000..5f07ee7b06f --- /dev/null +++ b/test/OpenTelemetry.Tests/Logs/OpenTelemetryLoggingExtensionsTests.cs @@ -0,0 +1,211 @@ +// +// 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. +// + +#nullable enable + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Xunit; + +namespace OpenTelemetry.Logs.Tests; + +public sealed class OpenTelemetryLoggingExtensionsTests +{ + [Fact] + public void ServiceCollectionAddOpenTelemetryNoParametersTest() + { + bool optionsCallbackInvoked = false; + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + configure.AddOpenTelemetry(); + }); + + serviceCollection.Configure(options => + { + optionsCallbackInvoked = true; + }); + + using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + Assert.True(optionsCallbackInvoked); + } + + [Theory] + [InlineData(1, 0)] + [InlineData(1, 1)] + [InlineData(5, 5)] + public void ServiceCollectionAddOpenTelemetryConfigureActionTests(int numberOfBuilderRegistrations, int numberOfOptionsRegistrations) + { + int configureCallbackInvocations = 0; + int optionsCallbackInvocations = 0; + OpenTelemetryLoggerOptions? optionsInstance = null; + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + for (int i = 0; i < numberOfBuilderRegistrations; i++) + { + configure.AddOpenTelemetry(ConfigureCallback); + } + }); + + for (int i = 0; i < numberOfOptionsRegistrations; i++) + { + serviceCollection.Configure(OptionsCallback); + } + + using ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); + + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + Assert.NotNull(optionsInstance); + + Assert.Equal(numberOfBuilderRegistrations, configureCallbackInvocations); + Assert.Equal(numberOfOptionsRegistrations, optionsCallbackInvocations); + + void ConfigureCallback(OpenTelemetryLoggerOptions options) + { + if (optionsInstance == null) + { + optionsInstance = options; + } + else + { + Assert.Equal(optionsInstance, options); + } + + configureCallbackInvocations++; + } + + void OptionsCallback(OpenTelemetryLoggerOptions options) + { + if (optionsInstance == null) + { + optionsInstance = options; + } + else + { + Assert.Equal(optionsInstance, options); + } + + optionsCallbackInvocations++; + } + } + + [Fact] + public void ServiceCollectionAddOpenTelemetryWithProviderTest() + { + var provider = new WrappedOpenTelemetryLoggerProvider(); + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + configure.AddOpenTelemetry(provider); + }); + + using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()) + { + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + loggerFactory!.Dispose(); + + // Note: Provider disposal does not actually happen until serviceProvider is disposed + Assert.False(provider.Disposed); + } + + Assert.True(provider.Disposed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ServiceCollectionAddOpenTelemetryWithProviderAndDisposeSpecifiedTests(bool dispose) + { + var provider = new WrappedOpenTelemetryLoggerProvider(); + + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(configure => + { + configure.AddOpenTelemetry(provider, disposeProvider: dispose); + }); + + using (ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider()) + { + ILoggerFactory? loggerFactory = serviceProvider.GetService(); + + Assert.NotNull(loggerFactory); + + loggerFactory!.Dispose(); + + // Note: Provider disposal does not actually happen until serviceProvider is disposed + Assert.False(provider.Disposed); + } + + Assert.Equal(dispose, provider.Disposed); + + provider.Dispose(); + + Assert.True(provider.Disposed); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LoggerFactoryCreateAddOpenTelemetryWithProviderAndDisposeSpecifiedTests(bool dispose) + { + var provider = new WrappedOpenTelemetryLoggerProvider(); + + using (var factory = LoggerFactory.Create(configure => + { + configure.AddOpenTelemetry(provider, disposeProvider: dispose); + })) + { + Assert.False(provider.Disposed); + } + + Assert.Equal(dispose, provider.Disposed); + + provider.Dispose(); + + Assert.True(provider.Disposed); + } + + private sealed class WrappedOpenTelemetryLoggerProvider : OpenTelemetryLoggerProvider + { + public bool Disposed { get; private set; } + + protected override void Dispose(bool disposing) + { + this.Disposed = true; + + base.Dispose(disposing); + } + } +} diff --git a/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs index 7a1ea4708db..59f80ad0984 100644 --- a/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs +++ b/test/OpenTelemetry.Tests/Metrics/ExponentialBucketHistogramTest.cs @@ -17,6 +17,7 @@ #if NET6_0_OR_GREATER using System; +using OpenTelemetry.Tests; using Xunit; namespace OpenTelemetry.Metrics.Tests; @@ -26,87 +27,230 @@ public class ExponentialBucketHistogramTest [Fact] public void IndexLookup() { - // An exponential bucket histogram with scale = 0. - // The base is 2 ^ (2 ^ -0) = 2. - // The buckets are: - // - // ... - // bucket[-3]: (1/8, 1/4] - // bucket[-2]: (1/4, 1/2] - // bucket[-1]: (1/2, 1] - // bucket[0]: (1, 2] - // bucket[1]: (2, 4] - // bucket[2]: (4, 8] - // bucket[3]: (8, 16] - // ... + /* + An exponential bucket histogram with scale = 0. + The base is 2 ^ (2 ^ 0) = 2. + The buckets are: + ... + bucket[-3]: (1/8, 1/4] + bucket[-2]: (1/4, 1/2] + bucket[-1]: (1/2, 1] + bucket[0]: (1, 2] + bucket[1]: (2, 4] + bucket[2]: (4, 8] + bucket[3]: (8, 16] + ... + */ + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); - var histogram_scale0 = new ExponentialBucketHistogram(0); + Assert.Equal(-1075, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-1074, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2 + Assert.Equal(-1073, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000011"))); // double.Epsilon * 3 + Assert.Equal(-1073, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000100"))); // double.Epsilon * 4 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000101"))); // double.Epsilon * 5 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7 + Assert.Equal(-1072, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8 + Assert.Equal(-1025, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-1024, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308 + Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-1023, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-1022, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308 + Assert.Equal(-3, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25 + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000001"))); // ~0.5000000000000001 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000000 0000000000000000000000000000000000000000000000000000"))); // 2 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000001 0000000000000000000000000000000000000000000000000000"))); // 4 + Assert.Equal(2, histogram.MapToIndex(IEEE754Double.FromString("0 10000000010 0000000000000000000000000000000000000000000000000000"))); // 8 + Assert.Equal(3, histogram.MapToIndex(IEEE754Double.FromString("0 10000000011 0000000000000000000000000000000000000000000000000000"))); // 16 + Assert.Equal(4, histogram.MapToIndex(IEEE754Double.FromString("0 10000000100 0000000000000000000000000000000000000000000000000000"))); // 32 + Assert.Equal(1022, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000000"))); // ~8.98846567431158E+307 + Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000001"))); // ~8.988465674311582E+307 + Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308 + Assert.Equal(1023, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) - Assert.Equal(-1075, histogram_scale0.MapToIndex(double.Epsilon)); + /* + An exponential bucket histogram with scale = -1. + The base is 2 ^ (2 ^ 1) = 4. + The buckets are: + ... + bucket[-3]: (1/64, 1/16] + bucket[-2]: (1/16, 1/4] + bucket[-1]: (1/4, 1] + bucket[0]: (1, 4] + bucket[1]: (4, 16] + bucket[2]: (16, 64] + bucket[3]: (64, 256] + ... + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -1); - Assert.Equal(-1074, histogram_scale0.MapToIndex(double.Epsilon * 2)); + Assert.Equal(-538, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2 + Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000011"))); // double.Epsilon * 3 + Assert.Equal(-537, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000100"))); // double.Epsilon * 4 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000101"))); // double.Epsilon * 5 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7 + Assert.Equal(-536, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8 + Assert.Equal(-513, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308 + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-512, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-511, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308 + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000001"))); // ~0.5000000000000001 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000000 0000000000000000000000000000000000000000000000000000"))); // 2 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000001 0000000000000000000000000000000000000000000000000000"))); // 4 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000010 0000000000000000000000000000000000000000000000000000"))); // 8 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000011 0000000000000000000000000000000000000000000000000000"))); // 16 + Assert.Equal(2, histogram.MapToIndex(IEEE754Double.FromString("0 10000000100 0000000000000000000000000000000000000000000000000000"))); // 32 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000000"))); // ~8.98846567431158E+307 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000001"))); // ~8.988465674311582E+307 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308 + Assert.Equal(511, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) - Assert.Equal(-1073, histogram_scale0.MapToIndex(double.Epsilon * 3)); - Assert.Equal(-1073, histogram_scale0.MapToIndex(double.Epsilon * 4)); + /* + An exponential bucket histogram with scale = -2. + The base is 2 ^ (2 ^ 2) = 16. + The buckets are: + ... + bucket[-3]: (1/4096, 1/256] + bucket[-2]: (1/256, 1/16] + bucket[-1]: (1/16, 1] + bucket[0]: (1, 16] + bucket[1]: (16, 256] + bucket[2]: (256, 4096] + bucket[3]: (4096, 65536] + ... + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -2); - Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 5)); - Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 6)); - Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 7)); - Assert.Equal(-1072, histogram_scale0.MapToIndex(double.Epsilon * 8)); + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000010"))); // double.Epsilon * 2 + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000011"))); // double.Epsilon * 3 + Assert.Equal(-269, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000100"))); // double.Epsilon * 4 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000101"))); // double.Epsilon * 5 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000110"))); // double.Epsilon * 6 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000111"))); // double.Epsilon * 7 + Assert.Equal(-268, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000001000"))); // double.Epsilon * 8 + Assert.Equal(-257, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000000"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.112536929253601E-308 + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-256, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000001"))); // ~2.225073858507202E-308 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111101 0000000000000000000000000000000000000000000000000000"))); // 0.25 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000000"))); // 0.5 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111110 0000000000000000000000000000000000000000000000000001"))); // ~0.5000000000000001 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000000 0000000000000000000000000000000000000000000000000000"))); // 2 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000001 0000000000000000000000000000000000000000000000000000"))); // 4 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000010 0000000000000000000000000000000000000000000000000000"))); // 8 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 10000000011 0000000000000000000000000000000000000000000000000000"))); // 16 + Assert.Equal(1, histogram.MapToIndex(IEEE754Double.FromString("0 10000000100 0000000000000000000000000000000000000000000000000000"))); // 32 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000000"))); // ~8.98846567431158E+307 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 0000000000000000000000000000000000000000000000000001"))); // ~8.988465674311582E+307 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111110"))); // ~1.7976931348623155E+308 + Assert.Equal(255, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) - Assert.Equal(-1023, histogram_scale0.MapToIndex(2.2250738585072009E-308)); - Assert.Equal(-1023, histogram_scale0.MapToIndex(2.2250738585072014E-308)); + /* + An exponential bucket histogram with scale = -10. + The base is 2 ^ (2 ^ 10) = 2 ^ 1024 = double.MaxValue + 2 ^ -52 (slightly bigger than double.MaxValue). + The buckets are: + bucket[-2]: [double.Epsilon, 2 ^ -1024] + bucket[-1]: (2 ^ -1024, 1] + bucket[0]: (1, double.MaxValue] + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -10); - Assert.Equal(-3, histogram_scale0.MapToIndex(0.25)); + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-2, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000000"))); // ~5.562684646268003E-309 (2 ^ -1024) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0100000000000000000000000000000000000000000000000001"))); // ~5.56268464626801E-309 + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1000000000000000000000000000000000000000000000000001"))); // ~1.1125369292536007E-308 (2 ^ -1023) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) - Assert.Equal(-2, histogram_scale0.MapToIndex(0.375)); - Assert.Equal(-2, histogram_scale0.MapToIndex(0.5)); + /* + An exponential bucket histogram with scale = -11. + The base is 2 ^ (2 ^ 11) = 2 ^ 2048 (much bigger than double.MaxValue). + The buckets are: + bucket[-1]: [double.Epsilon, 1] + bucket[0]: (1, double.MaxValue] + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: -11); - Assert.Equal(-1, histogram_scale0.MapToIndex(0.75)); - Assert.Equal(-1, histogram_scale0.MapToIndex(1)); + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000001"))); // ~4.9406564584124654E-324 (minimum subnormal positive, double.Epsilon) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000000 1111111111111111111111111111111111111111111111111111"))); // ~2.2250738585072009E-308 (maximum subnormal positive) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 00000000001 0000000000000000000000000000000000000000000000000000"))); // ~2.2250738585072014E-308 (minimum normal positive, 2 ^ -1022) + Assert.Equal(-1, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000000"))); // 1 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 01111111111 0000000000000000000000000000000000000000000000000001"))); // ~1.0000000000000002 + Assert.Equal(0, histogram.MapToIndex(IEEE754Double.FromString("0 11111111110 1111111111111111111111111111111111111111111111111111"))); // ~1.7976931348623157E+308 (maximum normal positive, double.MaxValue) - Assert.Equal(0, histogram_scale0.MapToIndex(1.5)); - Assert.Equal(0, histogram_scale0.MapToIndex(2)); - - Assert.Equal(1, histogram_scale0.MapToIndex(3)); - Assert.Equal(1, histogram_scale0.MapToIndex(4)); - - Assert.Equal(2, histogram_scale0.MapToIndex(5)); - Assert.Equal(2, histogram_scale0.MapToIndex(6)); - Assert.Equal(2, histogram_scale0.MapToIndex(7)); - Assert.Equal(2, histogram_scale0.MapToIndex(8)); + /* + An exponential bucket histogram with scale = 1. + The base is 2 ^ (2 ^ -1) = sqrt(2) = 1.41421356237. + The buckets are: + ... + bucket[-3]: (0.35355339059, 1/2] + bucket[-2]: (1/2, 0.70710678118] + bucket[-1]: (0.70710678118, 1] + bucket[0]: (1, 1.41421356237] + bucket[1]: (1.41421356237, 2] + bucket[2]: (2, 2.82842712474] + bucket[3]: (2.82842712474, 4] + ... + */ + histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 1); + } - Assert.Equal(3, histogram_scale0.MapToIndex(9)); - Assert.Equal(3, histogram_scale0.MapToIndex(16)); + [Fact] + public void InfinityHandling() + { + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); - Assert.Equal(4, histogram_scale0.MapToIndex(17)); - Assert.Equal(4, histogram_scale0.MapToIndex(32)); + histogram.Record(double.PositiveInfinity); + histogram.Record(double.NegativeInfinity); - // An exponential bucket histogram with scale = 1. - // The base is 2 ^ (2 ^ -1) = sqrt(2) = 1.41421356237. - // The buckets are: - // - // ... - // bucket[-3]: (0.35355339059, 1/2] - // bucket[-2]: (1/2, 0.70710678118] - // bucket[-1]: (0.70710678118, 1] - // bucket[0]: (1, 1.41421356237] - // bucket[1]: (1.41421356237, 2] - // bucket[2]: (2, 2.82842712474] - // bucket[3]: (2.82842712474, 4] - // ... + Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size); + } - var histogram_scale1 = new ExponentialBucketHistogram(1); + [Fact] + public void NaNHandling() + { + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); - Assert.Equal(-3, histogram_scale1.MapToIndex(0.5)); + histogram.Record(double.NaN); // NaN (language/runtime native) + histogram.Record(IEEE754Double.FromString("0 11111111111 0000000000000000000000000000000000000000000000000001").DoubleValue); // sNaN on x86/64 and ARM + histogram.Record(IEEE754Double.FromString("0 11111111111 1000000000000000000000000000000000000000000000000001").DoubleValue); // qNaN on x86/64 and ARM + histogram.Record(IEEE754Double.FromString("0 11111111111 1111111111111111111111111111111111111111111111111111").DoubleValue); // NaN (alternative encoding) - Assert.Equal(-2, histogram_scale1.MapToIndex(0.6)); + Assert.Equal(0, histogram.ZeroCount + histogram.PositiveBuckets.Size + histogram.NegativeBuckets.Size); + } - Assert.Equal(-1, histogram_scale1.MapToIndex(1)); + [Fact] + public void ZeroHandling() + { + var histogram = new ExponentialBucketHistogram(maxBuckets: 2, scale: 0); - Assert.Equal(1, histogram_scale1.MapToIndex(2)); + histogram.Record(IEEE754Double.FromString("0 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // +0 + histogram.Record(IEEE754Double.FromString("1 00000000000 0000000000000000000000000000000000000000000000000000").DoubleValue); // -0 - Assert.Equal(3, histogram_scale1.MapToIndex(4)); + Assert.Equal(2, histogram.ZeroCount); } } diff --git a/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs b/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs new file mode 100644 index 00000000000..4ae89faaba8 --- /dev/null +++ b/test/OpenTelemetry.Tests/Shared/IEEE754Double.cs @@ -0,0 +1,103 @@ +// +// 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. +// + +using System; +using System.Runtime.InteropServices; + +namespace OpenTelemetry.Tests; + +[StructLayout(LayoutKind.Explicit)] +public struct IEEE754Double +{ + [FieldOffset(0)] + public double DoubleValue = 0; + + [FieldOffset(0)] + public long LongValue = 0; + + [FieldOffset(0)] + public ulong ULongValue = 0; + + public IEEE754Double(double value) + { + this.DoubleValue = value; + } + + public static implicit operator double(IEEE754Double value) + { + return value.DoubleValue; + } + + public static IEEE754Double operator ++(IEEE754Double value) + { + value.ULongValue++; + return value; + } + + public static IEEE754Double operator --(IEEE754Double value) + { + value.ULongValue--; + return value; + } + + public static IEEE754Double FromDouble(double value) + { + return new IEEE754Double(value); + } + + public static IEEE754Double FromLong(long value) + { + return new IEEE754Double { LongValue = value }; + } + + public static IEEE754Double FromULong(ulong value) + { + return new IEEE754Double { ULongValue = value }; + } + + public static IEEE754Double FromString(string value) + { + return IEEE754Double.FromLong(Convert.ToInt64(value.Replace(" ", string.Empty), 2)); + } + + public override string ToString() + { + Span chars = stackalloc char[66]; + + var bits = this.ULongValue; + var index = chars.Length - 1; + + for (int i = 0; i < 52; i++) + { + chars[index--] = (char)(bits & 0x01 | 0x30); + bits >>= 1; + } + + chars[index--] = ' '; + + for (int i = 0; i < 11; i++) + { + chars[index--] = (char)(bits & 0x01 | 0x30); + bits >>= 1; + } + + chars[index--] = ' '; + + chars[index--] = (char)(bits & 0x01 | 0x30); + + return $"{chars.ToString()} ({this.DoubleValue})"; + } +} diff --git a/test/TestApp.AspNetCore.3.1/ActivityMiddleware.cs b/test/TestApp.AspNetCore.3.1/ActivityMiddleware.cs new file mode 100644 index 00000000000..44c1eff7d14 --- /dev/null +++ b/test/TestApp.AspNetCore.3.1/ActivityMiddleware.cs @@ -0,0 +1,61 @@ +// +// 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. +// + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace TestApp.AspNetCore._3._1 +{ + public class ActivityMiddleware + { + private readonly ActivityMiddlewareImpl impl; + private readonly RequestDelegate next; + + public ActivityMiddleware(RequestDelegate next, ActivityMiddlewareImpl impl) + { + this.next = next; + this.impl = impl; + } + + public async Task InvokeAsync(HttpContext context) + { + if (this.impl != null) + { + this.impl.PreProcess(context); + } + + await this.next(context); + + if (this.impl != null) + { + this.impl.PostProcess(context); + } + } + + public class ActivityMiddlewareImpl + { + public virtual void PreProcess(HttpContext context) + { + // do nothing + } + + public virtual void PostProcess(HttpContext context) + { + // do nothing + } + } + } +} diff --git a/test/TestApp.AspNetCore.3.1/Startup.cs b/test/TestApp.AspNetCore.3.1/Startup.cs index eaf926c8af6..b8d84242aa1 100644 --- a/test/TestApp.AspNetCore.3.1/Startup.cs +++ b/test/TestApp.AspNetCore.3.1/Startup.cs @@ -39,6 +39,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton( new CallbackMiddleware.CallbackMiddlewareImpl()); + services.AddSingleton( + new ActivityMiddleware.ActivityMiddlewareImpl()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -50,6 +52,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } app.UseMiddleware(); + app.UseMiddleware(); app.UseRouting(); app.UseAuthorization(); diff --git a/test/TestApp.AspNetCore.6.0/ActivityMiddleware.cs b/test/TestApp.AspNetCore.6.0/ActivityMiddleware.cs new file mode 100644 index 00000000000..c45b5e828ab --- /dev/null +++ b/test/TestApp.AspNetCore.6.0/ActivityMiddleware.cs @@ -0,0 +1,61 @@ +// +// 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. +// + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace TestApp.AspNetCore._6._0 +{ + public class ActivityMiddleware + { + private readonly ActivityMiddlewareImpl impl; + private readonly RequestDelegate next; + + public ActivityMiddleware(RequestDelegate next, ActivityMiddlewareImpl impl) + { + this.next = next; + this.impl = impl; + } + + public async Task InvokeAsync(HttpContext context) + { + if (this.impl != null) + { + this.impl.PreProcess(context); + } + + await this.next(context); + + if (this.impl != null) + { + this.impl.PostProcess(context); + } + } + + public class ActivityMiddlewareImpl + { + public virtual void PreProcess(HttpContext context) + { + // Do nothing + } + + public virtual void PostProcess(HttpContext context) + { + // Do nothing + } + } + } +} diff --git a/test/TestApp.AspNetCore.6.0/Startup.cs b/test/TestApp.AspNetCore.6.0/Startup.cs index 1da70536444..988ee74706c 100644 --- a/test/TestApp.AspNetCore.6.0/Startup.cs +++ b/test/TestApp.AspNetCore.6.0/Startup.cs @@ -39,6 +39,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton( new CallbackMiddleware.CallbackMiddlewareImpl()); + services.AddSingleton( + new ActivityMiddleware.ActivityMiddlewareImpl()); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -50,6 +52,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) } app.UseMiddleware(); + app.UseMiddleware(); app.UseRouting(); app.UseAuthorization();