open-telemetry / opentelemetry-dotnet-instrumentation

OpenTelemetry .NET Automatic Instrumentation

Home Page:https://opentelemetry.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

README update: Add Oracle .NET OpenTelemetry provider

alexkeh opened this issue · comments

Oracle recently released its .NET OpenTelemetry provider that works with both Oracle.ManagedDataAccess.Core (ODP.NET Core) and Oracle.ManagedDataAccess (managed ODP.NET). Can you update the Instrumentation Libraries README Databases table with the following or something similar?

Library Tracing Support Metrics Support Databases Tested Notes
Oracle.ManagedDataAccess.Core Yes Oracle Database Requires Oracle.ManagedDataAccess.OpenTelemetry NuGet package
Oracle.ManagedDataAccess Yes Oracle Database Requires Oracle.ManagedDataAccess.OpenTelemetry NuGet package

I realize I could file a PR with the README change. However, going through the EasyCLA from a corporate standpoint is a big unknown in terms of time commitment and effort to complete when the change is straightforward and the update is from public info.

Thanks!

@alexkeh,
Great to see that Oracle driver starts shipping native support for OpenTelemetry for pre-GA releases.
I hope you have still some time to consider following feedback and improve experience.

The ultimate solution for introducing native OTel support for traces for library is to just call TracerProviderBuilder.AddSource("YouLibraryNameActivitySource") without need to depend on any other external libraries. As an example please check MassTransit package which converted library based version to direct support.

I have checked what is doing Oracle.ManagedDataAccess.OpenTelemetry and the new version of Oracle.ManagedDataAccess package and IMO you are pretty close to this solution

From Oracle.ManagedDataAccess

public sealed class OracleCommand : DbCommand, ICloneable, IReplayBase
  {
    internal static bool IsOpenTelemetryEnabled = false;
    internal static OracleActivitySource OracleActivitySource;

  (...)
  // All Calls to OracleActivitySourceName are serrounded by this condition:
      if (OracleCommand.IsOpenTelemetryEnabled)
        activity = OracleCommand.OracleActivitySource.SpanStart(callingMethodName, command);
  }

and

using Oracle.ManagedDataAccess.Client;
using OracleInternal.ConnectionPool;
using OracleInternal.ServiceObjects;
using System;
using System.Data;
using System.Diagnostics;

namespace Oracle.ManagedDataAccess.OpenTelemetry
{
  internal class OracleActivitySource
  {
    internal static readonly ActivitySource m_Source = new ActivitySource("ODPNET");

    internal bool SetDbStatementForStoredProcedure { get; set; }

    internal bool SetDbStatementForText { get; set; }

    internal bool EnableConnectionLevelAttributes { get; set; }

    internal bool InstrumentOracleDataReaderRead { get; set; }

    internal bool RecordException { get; set; }

    internal bool EnableDBRoundTripTracing { get; set; }

    internal EnableOpenCloseTracing EnableOpenCloseTracing { get; set; }

    internal bool RequireApplicationRootSpanCreation { get; set; }

    internal object SpanStart(
      string activityDisplayName,
      OracleConnectionImpl connImpl = null,
      string sqlStatement = null,
      string statementType = null,
      ConnectionString cs = null,
      object parentActivity = null)
    {
      try
      {
        if (this.RequireApplicationRootSpanCreation && Activity.Current == null)
          return (object) null;
        Activity activity = parentActivity == null || string.IsNullOrWhiteSpace((parentActivity as Activity).Id) ? OracleActivitySource.m_Source.StartActivity(activityDisplayName, ActivityKind.Client) : OracleActivitySource.m_Source.StartActivity(activityDisplayName, ActivityKind.Client, (parentActivity as Activity).Id);
        if (activity != null)
        {
          activity.IsAllDataRequested = true;
          activity.SetTag("db.system", (object) "oracle");
          if (connImpl != null && this.EnableConnectionLevelAttributes)
          {
            activity.SetTag("db.name", (object) connImpl.m_databaseName);
            if (connImpl.m_cs != null)
            {
              activity.SetTag("db.connection_string", (object) connImpl.m_cs.m_passwlessConString);
              activity.SetTag("db.user", (object) connImpl.m_cs.m_userId);
            }
            else if (cs != null)
            {
              activity.SetTag("db.connection_string", (object) cs.m_passwlessConString);
              activity.SetTag("db.user", (object) cs.m_userId);
            }
          }
          else if (cs != null && this.EnableConnectionLevelAttributes)
          {
            activity.SetTag("db.connection_string", (object) cs.m_passwlessConString);
            activity.SetTag("db.user", (object) cs.m_userId);
          }
          if (!string.IsNullOrWhiteSpace(sqlStatement))
          {
            if (statementType == CommandType.StoredProcedure.ToString() && this.SetDbStatementForStoredProcedure)
              activity.SetTag("db.statement", (object) sqlStatement);
            else if (statementType == CommandType.Text.ToString() && this.SetDbStatementForText)
              activity.SetTag("db.statement", (object) sqlStatement);
            else if (statementType == CommandType.TableDirect.ToString())
              activity.SetTag("db.statement", (object) sqlStatement);
          }
        }
        return (object) activity;
      }
      catch
      {
        return (object) null;
      }
    }

    internal object SpanStart(string activityDisplayName, OracleCommand command = null) => command != null ? this.SpanStart(activityDisplayName, command.Connection.m_oracleConnectionImpl, command.CommandText, command.CommandType.ToString()) : this.SpanStart(activityDisplayName, (OracleConnectionImpl) null, (string) null, (string) null, (ConnectionString) null, (object) null);

    internal object SpanStart(
      string activityDisplayName,
      OracleConnectionImpl connImpl = null,
      OracleCommandImpl command = null)
    {
      return command != null ? this.SpanStart(activityDisplayName, connImpl, command.m_commandText, command.m_commandType.ToString()) : this.SpanStart(activityDisplayName, connImpl, (string) null, (string) null, (ConnectionString) null, (object) null);
    }

    internal object SpanStart(
      string activityDisplayName,
      OracleConnectionImpl connImpl = null,
      OracleCommandImpl command = null,
      ConnectionString cs = null,
      object parentActivity = null)
    {
      return command != null ? this.SpanStart(activityDisplayName, connImpl, command.m_commandText, command.m_commandType.ToString(), cs, parentActivity) : this.SpanStart(activityDisplayName, connImpl, (string) null, (string) null, cs, parentActivity);
    }

    internal void SpanStop(object activity, string dbname = "")
    {
      try
      {
        if (activity == null)
          return;
        ((Activity) activity).SetTag("otel.status_code", (object) "OK");
        if (!string.IsNullOrEmpty(dbname))
          ((Activity) activity).SetTag("db.name", (object) dbname);
        ((Activity) activity).Dispose();
      }
      catch
      {
      }
    }

    internal void SetException(object activity, Exception ex)
    {
      try
      {
        if (activity == null || !this.RecordException)
          return;
        if (ex != null)
        {
          ActivityTagsCollection tags = new ActivityTagsCollection()
          {
            {
              "exception.type",
              (object) ex.GetType().FullName
            },
            {
              "exception.message",
              (object) ex.Message
            }
          };
          tags.Add("exception.stacktrace", (object) ex.StackTrace);
          ActivityEvent e = new ActivityEvent("exception", tags: tags);
          ((Activity) activity).AddEvent(e);
          ((Activity) activity).SetTag("otel.status_description", (object) ex.Message);
        }
        ((Activity) activity).SetTag("otel.status_code", (object) "ERROR");
        ((Activity) activity).Dispose();
      }
      catch
      {
      }
    }

    internal void SetNullToCurrentActivity() => Activity.Current = (Activity) null;

    internal static class SemanticConventions
    {
      public const string DB_SYSTEM_NAME = "oracle";
      public const string SOURCE_NAME = "ODPNET";
      public const string DB_SYSTEM = "db.system";
      public const string DB_NAME = "db.name";
      public const string DB_CONNECTIONSTRING = "db.connection_string";
      public const string DB_USER = "db.user";
      public const string DB_STATEMENT = "db.statement";
      public const string OTEL_STATUS_CODE = "otel.status_code";
      public const string OTEL_DESCRIPTION = "otel.status_description";
      public const string OK = "OK";
      public const string ERROR = "ERROR";
      public const string EXCEPTION_TYPE = "exception.type";
      public const string EXCEPTION_MESSAGE = "exception.message";
      public const string EXCEPTION_STACKTRACE = "exception.stacktrace";
      public const string EXCEPTION = "exception";
    }
  }
}

From Oracle.ManagedDataAccess.OpenTelemetry

using OpenTelemetry.Trace;
using Oracle.ManagedDataAccess.Client;
using System;

namespace Oracle.ManagedDataAccess.OpenTelemetry
{
  public static class TracerProviderBuilderExtensions
  {
    public static TracerProviderBuilder AddOracleDataProviderInstrumentation(
      this TracerProviderBuilder builder,
      Action<OracleDataProviderInstrumentationOptions> configureInstrumentationOptions = null)
    {
      OracleDataProviderInstrumentationOptions instrumentationOptions = new OracleDataProviderInstrumentationOptions();
      if (configureInstrumentationOptions != null)
        configureInstrumentationOptions(instrumentationOptions);
      OracleActivitySource oracleActivitySource = new OracleActivitySource();
      oracleActivitySource.EnableConnectionLevelAttributes = instrumentationOptions.EnableConnectionLevelAttributes;
      oracleActivitySource.SetDbStatementForStoredProcedure = instrumentationOptions.SetDbStatementForStoredProcedure;
      oracleActivitySource.SetDbStatementForText = instrumentationOptions.SetDbStatementForText;
      oracleActivitySource.RecordException = instrumentationOptions.RecordException;
      oracleActivitySource.InstrumentOracleDataReaderRead = instrumentationOptions.InstrumentOracleDataReaderRead;
      oracleActivitySource.RequireApplicationRootSpanCreation = instrumentationOptions.RequireApplicationRootSpanCreation;
      oracleActivitySource.EnableDBRoundTripTracing = instrumentationOptions.EnableDBRoundTripTracing;
      oracleActivitySource.EnableOpenCloseTracing = instrumentationOptions.EnableOpenCloseTracing;
      OracleCommand.IsOpenTelemetryEnabled = true;
      OracleCommand.OracleActivitySource = oracleActivitySource;
      builder.AddSource("ODPNET");
      return builder;
    }
  }
}

Please consider following changes:

  1. [ActivitySource](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.activitysource?view=net-8.0) is designed in this way that it will create activity only if there is a listener for the activity. Otherwise it will return a null. Based on this you can make more expensive extensions and add additional attributes/tags.
    Based on this, consider to always creating ActivitySource in Oracle.ManagedDataAccess and fully remove IsOpenTelemetryEnabled property.
  2. ActivitySource name is now ODPNET. From my perspective it is not so obvious. Consider make it equal to you library name: Oracle.ManagedDataAccess.
  3. You should be able to remove otel.status_code and otel.status_description - ref open-telemetry/opentelemetry-dotnet#2569. There are better properties to track this. Keep in mind that the OK status should not be manually set. Keeping Unset if preferred solution.
  4. Please check what you can do with other recommended attributes from https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/database/database-spans.md
  5. I think that Oracle.ManagedDataAccess.OpenTelemetry package can be still useful as an optional option to configure your instrumentation (setting options) and registering AcitivitySource name in the OpenTelemetry. Based scenario without reference to this, and only calling ``TracerProviderBuilder.AddSource("Oracle.ManagedDataAccess")` should be supported.

Based on your answers, we can update documentation/add support for Oracle db in AutomaticInstrumentation.
In the current state, it sill can implemented in autoinstrumentation, but probably using some "dark magic" aka reflection. To avoid direct dependency on Oracle databases.

BTW Based on this: https://opentelemetry.devstats.cncf.io/d/5/companies-table?orgId=1&var-period_name=Last%20month&var-metric=contributions Oracle is pretty important contributor to OpenTelemetry. You can check with @marcalff how to achieve contributor status.

Thanks for the detailed feedback @Kielek. I will discuss these recommendations with the Oracle .NET dev team.

@alexkeh, do you have any plan to address this feedback? Or you keep current way for the implementation?

@Kielek Due to the holidays and different team members being on vacation, including myself, I haven't had a chance to discuss these recommendations with the full dev team yet.

@Kielek
I had a chance to discuss your recommendations with the ODP.NET team. Here's our action plan and feedback:

ActivitySource is designed in this way that it will create activity only if there is a listener for the activity. Otherwise it will return a null. Based on this you can make more expensive extensions and add additional attributes/tags.
Based on this, consider to always creating ActivitySource in Oracle.ManagedDataAccess and fully remove IsOpenTelemetryEnabled property.

When we developed ODP.NET OpenTelemetry we emulated API behavior in other OpenTelemetry providers, such as Microsoft SqlClient and Npgsql. Those providers don't appear to be following this recommendation. Yet, they are supported by autoinstrumentation. Is there a reason why ODP.NET specifically needs to follow this recommendation?

ActivitySource name is now ODPNET. From my perspective it is not so obvious. Consider make it equal to you library name: Oracle.ManagedDataAccess.

We plan to change the name to "ODP" for the ODP.NET Core provider, which runs in .NET (Core) and "ODPM" for the managed ODP.NET provider, which runs in .NET Framework.

You should be able to remove otel.status_code and otel.status_description - ref open-telemetry/opentelemetry-dotnet#2569. There are better properties to track this. Keep in mind that the OK status should not be manually set. Keeping Unset if preferred solution.

We're planning to add these changes.

Please check what you can do with other recommended attributes from https://github.com/open-telemetry/semantic-conventions/blob/v1.24.0/docs/database/database-spans.md

We're planning to add these changes.

I think that Oracle.ManagedDataAccess.OpenTelemetry package can be still useful as an optional option to configure your instrumentation (setting options) and registering AcitivitySource name in the OpenTelemetry. Based scenario without reference to this, and only calling ``TracerProviderBuilder.AddSource("Oracle.ManagedDataAccess")` should be supported.

Ok.

@Kielek I had a chance to discuss your recommendations with the ODP.NET team. Here's our action plan and feedback:

ActivitySource is designed in this way that it will create activity only if there is a listener for the activity. Otherwise it will return a null. Based on this you can make more expensive extensions and add additional attributes/tags.
Based on this, consider to always creating ActivitySource in Oracle.ManagedDataAccess and fully remove IsOpenTelemetryEnabled property.

When we developed ODP.NET OpenTelemetry we emulated API behavior in other OpenTelemetry providers, such as Microsoft SqlClient and Npgsql. Those providers don't appear to be following this recommendation. Yet, they are supported by autoinstrumentation. Is there a reason why ODP.NET specifically needs to follow this recommendation?

Microsoft SqlClient instrumentation is not the best way to follow. It was developed before the OpenTelemetry was popular and it internally utilize DiagnosticListener to translate events to the Activities. It requires as to reference additional library to consume these functionality. I suppose that, int future, new versions of the libraries will be rewritten to provide native support for OTel by the ActivitySource.

Npqsql is a good example how to develop implementation for OTel native support.
It boths can be enabled by calling AddSource("Npgsql") (this project is using this approach) or by calling AddNpgsql() from additional package on the BuildTracer. Both solution brings exactly the same effects. Npgsql is not implementic any magic configuration IsOpenTelemetryEnabled. For some reasons, it is using manual check if anything is listening on the activity source. It can be easily swiched to StartMethod activity and null check. The performance should be similar as activity is only created when the listenr (OTel) is active. BTW technically, the OTel is not the only user of the ActivitySources. Other functionalities can listen on the same ActivitySource.

Npgsql-like design allow us to enable the support for the any library without needs to reference any additional library (which is bad, because brings dependencies which could make a conflicts in runtime) and without using reflection (as forcing to enable IsOpenTelemetryEnabled feature.

ActivitySource name is now ODPNET. From my perspective it is not so obvious. Consider make it equal to you library name: Oracle.ManagedDataAccess.

We plan to change the name to "ODP" for the ODP.NET Core provider, which runs in .NET (Core) and "ODPM" for the managed ODP.NET provider, which runs in .NET Framework.

My recommendation for both, it should be "Oracle.ManagedDataAccess". The language, framework can be deducted based on the resource attributes attached to the traces. See OpenTelemetry.ResourceDetectors.ProcessRuntime.


If your code is somehow publicly available, I can find some time to provide PR/code changes with the discussed feedback.

@Kielek Is the reason you advise the removal of the internal property IsOpenTelemetryEnabled to allow generating instrumentation at all times or is there another reason?

@Kielek Is the reason you advise the removal of the internal property IsOpenTelemetryEnabled to allow generating instrumentation at all times or is there another reason?

It is how the ActivitySource API is designed. Everything from the client perspective, should be to call "AddSource" to the OTel (or any other activity source listener). There should be no need to look into any internal details of the library.

As a reference please check:
From: https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/docs/trace/extending-the-sdk#instrumentation-library

If you are writing a new library or modifying an existing library the recommendation is to use the ActivitySource API/OpenTelemetry API to emit activity/span instances directly. If a library is instrumented using the ActivitySource API then there isn't a need for a separate instrumentation library to exist. Users simply need to configure the OpenTelemetry SDK to listen to the ActivitySource used by the library by calling AddSource on the TracerProviderBuilder being configured. The following section is applicable only if you are writing an instrumentation library for something you cannot modify to emit activity/span instances directly.

And the MS official documentation:
https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs?source=recommendations#best-practices-2

To sum up,

Any library natively supporting should call

var activity = activitySource.StartActivity().

Then you should check

if (activity !-= null and activity.IsAllDataRequested)
{
    activity.SetAllExpensiveTagsHere();
}

Please let me know if it answers your doubts.

Then you should check

if (activity !-= null and activity.AllDataRequested)
{
    activity.SetAllExpensiveTagsHere();
}

+1, also check https://github.com/open-telemetry/opentelemetry-dotnet/blob/0a77cc3cced0be0a24dd54cd4707cf71c2b97314/src/OpenTelemetry.Api/README.md?plain=1#L210-L222

@alexkeh, do you have any progress in Oracle instrumentation?

@Kielek We plan to release a new ODP.NET OpenTelemetry version this month. It will include some of the changes you recommend.

@Kielek Oracle released a new ODP.NET OpenTelemetry provider this week. We've implemented all of the suggestions you made in December. Thanks for evaluating our previous release and supplying the detailed feedback.

Is there anything else recommended to enable automatic instrumentation?

Hi @Kielek
I work in the ODP.NET team along with @alexkeh .

We wanted some clarification from you regarding the following statement that you mentioned in one of your posts above.

Based on your answers, we can update documentation/add support for Oracle db in AutomaticInstrumentation.

From what we understand, what we have done for ODP.NET’s support for open telemetry is manual instrumentation as we have manually instrumented our code using System.Diagnostics API’s.

Here is a link which describes about open telemetry automatic instrumentation.

From what is mentioned in above link, I don’t think what ODP.NET is done can be classified as automatic instrumentation.

Please could you clarify if my understanding is correct?

@alexkeh, @pfdsilva, based on short review I suppose that you can check your application with 1.4.0 release. You can set OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES to Oracle.ManagedDataAccess,Oracle.ManagedDataAccess.Core. It should work.

I have a plan to add support for these two ActvitySources by default. It probably happen after March 25, next week I will have very limited time to work on this.

One more important change in db semantic convention open-telemetry/semantic-conventions#769
As you are still in prerelezse mode, consider fully removing db.connection_string attribute.

@Kielek I have a test app that uses ODP.NET's ODPC 23c provider. I was able to enable ODPC's open telemetry traces by doing the following.

  1. Add the following environment variables.

OTEL_DOTNET_AUTO_TRACES_ADDITIONAL_SOURCES=Oracle.ManagedDataAccess.Core
OTEL_DOTNET_AUTO_TRACES_CONSOLE_EXPORTER_ENABLED=true

  1. Add OpenTelemetry.AutoInstrumentation nuget to the test app.

dotnet add package OpenTelemetry.AutoInstrumentation

  1. Build the test app using the following command:

dotnet build PROJECT_NAME.csproj -r <Runtime Identifier (RID)>

  1. Execute the test app from the project directory build folder using the following command:

instrument.cmd dotnet PROJECT_NAME.dll -r <Runtime Identifier (RID)>

@pfdsilva, @alexkeh,
please, check this PR #3336 with support for both Oracle.ManagedDataAccess.Core and Oracle.ManagedDataAccess.

The plan is to keep it as a draft until you release stable version of packages.

Additional feedback after testing:
2 Important:

  • please drop db.connection_string attribute as mentioned in #3192 (comment)
  • consider adding versions to the place where you are creating ActivitySource. Just replace new ActivitySource("Oracle.ManagedDataAccess");bynew ActivitySource("Oracle.ManagedDataAccess", "your library version here, it can be nuget package version")`. It could be beneficial for users/support to easily verify what library version is used (to verify if there is no known issues which can be fixed just by upgrade).
    1 nice to have, it will make AutoInstrumentation easier:
  • It will be great to have possibility to set-up SetDbStatementForText by environmental variable. With this support I could just remove this whole dark magic (reflection) here 9746fa3

It is great to see such progress in Oracle instrumentation. It is pretty high on our users whish list!

@Kielek
Please could you let us know about this?

It will be great to have possibility to set-up SetDbStatementForText by environmental variable.

@Kielek Please could you let us know about this?

It will be great to have possibility to set-up SetDbStatementForText by environmental variable.

Ref 9746fa3

It allows to configure Oracle option by environmental variable. Technically we can have it here, but it should be easier (for us) to read this values eg in constructor or by IConfiguration with defaulting to Env. Vars.: Something like:

namespace Oracle.ManagedDataAccess.OpenTelemetry
{
  internal class OracleActivitySource
  {
    internal static readonly ActivitySource m_Source = new ActivitySource("Oracle.ManagedDataAccess");

    internal bool SetDbStatementForStoredProcedure { get; set; } = ReadBoolFromEnvVar(name: "OTEL_ORAC:LEMDA_SETDBSTATEMENTFORSTOREDPROCEDURE", default: true);

    internal bool SetDbStatementForText { get; set; ) = ReadBoolFromEnvVar(name: "OTEL_ORAC:LEMDA_SETDBSTATEMENTFORTEXT", default: false);

    internal bool EnableConnectionLevelAttributes { get; set; }

    internal bool InstrumentOracleDataReaderRead { get; set; }

    internal bool RecordException { get; set; }

    internal bool EnableDBRoundTripTracing { get; set; } = true;

    internal EnableOpenCloseTracing EnableOpenCloseTracing { get; set; }

    internal bool RequireApplicationRootSpanCreation { get; set; }

    internal bool AddDBInfoToDisplayName { get; set; }

    internal bool EnableSqlIdTracing { get; set; }

@Kielek
Have any of the other OpenTelemetry providers implemented this? We would like to try it out.

It allows to configure Oracle option by environmental variable. Technically we can have it here, but it should be easier (for us) to read this values eg in constructor or by IConfiguration with defaulting to Env.

Does any of the other open telemetry providers support something similar?

consider adding versions to the place where you are creating ActivitySource. Just replace new ActivitySource("Oracle.ManagedDataAccess");bynew ActivitySource("Oracle.ManagedDataAccess", "your library version here, it can be nuget package version")`. It could be beneficial for users/support to easily verify what library version is used (to verify if there is no known issues which can be fixed just by upgrade).

@Kielek We plan to release a stable version of ODP.NET OpenTelemetry by the end of the week of April 29. I'll let you know the day it happens.

@Kielek Have any of the other OpenTelemetry providers implemented this? We would like to try it out.

It allows to configure Oracle option by environmental variable. Technically we can have it here, but it should be easier (for us) to read this values eg in constructor or by IConfiguration with defaulting to Env.

Does any of the other open telemetry providers support something similar?

consider adding versions to the place where you are creating ActivitySource. Just replace new ActivitySource("Oracle.ManagedDataAccess");bynew ActivitySource("Oracle.ManagedDataAccess", "your library version here, it can be nuget package version")`. It could be beneficial for users/support to easily verify what library version is used (to verify if there is no known issues which can be fixed just by upgrade).

@pfdsilva,
If we speaking, about environemtal variables - I am not aware about such support in instrumentation libraries nor in natively instrumented libraries. But Oracle can be good example. For sure, it is not mandatory, but will reduce maintanance cost on OTel AutoInstrumentation side.

Versioning - almost all instrumetnation packages in https://github.com/open-telemetry/opentelemetry-dotnet-contrib and https://github.com/open-telemetry/opentelemetry-dotnet report library version by new ActivitySource
(name, version). Few weeks ago it was even fixed by this PR open-telemetry/opentelemetry-dotnet-contrib#1624. I would strongly recommend changing it.

@Kielek Oracle has released its Oracle.OpenTelemetry provider to production on NuGet Gallery. The version is 23.4. Please commit your changes to the .NET OpenTelemetry libraries and update the README.

Thanks!

@alexkeh, #3336 is already updated.
Expected to be merged soon and release within 1.7.0.

Thank you for delivering this support.