diff --git a/.github/workflows/dotnet-format.yml b/.github/workflows/dotnet-format.yml index 156989f90f..ba4d12353f 100644 --- a/.github/workflows/dotnet-format.yml +++ b/.github/workflows/dotnet-format.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v3 - name: Setup .NET 6.0 - uses: actions/setup-dotnet@v2 + uses: actions/setup-dotnet@v3.0.1 with: dotnet-version: 6.0.x diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..5c19a07622 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "fluentdData" + ] +} diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props index 6b4934f225..026bf333a4 100644 --- a/build/Common.nonprod.props +++ b/build/Common.nonprod.props @@ -20,15 +20,15 @@ Please sort alphabetically. Refer to https://docs.microsoft.com/en-us/nuget/concepts/package-versioning for semver syntax. --> - [0.12.1,0.13) + [0.13.2,0.14) [3.1.2,4.0.0) [2.3.1,3.0) - [5.0,6.0) + [5.0.0,7.0) [16.11.0,17.0) [4.17.2,5.0) $(OpenTelemetryPkgVer) [2.4.3,3.0) - [2.4.1,3.0) + [2.4.2,3.0) diff --git a/build/Common.props b/build/Common.props index 84eed10c51..2e4d94d967 100644 --- a/build/Common.props +++ b/build/Common.props @@ -23,19 +23,19 @@ Please sort alphabetically. Refer to https://docs.microsoft.com/en-us/nuget/concepts/package-versioning for semver syntax. --> - [2.4.0,3.0) + [4.2.0,5.0) [6.0.0] - [16.11.0] + [17.3.2] [2.1.0,5.0) [3.1.0,) - [1.0.0,2.0) + [1.0.3,2.0) [4.2.2,5.0) - [3.3.2] - [1.0.0,2.0) + [3.3.3] + [1.1.1,2.0) [1.3.1,2.0) $(OpenTelemetryApiPkgVer) [2.1.58,3.0) - [1.2.0-beta.354,2.0) + [1.2.0-beta.435,2.0) diff --git a/src/OpenTelemetry.Exporter.Geneva/GenevaBaseExporter.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaBaseExporter.cs index 3dc7185074..d38c76267a 100644 --- a/src/OpenTelemetry.Exporter.Geneva/GenevaBaseExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/GenevaBaseExporter.cs @@ -14,95 +14,9 @@ // limitations under the License. // -using System; -using System.Collections.Generic; - namespace OpenTelemetry.Exporter.Geneva; public abstract class GenevaBaseExporter : BaseExporter where T : class { - internal static readonly IReadOnlyDictionary V21_PART_A_MAPPING = new Dictionary - { - // Part A - [Schema.V21.PartA.IKey] = "env_iKey", - [Schema.V21.PartA.Name] = "env_name", - [Schema.V21.PartA.Ver] = "env_ver", - [Schema.V21.PartA.Time] = "env_time", - [Schema.V21.PartA.Cv] = "env_cv", - [Schema.V21.PartA.Epoch] = "env_epoch", - [Schema.V21.PartA.Flags] = "env_flags", - [Schema.V21.PartA.PopSample] = "env_popSample", - [Schema.V21.PartA.SeqNum] = "env_seqNum", - - // Part A Application extension - [Schema.V21.PartA.Extensions.App.Id] = "env_appId", - [Schema.V21.PartA.Extensions.App.Ver] = "env_appVer", - - // Part A Cloud extension - [Schema.V21.PartA.Extensions.Cloud.Environment] = "env_cloud_environment", - [Schema.V21.PartA.Extensions.Cloud.Location] = "env_cloud_location", - [Schema.V21.PartA.Extensions.Cloud.Name] = "env_cloud_name", - [Schema.V21.PartA.Extensions.Cloud.DeploymentUnit] = "env_cloud_deploymentUnit", - [Schema.V21.PartA.Extensions.Cloud.Role] = "env_cloud_role", - [Schema.V21.PartA.Extensions.Cloud.RoleInstance] = "env_cloud_roleInstance", - [Schema.V21.PartA.Extensions.Cloud.RoleVer] = "env_cloud_roleVer", - [Schema.V21.PartA.Extensions.Cloud.Ver] = "env_cloud_ver", - - // Part A Os extension - [Schema.V21.PartA.Extensions.Os.Name] = "env_os", - [Schema.V21.PartA.Extensions.Os.Ver] = "env_osVer", - }; - - internal static readonly IReadOnlyDictionary V40_PART_A_MAPPING = new Dictionary - { - // Part A - [Schema.V40.PartA.IKey] = "env_iKey", - [Schema.V40.PartA.Name] = "env_name", - [Schema.V40.PartA.Ver] = "env_ver", - [Schema.V40.PartA.Time] = "env_time", - - // Part A Application Extension - [Schema.V40.PartA.Extensions.App.Id] = "env_app_id", - [Schema.V40.PartA.Extensions.App.Ver] = "env_app_ver", - - // Part A Cloud Extension - [Schema.V40.PartA.Extensions.Cloud.Role] = "env_cloud_role", - [Schema.V40.PartA.Extensions.Cloud.RoleInstance] = "env_cloud_roleInstance", - [Schema.V40.PartA.Extensions.Cloud.RoleVer] = "env_cloud_roleVer", - - // Part A Os extension - [Schema.V40.PartA.Extensions.Os.Name] = "env_os_name", - [Schema.V40.PartA.Extensions.Os.Ver] = "env_os_ver", - }; - - internal static int AddPartAField(byte[] buffer, int cursor, string name, object value) - { - if (V40_PART_A_MAPPING.TryGetValue(name, out string replacementKey)) - { - cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, replacementKey); - } - else - { - cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, name); - } - - cursor = MessagePackSerializer.Serialize(buffer, cursor, value); - return cursor; - } - - internal static int AddPartAField(byte[] buffer, int cursor, string name, Span value) - { - if (V40_PART_A_MAPPING.TryGetValue(name, out string replacementKey)) - { - cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, replacementKey); - } - else - { - cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, name); - } - - cursor = MessagePackSerializer.SerializeSpan(buffer, cursor, value); - return cursor; - } } diff --git a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Traces/GenevaExporterHelperExtensions.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs similarity index 100% rename from src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Traces/GenevaExporterHelperExtensions.cs rename to src/OpenTelemetry.Exporter.Geneva/GenevaExporterHelperExtensions.cs diff --git a/src/OpenTelemetry.Exporter.Geneva/GenevaLogExporter.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaLogExporter.cs new file mode 100644 index 0000000000..b23bd32b10 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/GenevaLogExporter.cs @@ -0,0 +1,73 @@ +// +// 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; +using OpenTelemetry.Logs; + +namespace OpenTelemetry.Exporter.Geneva; + +public class GenevaLogExporter : GenevaBaseExporter +{ + internal bool IsUsingUnixDomainSocket; + + private bool isDisposed; + + private delegate ExportResult ExportLogRecordFunc(in Batch batch); + + private readonly ExportLogRecordFunc exportLogRecord; + + private readonly IDisposable exporter; + + public GenevaLogExporter(GenevaExporterOptions options) + { + Guard.ThrowIfNull(options); + Guard.ThrowIfNullOrWhitespace(options.ConnectionString); + + var msgPackExporter = new MsgPackLogExporter(options); + this.IsUsingUnixDomainSocket = msgPackExporter.IsUsingUnixDomainSocket; + this.exportLogRecord = (in Batch batch) => msgPackExporter.Export(in batch); + this.exporter = msgPackExporter; + } + + public override ExportResult Export(in Batch batch) + { + return this.exportLogRecord(batch); + } + + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + try + { + this.exporter.Dispose(); + } + catch (Exception ex) + { + ExporterEventSource.Log.ExporterException("GenevaLogExporter Dispose failed.", ex); + } + } + + this.isDisposed = true; + base.Dispose(disposing); + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Logs/GenevaLoggingExtensions.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaLoggingExtensions.cs similarity index 100% rename from src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Logs/GenevaLoggingExtensions.cs rename to src/OpenTelemetry.Exporter.Geneva/GenevaLoggingExtensions.cs diff --git a/src/OpenTelemetry.Exporter.Geneva/GenevaTraceExporter.cs b/src/OpenTelemetry.Exporter.Geneva/GenevaTraceExporter.cs new file mode 100644 index 0000000000..9c37b6db84 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/GenevaTraceExporter.cs @@ -0,0 +1,73 @@ +// +// 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.Diagnostics; +using OpenTelemetry.Internal; + +namespace OpenTelemetry.Exporter.Geneva; + +public class GenevaTraceExporter : GenevaBaseExporter +{ + internal readonly bool IsUsingUnixDomainSocket; + + private bool isDisposed; + + private delegate ExportResult ExportActivityFunc(in Batch batch); + + private readonly ExportActivityFunc exportActivity; + + private readonly IDisposable exporter; + + public GenevaTraceExporter(GenevaExporterOptions options) + { + Guard.ThrowIfNull(options); + Guard.ThrowIfNullOrWhitespace(options.ConnectionString); + + var msgPackExporter = new MsgPackTraceExporter(options); + this.IsUsingUnixDomainSocket = msgPackExporter.IsUsingUnixDomainSocket; + this.exportActivity = (in Batch batch) => msgPackExporter.Export(in batch); + this.exporter = msgPackExporter; + } + + public override ExportResult Export(in Batch batch) + { + return this.exportActivity(batch); + } + + protected override void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + try + { + this.exporter.Dispose(); + } + catch (Exception ex) + { + ExporterEventSource.Log.ExporterException("GenevaTraceExporter Dispose failed.", ex); + } + } + + this.isDisposed = true; + base.Dispose(disposing); + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackExporter.cs b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackExporter.cs new file mode 100644 index 0000000000..f543b01689 --- /dev/null +++ b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackExporter.cs @@ -0,0 +1,75 @@ +// +// 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.Collections.Generic; + +namespace OpenTelemetry.Exporter.Geneva; + +internal abstract class MsgPackExporter +{ + internal static readonly IReadOnlyDictionary V40_PART_A_MAPPING = new Dictionary + { + // Part A + [Schema.V40.PartA.IKey] = "env_iKey", + [Schema.V40.PartA.Name] = "env_name", + [Schema.V40.PartA.Ver] = "env_ver", + [Schema.V40.PartA.Time] = "env_time", + + // Part A Application Extension + [Schema.V40.PartA.Extensions.App.Id] = "env_app_id", + [Schema.V40.PartA.Extensions.App.Ver] = "env_app_ver", + + // Part A Cloud Extension + [Schema.V40.PartA.Extensions.Cloud.Role] = "env_cloud_role", + [Schema.V40.PartA.Extensions.Cloud.RoleInstance] = "env_cloud_roleInstance", + [Schema.V40.PartA.Extensions.Cloud.RoleVer] = "env_cloud_roleVer", + + // Part A Os extension + [Schema.V40.PartA.Extensions.Os.Name] = "env_os_name", + [Schema.V40.PartA.Extensions.Os.Ver] = "env_os_ver", + }; + + protected static int AddPartAField(byte[] buffer, int cursor, string name, object value) + { + if (V40_PART_A_MAPPING.TryGetValue(name, out string replacementKey)) + { + cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, replacementKey); + } + else + { + cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, name); + } + + cursor = MessagePackSerializer.Serialize(buffer, cursor, value); + return cursor; + } + + protected static int AddPartAField(byte[] buffer, int cursor, string name, Span value) + { + if (V40_PART_A_MAPPING.TryGetValue(name, out string replacementKey)) + { + cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, replacementKey); + } + else + { + cursor = MessagePackSerializer.SerializeUnicodeString(buffer, cursor, name); + } + + cursor = MessagePackSerializer.SerializeSpan(buffer, cursor, value); + return cursor; + } +} diff --git a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Logs/GenevaLogExporter.cs b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackLogExporter.cs similarity index 95% rename from src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Logs/GenevaLogExporter.cs rename to src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackLogExporter.cs index f6f17a0011..aa60cb88b4 100644 --- a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Logs/GenevaLogExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackLogExporter.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,12 +21,11 @@ using System.Runtime.InteropServices; using System.Threading; using Microsoft.Extensions.Logging; -using OpenTelemetry.Internal; using OpenTelemetry.Logs; namespace OpenTelemetry.Exporter.Geneva; -public class GenevaLogExporter : GenevaBaseExporter +internal sealed class MsgPackLogExporter : MsgPackExporter, IDisposable { private const int BUFFER_SIZE = 65360; // the maximum ETW payload (inclusive) private const int MaxSanitizedEventNameLength = 50; @@ -46,11 +45,8 @@ public class GenevaLogExporter : GenevaBaseExporter private readonly bool shouldPassThruTableMappings; private bool isDisposed; - public GenevaLogExporter(GenevaExporterOptions options) + public MsgPackLogExporter(GenevaExporterOptions options) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrWhitespace(options.ConnectionString); - // TODO: Validate mappings for reserved tablenames etc. if (options.TableNameMappings != null) { @@ -134,7 +130,7 @@ public GenevaLogExporter(GenevaExporterOptions options) private readonly IReadOnlyDictionary m_tableMappings; - public override ExportResult Export(in Batch batch) + public ExportResult Export(in Batch batch) { var result = ExportResult.Success; foreach (var logRecord in batch) @@ -154,31 +150,6 @@ public override ExportResult Export(in Batch batch) return result; } - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - // DO NOT Dispose m_buffer as it is a static type - try - { - (this.m_dataTransport as IDisposable)?.Dispose(); - this.m_prepopulatedFieldKeys.Clear(); - } - catch (Exception ex) - { - ExporterEventSource.Log.ExporterException("GenevaLogExporter Dispose failed.", ex); - } - } - - this.isDisposed = true; - base.Dispose(disposing); - } - internal bool IsUsingUnixDomainSocket { get => this.m_dataTransport is UnixDomainSocketDataTransport; @@ -296,7 +267,8 @@ internal int SerializeLogRecord(LogRecord logRecord) cntFields += 1; - cursor = AddPartAField(buffer, cursor, Schema.V40.PartA.Time, timestamp); + cursor = MessagePackSerializer.SerializeAsciiString(buffer, cursor, "env_time"); + cursor = MessagePackSerializer.SerializeUtcDateTime(buffer, cursor, timestamp); // LogRecord.Timestamp should already be converted to UTC format in the SDK cntFields += 1; // Part A - dt extension @@ -547,4 +519,25 @@ private static int SerializeSanitizedCategoryName(byte[] buffer, int cursor, str return cursor; } + + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + // DO NOT Dispose m_buffer as it is a static type + try + { + (this.m_dataTransport as IDisposable)?.Dispose(); + this.m_prepopulatedFieldKeys.Clear(); + } + catch (Exception ex) + { + ExporterEventSource.Log.ExporterException("MsgPackLogExporter Dispose failed.", ex); + } + + this.isDisposed = true; + } } diff --git a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Traces/GenevaTraceExporter.cs b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackTraceExporter.cs similarity index 95% rename from src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Traces/GenevaTraceExporter.cs rename to src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackTraceExporter.cs index 5ae7849df1..bdced95e7d 100644 --- a/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/Traces/GenevaTraceExporter.cs +++ b/src/OpenTelemetry.Exporter.Geneva/MsgPackExporter/MsgPackTraceExporter.cs @@ -1,4 +1,4 @@ -// +// // Copyright The OpenTelemetry Authors // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,17 +20,13 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; -using OpenTelemetry.Internal; namespace OpenTelemetry.Exporter.Geneva; -public class GenevaTraceExporter : GenevaBaseExporter +internal sealed class MsgPackTraceExporter : MsgPackExporter, IDisposable { - public GenevaTraceExporter(GenevaExporterOptions options) + public MsgPackTraceExporter(GenevaExporterOptions options) { - Guard.ThrowIfNull(options); - Guard.ThrowIfNullOrWhitespace(options.ConnectionString); - var partAName = "Span"; if (options.TableNameMappings != null && options.TableNameMappings.TryGetValue("Span", out var customTableName)) @@ -144,7 +140,7 @@ public GenevaTraceExporter(GenevaExporterOptions options) Buffer.BlockCopy(buffer, 0, this.m_bufferEpilogue, 0, cursor - 0); } - public override ExportResult Export(in Batch batch) + public ExportResult Export(in Batch batch) { // Note: The MessagePackSerializer takes way less time / memory than creating the activity itself. // This makes the short-circuit check less useful. @@ -173,30 +169,6 @@ public override ExportResult Export(in Batch batch) return result; } - protected override void Dispose(bool disposing) - { - if (this.isDisposed) - { - return; - } - - if (disposing) - { - try - { - (this.m_dataTransport as IDisposable)?.Dispose(); - this.m_buffer.Dispose(); - } - catch (Exception ex) - { - ExporterEventSource.Log.ExporterException("GenevaTraceExporter Dispose failed.", ex); - } - } - - this.isDisposed = true; - base.Dispose(disposing); - } - internal bool IsUsingUnixDomainSocket { get => this.m_dataTransport is UnixDomainSocketDataTransport; @@ -403,6 +375,26 @@ internal int SerializeActivity(Activity activity) return cursor; } + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + try + { + (this.m_dataTransport as IDisposable)?.Dispose(); + this.m_buffer.Dispose(); + } + catch (Exception ex) + { + ExporterEventSource.Log.ExporterException("MsgPackTraceExporter Dispose failed.", ex); + } + + this.isDisposed = true; + } + private const int BUFFER_SIZE = 65360; // the maximum ETW payload (inclusive) private readonly ThreadLocal m_buffer = new ThreadLocal(() => null); diff --git a/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md b/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md index e492e044af..ec1f06889a 100644 --- a/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md +++ b/src/OpenTelemetry.Exporter.Stackdriver/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Fix the issue of incorrect handling of null attributes. + ([#566](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/566)) + ## 1.0.0-beta.3 Released 2022-Jul-22 diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/AWSLambdaWrapper.cs b/src/OpenTelemetry.Instrumentation.AWSLambda/AWSLambdaWrapper.cs index c2995f5231..9359fc17b5 100644 --- a/src/OpenTelemetry.Instrumentation.AWSLambda/AWSLambdaWrapper.cs +++ b/src/OpenTelemetry.Instrumentation.AWSLambda/AWSLambdaWrapper.cs @@ -17,6 +17,7 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using Amazon.Lambda.Core; @@ -68,10 +69,7 @@ public static TResult Trace( ILambdaContext context, ActivityContext parentContext = default) { - TResult result = default; - Action action = () => result = lambdaHandler(input, context); - TraceInternal(tracerProvider, action, input, context, parentContext); - return result; + return TraceInternal(tracerProvider, lambdaHandler, input, context, parentContext); } /// @@ -95,8 +93,12 @@ public static void Trace( ILambdaContext context, ActivityContext parentContext = default) { - Action action = () => lambdaHandler(input, context); - TraceInternal(tracerProvider, action, input, context, parentContext); + Func func = (input, context) => + { + lambdaHandler(input, context); + return null; + }; + TraceInternal(tracerProvider, func, input, context, parentContext); } /// @@ -121,8 +123,12 @@ public static Task TraceAsync( ILambdaContext context, ActivityContext parentContext = default) { - Func action = async () => await lambdaHandler(input, context); - return TraceInternalAsync(tracerProvider, action, input, context, parentContext); + Func> func = async (input, context) => + { + await lambdaHandler(input, context); + return null; + }; + return TraceInternalAsync(tracerProvider, func, input, context, parentContext); } /// @@ -141,17 +147,14 @@ public static Task TraceAsync( /// unless X-Ray propagation is disabled in the configuration for this wrapper. /// /// Task of result. - public static async Task TraceAsync( + public static Task TraceAsync( TracerProvider tracerProvider, Func> lambdaHandler, TInput input, ILambdaContext context, ActivityContext parentContext = default) { - TResult result = default; - Func action = async () => result = await lambdaHandler(input, context); - await TraceInternalAsync(tracerProvider, action, input, context, parentContext); - return result; + return TraceInternalAsync(tracerProvider, lambdaHandler, input, context, parentContext); } #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters @@ -167,9 +170,12 @@ internal static Activity OnFunctionStart(TInput input, ILambdaContext co } } - var tags = AWSLambdaUtils.GetFunctionTags(input, context); + var functionTags = AWSLambdaUtils.GetFunctionTags(input, context); + var httpTags = AWSLambdaHttpUtils.GetHttpTags(input); + + // We assume that functionTags and httpTags have no intersection. var activityName = AWSLambdaUtils.GetFunctionName(context) ?? "AWS Lambda Invoke"; - var activity = AWSLambdaActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext, tags); + var activity = AWSLambdaActivitySource.StartActivity(activityName, ActivityKind.Server, parentContext, functionTags.Concat(httpTags)); return activity; } @@ -197,51 +203,55 @@ private static void OnException(Activity activity, Exception exception) } } - private static void TraceInternal( + private static TResult TraceInternal( TracerProvider tracerProvider, - Action handler, + Func handler, TInput input, ILambdaContext context, ActivityContext parentContext = default) { - var lambdaActivity = OnFunctionStart(input, context, parentContext); + var activity = OnFunctionStart(input, context, parentContext); try { - handler(); + var result = handler(input, context); + AWSLambdaHttpUtils.SetHttpTagsFromResult(activity, result); + return result; } catch (Exception ex) { - OnException(lambdaActivity, ex); + OnException(activity, ex); throw; } finally { - OnFunctionStop(lambdaActivity, tracerProvider); + OnFunctionStop(activity, tracerProvider); } } - private static async Task TraceInternalAsync( + private static async Task TraceInternalAsync( TracerProvider tracerProvider, - Func handlerAsync, + Func> handlerAsync, TInput input, ILambdaContext context, ActivityContext parentContext = default) { - var lambdaActivity = OnFunctionStart(input, context, parentContext); + var activity = OnFunctionStart(input, context, parentContext); try { - await handlerAsync(); + var result = await handlerAsync(input, context); + AWSLambdaHttpUtils.SetHttpTagsFromResult(activity, result); + return result; } catch (Exception ex) { - OnException(lambdaActivity, ex); + OnException(activity, ex); throw; } finally { - OnFunctionStop(lambdaActivity, tracerProvider); + OnFunctionStop(activity, tracerProvider); } } } diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AWSLambda/CHANGELOG.md index db388af70c..729760a2cf 100644 --- a/src/OpenTelemetry.Instrumentation.AWSLambda/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AWSLambda/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +* Add HTTP server span attributes for API Gateway triggers + ([#626](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/626)) + ## 1.1.0-beta.2 Release PR: [#590](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/590) diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaHttpUtils.cs b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaHttpUtils.cs new file mode 100644 index 0000000000..25adefc903 --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaHttpUtils.cs @@ -0,0 +1,136 @@ +// +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Web; +using Amazon.Lambda.APIGatewayEvents; +using OpenTelemetry.Trace; + +namespace OpenTelemetry.Instrumentation.AWSLambda.Implementation +{ + internal class AWSLambdaHttpUtils + { + // x-forwarded-... headers are described here https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/x-forwarded-headers.html + private const string HeaderXForwardedProto = "x-forwarded-proto"; + private const string HeaderHost = "host"; + + internal static IEnumerable> GetHttpTags(TInput input) + { + var tags = new List>(); + + string httpScheme = null; + string httpTarget = null; + string httpMethod = null; + string hostName = null; + int? hostPort = null; + + switch (input) + { + case APIGatewayProxyRequest request: + httpScheme = AWSLambdaUtils.GetHeaderValues(request, HeaderXForwardedProto)?.LastOrDefault(); + httpTarget = string.Concat(request.RequestContext?.Path ?? string.Empty, GetQueryString(request)); + httpMethod = request.HttpMethod; + var hostHeader = AWSLambdaUtils.GetHeaderValues(request, HeaderHost)?.LastOrDefault(); + (hostName, hostPort) = GetHostAndPort(httpScheme, hostHeader); + break; + case APIGatewayHttpApiV2ProxyRequest requestV2: + httpScheme = AWSLambdaUtils.GetHeaderValues(requestV2, HeaderXForwardedProto)?.LastOrDefault(); + httpTarget = string.Concat(requestV2.RawPath ?? string.Empty, GetQueryString(requestV2)); + httpMethod = requestV2.RequestContext?.Http?.Method; + var hostHeaderV2 = AWSLambdaUtils.GetHeaderValues(requestV2, HeaderHost)?.LastOrDefault(); + (hostName, hostPort) = GetHostAndPort(httpScheme, hostHeaderV2); + break; + } + + tags.AddTagIfNotNull(SemanticConventions.AttributeHttpScheme, httpScheme); + tags.AddTagIfNotNull(SemanticConventions.AttributeHttpTarget, httpTarget); + tags.AddTagIfNotNull(SemanticConventions.AttributeHttpMethod, httpMethod); + tags.AddTagIfNotNull(SemanticConventions.AttributeNetHostName, hostName); + tags.AddTagIfNotNull(SemanticConventions.AttributeNetHostPort, hostPort); + + return tags; + } + + internal static void SetHttpTagsFromResult(Activity activity, object result) + { + switch (result) + { + case APIGatewayProxyResponse response: + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); + break; + case APIGatewayHttpApiV2ProxyResponse responseV2: + activity.SetTag(SemanticConventions.AttributeHttpStatusCode, responseV2.StatusCode); + break; + } + } + + internal static string GetQueryString(APIGatewayProxyRequest request) + { + if (request.MultiValueQueryStringParameters == null) + { + return string.Empty; + } + + var queryString = new StringBuilder(); + var separator = '?'; + foreach (var parameterKvp in request.MultiValueQueryStringParameters) + { + // Multiple values for the same parameter will be added to query + // as ampersand separated: name=value1&name=value2 + foreach (var value in parameterKvp.Value) + { + queryString.Append(separator) + .Append(HttpUtility.UrlEncode(parameterKvp.Key)) + .Append("=") + .Append(HttpUtility.UrlEncode(value)); + separator = '&'; + } + } + + return queryString.ToString(); + } + + internal static string GetQueryString(APIGatewayHttpApiV2ProxyRequest request) => + string.IsNullOrEmpty(request.RawQueryString) ? string.Empty : "?" + request.RawQueryString; + + internal static (string Host, int? Port) GetHostAndPort(string httpScheme, string hostHeader) + { + if (hostHeader == null) + { + return (null, null); + } + + var hostAndPort = hostHeader.Split(new char[] { ':' }, 2); + if (hostAndPort.Length > 1) + { + var host = hostAndPort[0]; + return int.TryParse(hostAndPort[1], out var port) + ? (host, port) + : (host, null); + } + else + { + return (hostAndPort[0], GetDefaultPort(httpScheme)); + } + } + + private static int? GetDefaultPort(string httpScheme) => + httpScheme == "https" ? 443 : httpScheme == "http" ? 80 : null; + } +} diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs index c2ae886f0d..75d0ba2a63 100644 --- a/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs +++ b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/AWSLambdaUtils.cs @@ -137,6 +137,30 @@ internal static IEnumerable> GetFunctionTags GetHeaderValues(APIGatewayProxyRequest request, string name) + { + var multiValueHeader = request.MultiValueHeaders?.GetValueByKeyIgnoringCase(name); + if (multiValueHeader != null) + { + return multiValueHeader; + } + + var headerValue = request.Headers?.GetValueByKeyIgnoringCase(name); + + return headerValue != null ? new[] { headerValue } : null; + } + + internal static IEnumerable GetHeaderValues(APIGatewayHttpApiV2ProxyRequest request, string name) + { + var headerValue = GetHeaderValue(request, name); + + // Multiple values for the same header will be separated by a comma. + return headerValue?.Split(','); + } + + private static string GetHeaderValue(APIGatewayHttpApiV2ProxyRequest request, string name) => + request.Headers?.GetValueByKeyIgnoringCase(name); + private static string GetAccountId(string functionArn) { // The fifth item of function arn: https://github.com/open-telemetry/opentelemetry-specification/blob/86aeab1e0a7e6c67be09c7f15ff25063ee6d2b5c/specification/trace/semantic_conventions/instrumentation/aws-lambda.md#all-triggers @@ -167,16 +191,11 @@ private static string GetFaasId(string functionArn) return faasId; } - private static string GetFaasTrigger(TInput input) - { - var trigger = "other"; - if (input is APIGatewayProxyRequest || input is APIGatewayHttpApiV2ProxyRequest) - { - trigger = "http"; - } + private static string GetFaasTrigger(TInput input) => + IsHttpRequest(input) ? "http" : "other"; - return trigger; - } + private static bool IsHttpRequest(TInput input) => + input is APIGatewayProxyRequest || input is APIGatewayHttpApiV2ProxyRequest; private static ActivityContext ParseXRayTraceHeader(string rawHeader) { @@ -190,27 +209,4 @@ private static ActivityContext ParseXRayTraceHeader(string rawHeader) var propagationContext = xrayPropagator.Extract(default, carrier, Getter); return propagationContext.ActivityContext; } - - private static IEnumerable GetHeaderValues(APIGatewayProxyRequest request, string name) - { - if (request.MultiValueHeaders != null && - request.MultiValueHeaders.TryGetValue(name, out var values)) - { - return values; - } - - return null; - } - - private static IEnumerable GetHeaderValues(APIGatewayHttpApiV2ProxyRequest request, string name) - { - if (request.Headers != null && - request.Headers.TryGetValue(name, out var header)) - { - // Multiple values for the same header will be separated by a comma. - return header?.Split(','); - } - - return null; - } } diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/CommonExtensions.cs b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/CommonExtensions.cs new file mode 100644 index 0000000000..d82ebfad5e --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.AWSLambda/Implementation/CommonExtensions.cs @@ -0,0 +1,57 @@ +// +// 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.Collections.Generic; + +namespace OpenTelemetry.Instrumentation.AWSLambda.Implementation +{ + internal static class CommonExtensions + { + internal static void AddTagIfNotNull(this List> tags, string tagName, object tagValue) + { + if (tagValue != null) + { + tags.Add(new(tagName, tagValue)); + } + } + + internal static T GetValueByKeyIgnoringCase(this IDictionary dict, string key) + { + // TODO: there may be opportunities for performance improvements of this method. + + // We had to introduce case-insensitive headers search as can't fully rely on + // AWS documentation stating that expected headers are lower-case. AWS test + // console offers JSON example with camel case header names. + // See X-Forwarded-Proto or X-Forwarded-Port for example. + + if (dict == null) + { + return default; + } + + foreach (var kvp in dict) + { + if (string.Equals(kvp.Key, key, StringComparison.OrdinalIgnoreCase)) + { + return kvp.Value; + } + } + + return default; + } + } +} diff --git a/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj b/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj index 239fb3e9f1..4afae25088 100644 --- a/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj +++ b/src/OpenTelemetry.Instrumentation.AWSLambda/OpenTelemetry.Instrumentation.AWSLambda.csproj @@ -17,6 +17,7 @@ + diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md index ed4038c5fc..bd3006b278 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## 1.0.0-rc9.6 + +Released 2022-Sep-28 + +* Update `OpenTelemetry.Api` to `1.3.1`. +([#665](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/665)) + ## 1.0.0-rc9.5 (source code moved to contrib repo) Released 2022-Jun-21 diff --git a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj index 35ea9547d7..64ed2d64c2 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.csproj @@ -1,7 +1,7 @@ - net462 + $(NetFrameworkMinimumSupportedVersion) A module that instruments incoming request with System.Diagnostics.Activity and notifies listeners with DiagnosticsSource. $(PackageTags);distributed-tracing;AspNet;MVC;WebAPI Instrumentation.AspNet.TelemetryHttpModule- @@ -23,6 +23,6 @@ - + diff --git a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md b/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md index 71a4bfa708..ca186294be 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md +++ b/src/OpenTelemetry.Instrumentation.AspNet/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +## 1.0.0-rc9.6 + +Released 2022-Sep-28 + +* Migrate to native Activity `Status` and `StatusDesciption`. + ([#651](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/651)) + ## 1.0.0-rc9.5 (source code moved to contrib repo) Released 2022-Jun-21 diff --git a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs index 716b9c0136..a8c1ddb9a4 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs +++ b/src/OpenTelemetry.Instrumentation.AspNet/Implementation/HttpInListener.cs @@ -128,9 +128,9 @@ private void OnStopActivity(Activity activity, HttpContext context) activity.SetTag(SemanticConventions.AttributeHttpStatusCode, response.StatusCode); - if (activity.GetStatus().StatusCode == StatusCode.Unset) + if (activity.Status == ActivityStatusCode.Unset) { - activity.SetStatus(SpanHelper.ResolveSpanStatusForHttpStatusCode(activity.Kind, response.StatusCode)); + activity.SetStatus(SpanHelper.ResolveActivityStatusForHttpStatusCode(activity.Kind, response.StatusCode)); } var routeData = context.Request.RequestContext.RouteData; @@ -181,7 +181,7 @@ private void OnException(Activity activity, HttpContext context, Exception excep activity.RecordException(exception); } - activity.SetStatus(Status.Error.WithDescription(exception.Message)); + activity.SetStatus(ActivityStatusCode.Error, exception.Message); try { diff --git a/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj b/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj index 03f775a3d7..ea25e6d359 100644 --- a/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj +++ b/src/OpenTelemetry.Instrumentation.AspNet/OpenTelemetry.Instrumentation.AspNet.csproj @@ -1,7 +1,7 @@ - net462 + $(NetFrameworkMinimumSupportedVersion) ASP.NET instrumentation for OpenTelemetry .NET $(PackageTags);distributed-tracing;AspNet;MVC;WebAPI true @@ -14,7 +14,6 @@ - diff --git a/src/OpenTelemetry.Instrumentation.AspNet/SpanHelper.cs b/src/OpenTelemetry.Instrumentation.AspNet/SpanHelper.cs new file mode 100644 index 0000000000..14e6bc4e2c --- /dev/null +++ b/src/OpenTelemetry.Instrumentation.AspNet/SpanHelper.cs @@ -0,0 +1,43 @@ +// +// 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.Diagnostics; + +namespace OpenTelemetry.Instrumentation.AspNet; + +/// +/// A collection of helper methods to be used when building spans. +/// +internal static class SpanHelper +{ + /// + /// Helper method that populates Activity Status from http status code according + /// to https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/http.md#status. + /// + /// The span kind. + /// Http status code. + /// Resolved span for the Http status code. + public static ActivityStatusCode ResolveActivityStatusForHttpStatusCode(ActivityKind kind, int httpStatusCode) + { + var upperBound = kind == ActivityKind.Client ? 399 : 499; + if (httpStatusCode >= 100 && httpStatusCode <= upperBound) + { + return ActivityStatusCode.Unset; + } + + return ActivityStatusCode.Error; + } +} diff --git a/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs b/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs index 516945ebd1..223808b4a4 100644 --- a/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Instrumentation.Process/MeterProviderBuilderExtensions.cs @@ -41,7 +41,7 @@ public static MeterProviderBuilder AddProcessInstrumentation( configure?.Invoke(options); var instrumentation = new ProcessMetrics(options); - builder.AddMeter(ProcessMetrics.MeterInstance.Name); + builder.AddMeter(instrumentation.MeterInstance.Name); return builder.AddInstrumentation(() => instrumentation); } } diff --git a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs index 563ff11452..30c9b2a7d8 100644 --- a/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs +++ b/src/OpenTelemetry.Instrumentation.Process/ProcessMetrics.cs @@ -14,48 +14,90 @@ // limitations under the License. // +using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Reflection; using Diagnostics = System.Diagnostics; namespace OpenTelemetry.Instrumentation.Process; -internal class ProcessMetrics +internal sealed class ProcessMetrics { internal static readonly AssemblyName AssemblyName = typeof(ProcessMetrics).Assembly.GetName(); - internal static readonly Meter MeterInstance = new(AssemblyName.Name, AssemblyName.Version.ToString()); - private static readonly Diagnostics.Process CurrentProcess = Diagnostics.Process.GetCurrentProcess(); + internal readonly Meter MeterInstance = new(AssemblyName.Name, AssemblyName.Version.ToString()); - static ProcessMetrics() + private readonly Diagnostics.Process currentProcess = Diagnostics.Process.GetCurrentProcess(); + private double? memoryUsage; + private double? virtualMemoryUsage; + private double? userProcessorTime; + private double? privilegedProcessorTime; + + public ProcessMetrics(ProcessInstrumentationOptions options) { // TODO: change to ObservableUpDownCounter - MeterInstance.CreateObservableGauge( + this.MeterInstance.CreateObservableGauge( "process.memory.usage", () => { - CurrentProcess.Refresh(); - return CurrentProcess.WorkingSet64; + if (!this.memoryUsage.HasValue) + { + this.Snapshot(); + } + + var value = this.memoryUsage.Value; + this.memoryUsage = null; + return value; }, unit: "By", - description: "The amount of physical memory in use."); + description: "The amount of physical memory allocated for this process."); // TODO: change to ObservableUpDownCounter - MeterInstance.CreateObservableGauge( + this.MeterInstance.CreateObservableGauge( "process.memory.virtual", () => { - CurrentProcess.Refresh(); - return CurrentProcess.VirtualMemorySize64; + if (!this.virtualMemoryUsage.HasValue) + { + this.Snapshot(); + } + + var value = this.virtualMemoryUsage.Value; + this.virtualMemoryUsage = null; + return value; }, unit: "By", - description: "The amount of committed virtual memory."); + description: "The amount of virtual memory allocated for this process that cannot be shared with other processes."); + + this.MeterInstance.CreateObservableCounter( + "process.cpu.time", + () => + { + if (!this.userProcessorTime.HasValue || !this.privilegedProcessorTime.HasValue) + { + this.Snapshot(); + } + + var userProcessorTimeValue = this.userProcessorTime.Value; + var privilegedProcessorTimeValue = this.privilegedProcessorTime.Value; + this.userProcessorTime = null; + this.privilegedProcessorTime = null; + + return new[] + { + new Measurement(userProcessorTimeValue, new KeyValuePair("state", "user")), + new Measurement(privilegedProcessorTimeValue, new KeyValuePair("state", "system")), + }; + }, + unit: "s", + description: "Total CPU seconds broken down by different states."); } - /// - /// Initializes a new instance of the class. - /// - /// The options to define the metrics. - public ProcessMetrics(ProcessInstrumentationOptions options) + private void Snapshot() { + this.currentProcess.Refresh(); + this.memoryUsage = this.currentProcess.WorkingSet64; + this.virtualMemoryUsage = this.currentProcess.PrivateMemorySize64; + this.userProcessorTime = this.currentProcess.UserProcessorTime.TotalSeconds; + this.privilegedProcessorTime = this.currentProcess.PrivilegedProcessorTime.TotalSeconds; } } diff --git a/src/OpenTelemetry.Instrumentation.Runtime/README.md b/src/OpenTelemetry.Instrumentation.Runtime/README.md index 4987f4ac91..f2b465b82e 100644 --- a/src/OpenTelemetry.Instrumentation.Runtime/README.md +++ b/src/OpenTelemetry.Instrumentation.Runtime/README.md @@ -96,9 +96,6 @@ to all the `ObservableGauge` below. Note: This metric is only available when targeting .NET6 or later. -Note: `gc.heap.fragmentation.size` metrics is removed for .NET 6 because of a -[bug](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/issues/496). - | Units | Instrument Type | Value Type | Attribute Key(s) | Attribute Values | |---------|-----------------|------------|------------------|------------------| | `bytes` | ObservableGauge | `Int64` | No Attributes | N/A | @@ -135,7 +132,7 @@ The API used to retrieve the value is: The heap fragmentation, as observed during the latest garbage collection. The value will be unavailable until at least one garbage collection has occurred. -Note: This metric is only available when targeting .NET6 or later. +Note: This metric is only available when targeting .NET 7 or later. | Units | Instrument Type | Value Type | Attribute Key(s) | Attribute Values | |---------|-----------------|------------|------------------|----------------------------| diff --git a/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/LogExporterBenchmarks.cs b/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/LogExporterBenchmarks.cs index 876d10f014..4c6c4bc122 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/LogExporterBenchmarks.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/LogExporterBenchmarks.cs @@ -20,42 +20,42 @@ using OpenTelemetry.Logs; /* -BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000 +BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22621 Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores .NET SDK=7.0.100-preview.6.22352.1 - [Host] : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT - DefaultJob : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT + [Host] : .NET 6.0.9 (6.0.922.41905), X64 RyuJIT + DefaultJob : .NET 6.0.9 (6.0.922.41905), X64 RyuJIT Without Scopes -| Method | IncludeFormattedMessage | Mean | Error | StdDev | Gen 0 | Allocated | -|-------------------------- |------------------------ |---------:|---------:|---------:|-------:|----------:| -| LoggerWithMessageTemplate | False | 979.5 ns | 11.46 ns | 10.72 ns | 0.0401 | 256 B | -| LoggerWithDirectLoggerAPI | False | 887.9 ns | 17.24 ns | 16.13 ns | 0.0620 | 392 B | -| LoggerWithSourceGenerator | False | 965.8 ns | 16.84 ns | 15.75 ns | 0.0343 | 216 B | -| SerializeLogRecord | False | 696.5 ns | 13.94 ns | 14.92 ns | 0.0038 | 24 B | -| Export | False | 744.9 ns | 12.91 ns | 12.08 ns | 0.0038 | 24 B | -| LoggerWithMessageTemplate | True | 978.6 ns | 18.95 ns | 19.46 ns | 0.0401 | 256 B | -| LoggerWithDirectLoggerAPI | True | 878.3 ns | 11.43 ns | 10.69 ns | 0.0620 | 392 B | -| LoggerWithSourceGenerator | True | 942.8 ns | 14.55 ns | 13.61 ns | 0.0343 | 216 B | -| SerializeLogRecord | True | 707.3 ns | 9.01 ns | 8.42 ns | 0.0038 | 24 B | -| Export | True | 752.0 ns | 8.97 ns | 7.49 ns | 0.0038 | 24 B | +| Method | IncludeFormattedMessage | Mean | Error | StdDev | Gen 0 | Allocated | +|-------------------------- |------------------------ |-----------:|--------:|--------:|-------:|----------:| +| LoggerWithMessageTemplate | False | 1,273.1 ns | 6.09 ns | 5.39 ns | 0.0362 | 232 B | +| LoggerWithDirectLoggerAPI | False | 1,213.0 ns | 9.71 ns | 8.61 ns | 0.0572 | 368 B | +| LoggerWithSourceGenerator | False | 1,243.5 ns | 6.13 ns | 5.44 ns | 0.0305 | 192 B | +| SerializeLogRecord | False | 587.7 ns | 2.71 ns | 2.54 ns | - | - | +| Export | False | 955.0 ns | 5.46 ns | 5.11 ns | - | - | +| LoggerWithMessageTemplate | True | 1,261.1 ns | 6.59 ns | 5.84 ns | 0.0362 | 232 B | +| LoggerWithDirectLoggerAPI | True | 1,214.4 ns | 4.56 ns | 4.27 ns | 0.0572 | 368 B | +| LoggerWithSourceGenerator | True | 1,229.6 ns | 6.84 ns | 6.40 ns | 0.0305 | 192 B | +| SerializeLogRecord | True | 581.6 ns | 2.38 ns | 2.11 ns | - | - | +| Export | True | 958.4 ns | 3.02 ns | 2.52 ns | - | - | With Scopes (https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/545) -| Method | IncludeFormattedMessage | Mean | Error | StdDev | Median | Gen 0 | Allocated | -|-------------------------- |------------------------ |-----------:|---------:|---------:|-----------:|-------:|----------:| -| LoggerWithMessageTemplate | False | 1,042.8 ns | 19.34 ns | 54.55 ns | 1,022.1 ns | 0.0572 | 360 B | -| LoggerWithDirectLoggerAPI | False | 953.4 ns | 13.90 ns | 13.00 ns | 950.4 ns | 0.0782 | 496 B | -| LoggerWithSourceGenerator | False | 962.1 ns | 18.93 ns | 17.71 ns | 957.6 ns | 0.0496 | 320 B | -| SerializeLogRecord | False | 722.8 ns | 6.26 ns | 5.23 ns | 722.9 ns | 0.0200 | 128 B | -| Export | False | 789.2 ns | 15.11 ns | 14.14 ns | 787.3 ns | 0.0200 | 128 B | -| LoggerWithMessageTemplate | True | 986.8 ns | 12.56 ns | 11.13 ns | 983.4 ns | 0.0572 | 360 B | -| LoggerWithDirectLoggerAPI | True | 932.1 ns | 18.25 ns | 20.29 ns | 924.7 ns | 0.0782 | 496 B | -| LoggerWithSourceGenerator | True | 980.0 ns | 15.56 ns | 14.55 ns | 979.6 ns | 0.0496 | 320 B | -| SerializeLogRecord | True | 737.5 ns | 13.46 ns | 12.59 ns | 738.8 ns | 0.0200 | 128 B | -| Export | True | 772.2 ns | 14.02 ns | 13.11 ns | 774.8 ns | 0.0200 | 128 B | +| Method | IncludeFormattedMessage | Mean | Error | StdDev | Gen 0 | Allocated | +|-------------------------- |------------------------ |-----------:|--------:|--------:|-------:|----------:| +| LoggerWithMessageTemplate | False | 1,280.8 ns | 7.45 ns | 6.61 ns | 0.0534 | 336 B | +| LoggerWithDirectLoggerAPI | False | 1,261.5 ns | 6.38 ns | 5.96 ns | 0.0744 | 472 B | +| LoggerWithSourceGenerator | False | 1,309.3 ns | 4.83 ns | 4.52 ns | 0.0458 | 296 B | +| SerializeLogRecord | False | 611.3 ns | 4.63 ns | 4.11 ns | 0.0162 | 104 B | +| Export | False | 1,012.2 ns | 7.56 ns | 7.07 ns | 0.0153 | 104 B | +| LoggerWithMessageTemplate | True | 1,278.3 ns | 6.63 ns | 5.88 ns | 0.0534 | 336 B | +| LoggerWithDirectLoggerAPI | True | 1,263.8 ns | 8.26 ns | 7.73 ns | 0.0744 | 472 B | +| LoggerWithSourceGenerator | True | 1,273.4 ns | 5.57 ns | 5.21 ns | 0.0458 | 296 B | +| SerializeLogRecord | True | 604.3 ns | 2.83 ns | 2.65 ns | 0.0162 | 104 B | +| Export | True | 1,003.6 ns | 9.29 ns | 8.69 ns | 0.0153 | 104 B | */ namespace OpenTelemetry.Exporter.Geneva.Benchmark; @@ -65,7 +65,7 @@ public class LogExporterBenchmarks { private readonly ILogger logger; private readonly ILoggerFactory loggerFactory; - private readonly GenevaLogExporter exporter; + private readonly MsgPackLogExporter exporter; private readonly LogRecord logRecord; private readonly Batch batch; @@ -97,7 +97,7 @@ public LogExporterBenchmarks() // For msgpack serialization + export this.logRecord = GenerateTestLogRecord(); this.batch = GenerateTestLogRecordBatch(); - this.exporter = new GenevaLogExporter(new GenevaExporterOptions + this.exporter = new MsgPackLogExporter(new GenevaExporterOptions { ConnectionString = "EtwSession=OpenTelemetry", PrepopulatedFields = new Dictionary diff --git a/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/TraceExporterBenchmarks.cs b/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/TraceExporterBenchmarks.cs index 3460304ff1..3956343f72 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/TraceExporterBenchmarks.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Benchmark/Exporter/TraceExporterBenchmarks.cs @@ -14,23 +14,24 @@ // limitations under the License. // +using System; using System.Collections.Generic; using System.Diagnostics; using BenchmarkDotNet.Attributes; using OpenTelemetry.Trace; /* -BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000 +BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22621 Intel Core i7-9700 CPU 3.00GHz, 1 CPU, 8 logical and 8 physical cores .NET SDK=7.0.100-preview.6.22352.1 - [Host] : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT - DefaultJob : .NET 6.0.8 (6.0.822.36306), X64 RyuJIT + [Host] : .NET 6.0.9 (6.0.922.41905), X64 RyuJIT + DefaultJob : .NET 6.0.9 (6.0.922.41905), X64 RyuJIT | Method | Mean | Error | StdDev | Gen 0 | Allocated | |------------------ |---------:|--------:|--------:|-------:|----------:| -| ExportActivity | 422.0 ns | 8.41 ns | 8.64 ns | 0.0062 | 40 B | -| SerializeActivity | 387.4 ns | 7.22 ns | 7.09 ns | 0.0062 | 40 B | +| ExportActivity | 719.9 ns | 5.83 ns | 5.45 ns | 0.0057 | 40 B | +| SerializeActivity | 361.7 ns | 1.09 ns | 0.97 ns | 0.0062 | 40 B | */ namespace OpenTelemetry.Exporter.Geneva.Benchmark; @@ -40,7 +41,7 @@ public class TraceExporterBenchmarks { private readonly Activity activity; private readonly Batch batch; - private readonly GenevaTraceExporter exporter; + private readonly MsgPackTraceExporter exporter; private readonly ActivitySource activitySource = new ActivitySource("OpenTelemetry.Exporter.Geneva.Benchmark"); public TraceExporterBenchmarks() @@ -67,7 +68,7 @@ public TraceExporterBenchmarks() this.activity?.SetStatus(Status.Error); } - this.exporter = new GenevaTraceExporter(new GenevaExporterOptions + this.exporter = new MsgPackTraceExporter(new GenevaExporterOptions { ConnectionString = "EtwSession=OpenTelemetry", PrepopulatedFields = new Dictionary diff --git a/test/OpenTelemetry.Exporter.Geneva.Benchmark/OpenTelemetry.Exporter.Geneva.Benchmark.csproj b/test/OpenTelemetry.Exporter.Geneva.Benchmark/OpenTelemetry.Exporter.Geneva.Benchmark.csproj index ca229c6b57..57236f7a96 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Benchmark/OpenTelemetry.Exporter.Geneva.Benchmark.csproj +++ b/test/OpenTelemetry.Exporter.Geneva.Benchmark/OpenTelemetry.Exporter.Geneva.Benchmark.csproj @@ -8,7 +8,7 @@ - + diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs index d86edadca5..1014c7b2da 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaLogExporterTests.cs @@ -171,7 +171,7 @@ public void TableNameMappingTest(params string[] category) .AddFilter("*", LogLevel.Trace)); // Enable all LogLevels // Create a test exporter to get MessagePack byte data to validate if the data was serialized correctly. - using var exporter = new GenevaLogExporter(exporterOptions); + using var exporter = new MsgPackLogExporter(exporterOptions); ILogger logger; ThreadLocal m_buffer; @@ -188,7 +188,7 @@ public void TableNameMappingTest(params string[] category) logger.LogError("this does not matter"); Assert.Single(logRecordList); - m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); actualTableName = (fluentdData as object[])[0] as string; @@ -206,7 +206,7 @@ public void TableNameMappingTest(params string[] category) logger.LogError("this does not matter"); Assert.Single(logRecordList); - m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); actualTableName = (fluentdData as object[])[0] as string; @@ -293,13 +293,13 @@ public void PassThruTableMappingsWhenTheRuleIsEnabled() .AddFilter("*", LogLevel.Trace)); // Enable all LogLevels // Create a test exporter to get MessagePack byte data to validate if the data was serialized correctly. - using var exporter = new GenevaLogExporter(exporterOptions); + using var exporter = new MsgPackLogExporter(exporterOptions); ILogger passThruTableMappingsLogger, userInitializedTableMappingsLogger; ThreadLocal m_buffer; object fluentdData; string actualTableName; - m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; // Verify that the category table mappings specified by the users in the Geneva Configuration are mapped correctly. foreach (var mapping in userInitializedCategoryToTableNameMappings) @@ -407,7 +407,7 @@ public void SerializeILoggerScopes() byte[] serializedData; if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - var m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + var m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; serializedData = m_buffer.Value; } else @@ -504,7 +504,7 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) .AddFilter(typeof(GenevaLogExporterTests).FullName, LogLevel.Trace)); // Enable all LogLevels // Create a test exporter to get MessagePack byte data to validate if the data was serialized correctly. - using var exporter = new GenevaLogExporter(exporterOptions); + using var exporter = new MsgPackLogExporter(exporterOptions); // Emit a LogRecord and grab a copy of the LogRecord from the collection passed to InMemoryExporter var logger = loggerFactory.CreateLogger(); @@ -524,7 +524,7 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) // VALIDATE Assert.Single(logRecordList); - var m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + var m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); object fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); var body = GetField(fluentdData, "body"); @@ -554,7 +554,7 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) // VALIDATE Assert.Single(logRecordList); - m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); body = GetField(fluentdData, "body"); @@ -581,7 +581,7 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) // VALIDATE Assert.Single(logRecordList); - m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); body = GetField(fluentdData, "body"); @@ -606,7 +606,7 @@ public void SerializationTestWithILoggerLogMethod(bool includeFormattedMessage) // VALIDATE Assert.Single(logRecordList); - m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); Assert.Equal("Value1", GetField(fluentdData, "Key1")); @@ -706,7 +706,7 @@ public void SerializationTestWithILoggerLogWithTemplates(bool hasTableNameMappin .AddFilter(typeof(GenevaLogExporterTests).FullName, LogLevel.Trace)); // Enable all LogLevels // Create a test exporter to get MessagePack byte data to validate if the data was serialized correctly. - using var exporter = new GenevaLogExporter(exporterOptions); + using var exporter = new MsgPackLogExporter(exporterOptions); // Emit a LogRecord and grab a copy of the LogRecord from the collection passed to InMemoryExporter var logger = loggerFactory.CreateLogger(); @@ -751,7 +751,7 @@ public void SerializationTestWithILoggerLogWithTemplates(bool hasTableNameMappin // logRecordList should have 14 logRecord entries as there were 14 Log calls Assert.Equal(14, logRecordList.Count); - var m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + var m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; foreach (var logRecord in logRecordList) { @@ -842,7 +842,7 @@ public void SuccessfulExportOnLinux() serverSocket.ReceiveTimeout = 10000; // Create a test exporter to get MessagePack byte data for validation of the data received via Socket. - using var exporter = new GenevaLogExporter(new GenevaExporterOptions + using var exporter = new MsgPackLogExporter(new GenevaExporterOptions { ConnectionString = "Endpoint=unix:" + path, PrepopulatedFields = new Dictionary @@ -938,7 +938,7 @@ public void SerializationTestForException() .AddFilter(typeof(GenevaLogExporterTests).FullName, LogLevel.Trace)); // Enable all LogLevels // Create a test exporter to get MessagePack byte data to validate if the data was serialized correctly. - using var exporter = new GenevaLogExporter(exporterOptions); + using var exporter = new MsgPackLogExporter(exporterOptions); // Emit a LogRecord and grab a copy of the LogRecord from the collection passed to InMemoryExporter var logger = loggerFactory.CreateLogger(); @@ -954,7 +954,7 @@ public void SerializationTestForException() // VALIDATE Assert.Single(logRecordList); - var m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + var m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); object fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); var exceptionType = GetField(fluentdData, "env_ex_type"); @@ -1012,7 +1012,7 @@ public void SerializationTestForEventId() .AddFilter(typeof(GenevaLogExporterTests).FullName, LogLevel.Trace)); // Enable all LogLevels // Create a test exporter to get MessagePack byte data to validate if the data was serialized correctly. - using var exporter = new GenevaLogExporter(exporterOptions); + using var exporter = new MsgPackLogExporter(exporterOptions); // Emit a LogRecord and grab a copy of the LogRecord from the collection passed to InMemoryExporter var logger = loggerFactory.CreateLogger(); @@ -1028,7 +1028,7 @@ public void SerializationTestForEventId() // VALIDATE Assert.Single(logRecordList); - var m_buffer = typeof(GenevaLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + var m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; _ = exporter.SerializeLogRecord(logRecordList[0]); object fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); @@ -1130,24 +1130,24 @@ private void AssertFluentdForwardModeForLogRecord(GenevaExporterOptions exporter // Part A core envelope fields - var nameKey = GenevaBaseExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Name]; + var nameKey = MsgPackExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Name]; // Check if the user has configured a custom table mapping Assert.Equal(partAName, mapping[nameKey]); // TODO: Update this when we support multiple Schema formats var partAVer = "4.0"; - var verKey = GenevaBaseExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Ver]; + var verKey = MsgPackExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Ver]; Assert.Equal(partAVer, mapping[verKey]); foreach (var item in exporterOptions.PrepopulatedFields) { var partAValue = item.Value as string; - var partAKey = GenevaBaseExporter.V40_PART_A_MAPPING[item.Key]; + var partAKey = MsgPackExporter.V40_PART_A_MAPPING[item.Key]; Assert.Equal(partAValue, mapping[partAKey]); } - var timeKey = GenevaBaseExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Time]; + var timeKey = MsgPackExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Time]; Assert.Equal(logRecord.Timestamp.Ticks, ((DateTime)mapping[timeKey]).Ticks); // Part A dt extensions diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs index 0db23b09b7..1f8cab0c1a 100644 --- a/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/GenevaTraceExporterTests.cs @@ -250,10 +250,10 @@ public void GenevaTraceExporter_Serialization_Success(bool hasTableNameMapping, exporterOptions.CustomFields = new string[] { "clientRequestId" }; } - using var exporter = new GenevaTraceExporter(exporterOptions); - var dedicatedFields = typeof(GenevaTraceExporter).GetField("m_dedicatedFields", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(exporter) as IReadOnlyDictionary; - var CS40_PART_B_MAPPING = typeof(GenevaTraceExporter).GetField("CS40_PART_B_MAPPING", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as IReadOnlyDictionary; - var m_buffer = typeof(GenevaTraceExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(exporter) as ThreadLocal; + using var exporter = new MsgPackTraceExporter(exporterOptions); + var dedicatedFields = typeof(MsgPackTraceExporter).GetField("m_dedicatedFields", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(exporter) as IReadOnlyDictionary; + var CS40_PART_B_MAPPING = typeof(MsgPackTraceExporter).GetField("CS40_PART_B_MAPPING", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as IReadOnlyDictionary; + var m_buffer = typeof(MsgPackTraceExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(exporter) as ThreadLocal; // Add an ActivityListener to serialize the activity and assert that it was valid on ActivityStopped event @@ -417,7 +417,7 @@ public void GenevaTraceExporter_Success_Linux() serverSocket.ReceiveTimeout = 10000; // Create a test exporter to get MessagePack byte data for validation of the data received via Socket. - var exporter = new GenevaTraceExporter(new GenevaExporterOptions + var exporter = new MsgPackTraceExporter(new GenevaExporterOptions { ConnectionString = "Endpoint=unix:" + path, PrepopulatedFields = new Dictionary @@ -526,24 +526,24 @@ private void AssertFluentdForwardModeForActivity(GenevaExporterOptions exporterO // Part A core envelope fields - var nameKey = GenevaBaseExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Name]; + var nameKey = MsgPackExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Name]; // Check if the user has configured a custom table mapping Assert.Equal(partAName, mapping[nameKey]); // TODO: Update this when we support multiple Schema formats var partAVer = "4.0"; - var verKey = GenevaBaseExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Ver]; + var verKey = MsgPackExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Ver]; Assert.Equal(partAVer, mapping[verKey]); foreach (var item in exporterOptions.PrepopulatedFields) { var partAValue = item.Value as string; - var partAKey = GenevaBaseExporter.V40_PART_A_MAPPING[item.Key]; + var partAKey = MsgPackExporter.V40_PART_A_MAPPING[item.Key]; Assert.Equal(partAValue, mapping[partAKey]); } - var timeKey = GenevaBaseExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Time]; + var timeKey = MsgPackExporter.V40_PART_A_MAPPING[Schema.V40.PartA.Time]; Assert.Equal(tsEnd, ((DateTime)mapping[timeKey]).Ticks); // Part A dt extensions diff --git a/test/OpenTelemetry.Exporter.Geneva.Tests/LogSerializationTests.cs b/test/OpenTelemetry.Exporter.Geneva.Tests/LogSerializationTests.cs new file mode 100644 index 0000000000..69cbf74d4a --- /dev/null +++ b/test/OpenTelemetry.Exporter.Geneva.Tests/LogSerializationTests.cs @@ -0,0 +1,149 @@ +// +// 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.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using Microsoft.Extensions.Logging; +using OpenTelemetry.Logs; +using Xunit; +using Xunit.Abstractions; + +namespace OpenTelemetry.Exporter.Geneva.Tests; + +public class LogSerializationTests +{ + /* + Run from the current directory: + dotnet test -f net6.0 --filter FullyQualifiedName~LogSerializationTests -l "console;verbosity=detailed" + */ + private readonly ITestOutputHelper output; + + public LogSerializationTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void SerializationTestForException() + { + var exceptionMessage = "Exception Message"; + var exportedFields = GetExportedFieldsAfterLogging(logger => logger.Log( + logLevel: LogLevel.Information, + eventId: default, + state: null, + exception: new Exception(exceptionMessage), + formatter: null)); + + PrintFields(this.output, exportedFields); + var actualExceptionMessage = exportedFields["env_ex_msg"]; + Assert.Equal(exceptionMessage, actualExceptionMessage); + } + + private static void PrintFields(ITestOutputHelper output, Dictionary fields) + { + foreach (var field in fields) + { + output.WriteLine($"{field.Key}:{field.Value}"); + } + } + + private static Dictionary GetExportedFieldsAfterLogging(Action doLog) + { + Socket server = null; + string path = string.Empty; + try + { + var logRecordList = new List(); + using var loggerFactory = LoggerFactory.Create(builder => builder + .AddOpenTelemetry(options => + { + options.AddInMemoryExporter(logRecordList); + }) + .AddFilter(typeof(GenevaLogExporterTests).FullName, LogLevel.Trace)); // Enable all LogLevels + + var logger = loggerFactory.CreateLogger(); + doLog(logger); + + Assert.Single(logRecordList); + var exporterOptions = new GenevaExporterOptions(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + exporterOptions.ConnectionString = "EtwSession=OpenTelemetry"; + } + else + { + path = GenerateTempFilePath(); + exporterOptions.ConnectionString = "Endpoint=unix:" + path; + var endpoint = new UnixDomainSocketEndPoint(path); + server = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP); + server.Bind(endpoint); + server.Listen(1); + } + + using var exporter = new MsgPackLogExporter(exporterOptions); + var m_buffer = typeof(MsgPackLogExporter).GetField("m_buffer", BindingFlags.NonPublic | BindingFlags.Static).GetValue(exporter) as ThreadLocal; + _ = exporter.SerializeLogRecord(logRecordList[0]); + object fluentdData = MessagePack.MessagePackSerializer.Deserialize(m_buffer.Value, MessagePack.Resolvers.ContractlessStandardResolver.Instance); + + return GetFields(fluentdData); + } + finally + { + server?.Dispose(); + try + { + File.Delete(path); + } + catch + { + } + } + } + + private static string GenerateTempFilePath() + { + while (true) + { + string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + if (!File.Exists(path)) + { + return path; + } + } + } + + private static Dictionary GetFields(object fluentdData) + { + /* Fluentd Forward Mode: + [ + "Log", + [ + [ , { "env_ver": "4.0", ... } ] + ], + { "TimeFormat": "DateTime" } + ] + */ + + var TimeStampAndMappings = ((fluentdData as object[])[1] as object[])[0]; + var mapping = (TimeStampAndMappings as object[])[1] as Dictionary; + return mapping; + } +} diff --git a/test/OpenTelemetry.Exporter.Instana.Tests/InstanaSpanTest.cs b/test/OpenTelemetry.Exporter.Instana.Tests/InstanaSpanTest.cs index a72c9cf878..7f8a4d6b06 100644 --- a/test/OpenTelemetry.Exporter.Instana.Tests/InstanaSpanTest.cs +++ b/test/OpenTelemetry.Exporter.Instana.Tests/InstanaSpanTest.cs @@ -136,7 +136,7 @@ internal class SpanEvent } #pragma warning disable SA1402 // File may only contain a single type -public class DataConverter : JsonConverter +internal class DataConverter : JsonConverter #pragma warning restore SA1402 // File may only contain a single type { public override bool CanWrite => false; diff --git a/test/OpenTelemetry.Extensions.PersistentStorage.Tests/FileBlobProviderTests.cs b/test/OpenTelemetry.Extensions.PersistentStorage.Tests/FileBlobProviderTests.cs index 2e56912904..23be0a0f54 100644 --- a/test/OpenTelemetry.Extensions.PersistentStorage.Tests/FileBlobProviderTests.cs +++ b/test/OpenTelemetry.Extensions.PersistentStorage.Tests/FileBlobProviderTests.cs @@ -78,7 +78,7 @@ public void FileBlobProvider_TestRetentionPeriod() var testDirectory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName())); long maxSizeInBytes = 100000; int maintenancePeriodInMilliseconds = 3000; - int retentionPeriodInMilliseconds = 2000; + int retentionPeriodInMilliseconds = 1000; int writeTimeOutInMilliseconds = 1000; using var blobProvider = new FileBlobProvider( testDirectory.FullName, @@ -90,9 +90,10 @@ public void FileBlobProvider_TestRetentionPeriod() var data = Encoding.UTF8.GetBytes("Hello, World!"); Assert.True(blobProvider.TryCreateBlob(data, out var blob)); - // Wait for maintenance job to run - // TODO: reduce/eliminate sleep time - Thread.Sleep(4000); + // Wait for rentention deadline to expire + Thread.Sleep(2000); + var retentionDeadline = DateTime.UtcNow - TimeSpan.FromMilliseconds(retentionPeriodInMilliseconds); + PersistentStorageHelper.RemoveExpiredBlob(retentionDeadline, ((FileBlob)blob).FullPath); // Blob will be deleted as retention period is 1 sec Assert.False(File.Exists(((FileBlob)blob).FullPath)); @@ -125,9 +126,10 @@ public void FileBlobProvider_TestWriteTimeoutPeriod() // validate file moved successfully Assert.True(File.Exists(((FileBlob)blob).FullPath + ".tmp")); - // Wait for maintenance job to run - // TODO: reduce/eliminate sleep time - Thread.Sleep(4000); + // wait for timeout period + Thread.Sleep(2000); + var timeoutDeadline = DateTime.UtcNow - TimeSpan.FromMilliseconds(writeTimeOutInMilliseconds); + PersistentStorageHelper.RemoveTimedOutTmpFiles(timeoutDeadline, ((FileBlob)blob).FullPath + ".tmp"); // tmp file will be deleted as write timeout period is 1 sec Assert.False(File.Exists(((FileBlob)blob).FullPath + ".tmp")); @@ -160,9 +162,9 @@ public void FileBlobProviderTests_TestLeaseExpiration() var leasePath = ((FileBlob)blob).FullPath; Assert.True(File.Exists(leasePath)); - // Wait for maintenance job to run - // TODO: reduce/eliminate sleep time - Thread.Sleep(4000); + // Wait for lease to expire + Thread.Sleep(2000); + PersistentStorageHelper.RemoveExpiredLease(DateTime.UtcNow, leasePath); // File name will be change to .blob Assert.True(File.Exists(blobPath)); diff --git a/test/OpenTelemetry.Instrumentation.AWSLambda.Tests/Implementation/AWSLambdaHttpUtilsTests.cs b/test/OpenTelemetry.Instrumentation.AWSLambda.Tests/Implementation/AWSLambdaHttpUtilsTests.cs new file mode 100644 index 0000000000..a5ecf93424 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.AWSLambda.Tests/Implementation/AWSLambdaHttpUtilsTests.cs @@ -0,0 +1,269 @@ +// +// 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Amazon.Lambda.APIGatewayEvents; +using Moq; +using OpenTelemetry.Instrumentation.AWSLambda.Implementation; +using OpenTelemetry.Trace; +using Xunit; + +namespace OpenTelemetry.Instrumentation.AWSLambda.Tests.Implementation +{ + public class AWSLambdaHttpUtilsTests + { + [Fact] + public void GetHttpTags_APIGatewayProxyRequest_ReturnsCorrectTags() + { + var request = new APIGatewayProxyRequest + { + MultiValueHeaders = new Dictionary> + { + { "X-Forwarded-Proto", new List { "https" } }, + { "Host", new List { "localhost:1234" } }, + }, + HttpMethod = "GET", + MultiValueQueryStringParameters = new Dictionary> + { + { "q1", new[] { "value1" } }, + }, + RequestContext = new APIGatewayProxyRequest.ProxyRequestContext + { + Path = "/path/test", + }, + }; + + var actualTags = AWSLambdaHttpUtils.GetHttpTags(request); + + var expectedTags = new Dictionary + { + { "http.scheme", "https" }, + { "http.target", "/path/test?q1=value1" }, + { "net.host.name", "localhost" }, + { "net.host.port", 1234 }, + { "http.method", "GET" }, + }; + + AssertTags(expectedTags, actualTags); + } + + [Fact] + public void GetHttpTags_APIGatewayProxyRequestWithMultiValueHeader_UsesLastValue() + { + var request = new APIGatewayProxyRequest + { + MultiValueHeaders = new Dictionary> + { + { "X-Forwarded-Proto", new List { "https", "http" } }, + { "Host", new List { "localhost:1234", "myhost:432" } }, + }, + }; + + var actualTags = AWSLambdaHttpUtils.GetHttpTags(request); + + var expectedTags = new Dictionary + { + { "http.target", string.Empty }, + { "http.scheme", "http" }, + { "net.host.name", "myhost" }, + { "net.host.port", 432 }, + }; + + AssertTags(expectedTags, actualTags); + } + + [Fact] + public void GetHttpTags_APIGatewayHttpApiV2ProxyRequest_ReturnsCorrectTags() + { + var request = new APIGatewayHttpApiV2ProxyRequest + { + Headers = new Dictionary + { + { "X-Forwarded-Proto", "https" }, + { "Host", "localhost:1234" }, + }, + RawPath = "/path/test", + RawQueryString = "q1=value1", + RequestContext = new APIGatewayHttpApiV2ProxyRequest.ProxyRequestContext + { + Http = new APIGatewayHttpApiV2ProxyRequest.HttpDescription + { + Method = "GET", + }, + }, + }; + + var actualTags = AWSLambdaHttpUtils.GetHttpTags(request); + + var expectedTags = new Dictionary + { + { "http.scheme", "https" }, + { "http.target", "/path/test?q1=value1" }, + { "net.host.name", "localhost" }, + { "net.host.port", 1234 }, + { "http.method", "GET" }, + }; + + AssertTags(expectedTags, actualTags); + } + + [Fact] + public void GetHttpTags_APIGatewayHttpApiV2ProxyRequestWithMultiValueHeader_UsesLastValue() + { + var request = new APIGatewayHttpApiV2ProxyRequest + { + Headers = new Dictionary + { + { "X-Forwarded-Proto", "https,http" }, + { "Host", "localhost:1234,myhost:432" }, + }, + }; + + var actualTags = AWSLambdaHttpUtils.GetHttpTags(request); + + var expectedTags = new Dictionary + { + { "http.target", string.Empty }, + { "http.scheme", "http" }, + { "net.host.name", "myhost" }, + { "net.host.port", 432 }, + }; + + AssertTags(expectedTags, actualTags); + } + + [Fact] + public void SetHttpTagsFromResult_APIGatewayProxyResponse_SetsCorrectTags() + { + var response = new APIGatewayProxyResponse + { + StatusCode = 200, + }; + var activityProcessor = new Mock>(); + + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddProcessor(activityProcessor.Object) + .AddSource("TestActivitySource") + .Build(); + + using var testActivitySource = new ActivitySource("TestActivitySource"); + using var activity = testActivitySource.StartActivity("TestActivity"); + + AWSLambdaHttpUtils.SetHttpTagsFromResult(activity, response); + + var expectedTags = new Dictionary + { + { "http.status_code", 200 }, + }; + AssertTags(expectedTags, activity.TagObjects); + } + + [Fact] + public void SetHttpTagsFromResult_APIGatewayHttpApiV2ProxyResponse_SetsCorrectTags() + { + var response = new APIGatewayHttpApiV2ProxyResponse + { + StatusCode = 200, + }; + var activityProcessor = new Mock>(); + + using var tracerProvider = Sdk.CreateTracerProviderBuilder() + .AddProcessor(activityProcessor.Object) + .AddSource("TestActivitySource") + .Build(); + + using var testActivitySource = new ActivitySource("TestActivitySource"); + using var activity = testActivitySource.StartActivity("TestActivity"); + + AWSLambdaHttpUtils.SetHttpTagsFromResult(activity, response); + + var expectedTags = new Dictionary + { + { "http.status_code", 200 }, + }; + AssertTags(expectedTags, activity.TagObjects); + } + + [Theory] + [InlineData(null, null, null, null)] + [InlineData("", "", "", null)] + [InlineData(null, "localhost:4321", "localhost", 4321)] + [InlineData(null, "localhost:1a", "localhost", null)] + [InlineData(null, "localhost", "localhost", null)] + [InlineData("http", "localhost", "localhost", 80)] + [InlineData("https", "localhost", "localhost", 443)] + public void GetHostAndPort_HostHeader_ReturnsCorrectHostAndPort(string httpSchema, string hostHeader, string expectedHost, int? expectedPort) + { + (var host, var port) = AWSLambdaHttpUtils.GetHostAndPort(httpSchema, hostHeader); + + Assert.Equal(expectedHost, host); + Assert.Equal(expectedPort, port); + } + + [Theory] + [InlineData(null, "")] + [InlineData(new string[] { }, "")] + [InlineData(new[] { "value1" }, "?name=value1")] + [InlineData(new[] { "value$a" }, "?name=value%24a")] + [InlineData(new[] { "value 1" }, "?name=value+1")] + [InlineData(new[] { "value1", "value2" }, "?name=value1&name=value2")] + public void GetQueryString_APIGatewayProxyRequest_CorrectQueryString(IList values, string expectedQueryString) + { + var request = new APIGatewayProxyRequest(); + if (values != null) + { + request.MultiValueQueryStringParameters = new Dictionary> + { + { "name", values }, + }; + } + + var queryString = AWSLambdaHttpUtils.GetQueryString(request); + + Assert.Equal(expectedQueryString, queryString); + } + + [Theory] + [InlineData(null, "")] + [InlineData("", "")] + [InlineData("name=value1", "?name=value1")] + [InlineData("sdckj9_+", "?sdckj9_+")] + public void GetQueryString_APIGatewayHttpApiV2ProxyRequest_CorrectQueryString(string rawQueryString, string expectedQueryString) + { + var request = new APIGatewayHttpApiV2ProxyRequest + { + RawQueryString = rawQueryString, + }; + + var queryString = AWSLambdaHttpUtils.GetQueryString(request); + + Assert.Equal(expectedQueryString, queryString); + } + + private static void AssertTags(IReadOnlyDictionary expectedTags, IEnumerable> actualTags) + where TActualValue : class + { + Assert.NotNull(actualTags); + Assert.Equal(expectedTags.Count, actualTags.Count()); + foreach (var tag in expectedTags) + { + Assert.Contains(new KeyValuePair(tag.Key, tag.Value as TActualValue), actualTags); + } + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.AWSLambda.Tests/Implementation/CommonExtensionsTests.cs b/test/OpenTelemetry.Instrumentation.AWSLambda.Tests/Implementation/CommonExtensionsTests.cs new file mode 100644 index 0000000000..7cecb57cb5 --- /dev/null +++ b/test/OpenTelemetry.Instrumentation.AWSLambda.Tests/Implementation/CommonExtensionsTests.cs @@ -0,0 +1,49 @@ +// +// 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.Collections.Generic; +using System.Linq; +using OpenTelemetry.Instrumentation.AWSLambda.Implementation; +using Xunit; + +namespace OpenTelemetry.Instrumentation.AWSLambda.Tests.Implementation +{ + public class CommonExtensionsTests + { + [Theory] + [InlineData("test")] + [InlineData(443)] + [InlineData(null)] + public void AddTagIfNotNull_Tag_CorrectTagsList(object tag) + { + var tags = new List>(); + + tags.AddTagIfNotNull("tagName", tag); + + if (tag != null) + { + Assert.Single(tags); + var actualTag = tags.First(); + Assert.Equal("tagName", actualTag.Key); + Assert.Equal(tag, actualTag.Value); + } + else + { + Assert.Empty(tags); + } + } + } +} diff --git a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj index 84c20ffb54..2fbd767b8b 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests/OpenTelemetry.Instrumentation.AspNet.TelemetryHttpModule.Tests.csproj @@ -2,7 +2,7 @@ Unit test project for ASP.NET HttpModule - net462 + $(NetFrameworkMinimumSupportedVersion) diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs index 0e3bd5eef2..70d477c627 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs +++ b/test/OpenTelemetry.Instrumentation.AspNet.Tests/HttpInListenerTests.cs @@ -137,7 +137,7 @@ public void AspNetRequestsAreCollectedSuccessfully( return httpContext.Request.Path != filter; }; - options.Enrich = GetEnrichmentAction(setStatusToErrorInEnrich ? Status.Error : default); + options.Enrich = GetEnrichmentAction(setStatusToErrorInEnrich ? ActivityStatusCode.Error : default); options.RecordException = recordException; }) @@ -217,27 +217,27 @@ public void AspNetRequestsAreCollectedSuccessfully( if (recordException) { - var status = span.GetStatus(); - Assert.Equal(Status.Error.StatusCode, status.StatusCode); - Assert.Equal("Operation is not valid due to the current state of the object.", status.Description); + var status = span.Status; + Assert.Equal(ActivityStatusCode.Error, span.Status); + Assert.Equal("Operation is not valid due to the current state of the object.", span.StatusDescription); } else if (setStatusToErrorInEnrich) { // This validates that users can override the // status in Enrich. - Assert.Equal(Status.Error, span.GetStatus()); + Assert.Equal(ActivityStatusCode.Error, span.Status); // Instrumentation is not expected to set status description // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode - Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); + Assert.True(string.IsNullOrEmpty(span.StatusDescription)); } else { - Assert.Equal(Status.Unset, span.GetStatus()); + Assert.Equal(ActivityStatusCode.Unset, span.Status); // Instrumentation is not expected to set status description // as the reason can be inferred from SemanticConventions.AttributeHttpStatusCode - Assert.True(string.IsNullOrEmpty(span.GetStatus().Description)); + Assert.True(string.IsNullOrEmpty(span.StatusDescription)); } } @@ -324,7 +324,7 @@ public void ExtractContextIrrespectiveOfTheFilterApplied() Assert.True(isPropagatorCalled); } - private static Action GetEnrichmentAction(Status statusToBeSet) + private static Action GetEnrichmentAction(ActivityStatusCode statusToBeSet) { void EnrichAction(Activity activity, string method, object obj) { diff --git a/test/OpenTelemetry.Instrumentation.AspNet.Tests/OpenTelemetry.Instrumentation.AspNet.Tests.csproj b/test/OpenTelemetry.Instrumentation.AspNet.Tests/OpenTelemetry.Instrumentation.AspNet.Tests.csproj index e3499a281e..1809ae8dbf 100644 --- a/test/OpenTelemetry.Instrumentation.AspNet.Tests/OpenTelemetry.Instrumentation.AspNet.Tests.csproj +++ b/test/OpenTelemetry.Instrumentation.AspNet.Tests/OpenTelemetry.Instrumentation.AspNet.Tests.csproj @@ -2,7 +2,7 @@ Unit test project for OpenTelemetry ASP.NET instrumentation - net462 + $(NetFrameworkMinimumSupportedVersion) @@ -27,7 +27,6 @@ - diff --git a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs index e227506a81..82631a34ab 100644 --- a/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs +++ b/test/OpenTelemetry.Instrumentation.Process.Tests/ProcessMetricsTests.cs @@ -15,6 +15,7 @@ // using System.Collections.Generic; +using System.Diagnostics.Metrics; using System.Linq; using OpenTelemetry.Metrics; using Xunit; @@ -36,10 +37,95 @@ public void ProcessMetricsAreCaptured() meterProvider.ForceFlush(MaxTimeToAllowForFlush); - Assert.True(exportedItems.Count == 2); + Assert.True(exportedItems.Count == 3); var physicalMemoryMetric = exportedItems.FirstOrDefault(i => i.Name == "process.memory.usage"); Assert.NotNull(physicalMemoryMetric); var virtualMemoryMetric = exportedItems.FirstOrDefault(i => i.Name == "process.memory.virtual"); Assert.NotNull(virtualMemoryMetric); + var cpuTimeMetric = exportedItems.FirstOrDefault(i => i.Name == "process.cpu.time"); + Assert.NotNull(cpuTimeMetric); + } + + [Fact] + public void CpuTimeMetricsAreCaptured() + { + var exportedItems = new List(); + using var meterProvider = Sdk.CreateMeterProviderBuilder() + .AddProcessInstrumentation() + .AddInMemoryExporter(exportedItems) + .Build(); + + meterProvider.ForceFlush(MaxTimeToAllowForFlush); + + var cpuTimeMetric = exportedItems.FirstOrDefault(i => i.Name == "process.cpu.time"); + Assert.NotNull(cpuTimeMetric); + + var userTimeCaptured = false; + var systemTimeCaptured = false; + + var points = cpuTimeMetric.GetMetricPoints().GetEnumerator(); + while (points.MoveNext() && (!userTimeCaptured || !systemTimeCaptured)) + { + foreach (var tag in points.Current.Tags) + { + if (tag.Key == "state" && tag.Value.ToString() == "user") + { + userTimeCaptured = true; + } + else if (tag.Key == "state" && tag.Value.ToString() == "system") + { + systemTimeCaptured = true; + } + } + } + + Assert.True(userTimeCaptured); + Assert.True(systemTimeCaptured); + } + + [Fact] + public void CheckValidGaugeValueWhen2MeterProviderInstancesHaveTheSameMeterName() + { + var exportedItemsA = new List(); + var exportedItemsB = new List(); + + using var meterProviderA = Sdk.CreateMeterProviderBuilder() + .AddProcessInstrumentation() + .AddInMemoryExporter(exportedItemsA) + .Build(); + + using var meterProviderB = Sdk.CreateMeterProviderBuilder() + .AddProcessInstrumentation() + .AddInMemoryExporter(exportedItemsB) + .Build(); + + meterProviderA.ForceFlush(MaxTimeToAllowForFlush); + meterProviderB.ForceFlush(MaxTimeToAllowForFlush); + + var metricA = exportedItemsA.FirstOrDefault(i => i.Name == "process.memory.usage"); + var metricB = exportedItemsB.FirstOrDefault(i => i.Name == "process.memory.usage"); + + Assert.True(GetValue(metricA) > 0); + Assert.True(GetValue(metricB) > 0); + } + + private static double GetValue(Metric metric) + { + Assert.NotNull(metric); + double sum = 0; + + foreach (ref readonly var metricPoint in metric.GetMetricPoints()) + { + if (metric.MetricType.IsGauge()) + { + sum += metricPoint.GetGaugeLastValueDouble(); + } + else if (metric.MetricType.IsDouble()) + { + sum += metricPoint.GetSumDouble(); + } + } + + return sum; } }