diff --git a/.github/workflows/ci-concurrency.yml b/.github/workflows/ci-concurrency.yml
index 2734a1df94b..1a0a5bcbd0d 100644
--- a/.github/workflows/ci-concurrency.yml
+++ b/.github/workflows/ci-concurrency.yml
@@ -35,7 +35,7 @@ jobs:
- name: Publish Artifacts
if: always() && !cancelled()
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}-${{ matrix.project }}-${{ matrix.version }}-coyoteoutput
path: '**/*_CoyoteOutput.*'
diff --git a/.github/workflows/publish-packages-1.0.yml b/.github/workflows/publish-packages-1.0.yml
index 41cad730f48..450cc398567 100644
--- a/.github/workflows/publish-packages-1.0.yml
+++ b/.github/workflows/publish-packages-1.0.yml
@@ -36,7 +36,7 @@ jobs:
run: dotnet pack OpenTelemetry.proj --configuration Release --no-build --no-restore
- name: Publish Artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: ${{ github.ref_name }}-packages
path: '**/bin/**/*.*nupkg'
diff --git a/LICENSE b/LICENSE.TXT
similarity index 100%
rename from LICENSE
rename to LICENSE.TXT
diff --git a/OpenTelemetry.sln b/OpenTelemetry.sln
index 8a158c98cd6..52835a5b4ca 100644
--- a/OpenTelemetry.sln
+++ b/OpenTelemetry.sln
@@ -15,11 +15,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.github\workflows\ci-concurrency.yml = .github\workflows\ci-concurrency.yml
CONTRIBUTING.md = CONTRIBUTING.md
global.json = global.json
- LICENSE = LICENSE
NuGet.config = NuGet.config
+ LICENSE.TXT = LICENSE.TXT
OpenTelemetry.proj = OpenTelemetry.proj
README.md = README.md
VERSIONING.md = VERSIONING.md
+ THIRD-PARTY-NOTICES.TXT = THIRD-PARTY-NOTICES.TXT
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{7CB2F02E-03FA-4FFF-89A5-C51F107623FD}"
diff --git a/README.md b/README.md
index 6762bb2f441..6aa8ca5368d 100644
--- a/README.md
+++ b/README.md
@@ -51,9 +51,12 @@ Here are the [instrumentation
libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#instrumentation-library):
* [ASP.NET Core](./src/OpenTelemetry.Instrumentation.AspNetCore/README.md)
-* [Grpc.Net.Client](./src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
-* [HTTP clients](./src/OpenTelemetry.Instrumentation.Http/README.md)
-* [SQL client](./src/OpenTelemetry.Instrumentation.SqlClient/README.md)
+* gRPC client:
+ [Grpc.Net.Client](./src/OpenTelemetry.Instrumentation.GrpcNetClient/README.md)
+* HTTP clients: [System.Net.Http.HttpClient and
+ System.Net.HttpWebRequest](./src/OpenTelemetry.Instrumentation.Http/README.md)
+* SQL clients: [Microsoft.Data.SqlClient and
+ System.Data.SqlClient](./src/OpenTelemetry.Instrumentation.SqlClient/README.md)
Here are the [exporter
libraries](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#exporter-library):
diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT
new file mode 100644
index 00000000000..b65f5723308
--- /dev/null
+++ b/THIRD-PARTY-NOTICES.TXT
@@ -0,0 +1,31 @@
+OpenTelemetry .NET uses third-party libraries or other resources that may be
+distributed under licenses different than the OpenTelemetry .NET software.
+
+The attached notices are provided for information only.
+
+License notice for .NET
+-------------------------------
+
+The MIT License (MIT)
+
+Copyright (c) .NET Foundation and Contributors
+
+All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/build/Common.prod.props b/build/Common.prod.props
index 555776982f0..95c9f0c63ea 100644
--- a/build/Common.prod.props
+++ b/build/Common.prod.props
@@ -13,13 +13,15 @@
Observability;OpenTelemetry;Monitoring;Telemetry;Tracing;Metrics;Loggingopentelemetry-icon-color.pnghttps://opentelemetry.io
- Apache-2.0OpenTelemetry AuthorsCopyright The OpenTelemetry Authors
- true$(Build_ArtifactStagingDirectory)truesnupkg
+ Apache-2.0
+ true
+ $(RepoRoot)\LICENSE.TXT
+ $(RepoRoot)\THIRD-PARTY-NOTICES.TXT
diff --git a/docs/metrics/README.md b/docs/metrics/README.md
index 20bb5945b30..4499281cdd8 100644
--- a/docs/metrics/README.md
+++ b/docs/metrics/README.md
@@ -2,20 +2,23 @@
## Best Practices
-- Instruments SHOULD only be created once and reused throughout the application
- lifetime. This
- [example](../../docs/metrics/getting-started-console/Program.cs) shows how an
- instrument is created a `static` field and then used in the application. You
- could also look at this ASP.NET Core
- [example](../../examples/AspNetCore/Program.cs) which shows a more Dependency
- Injection friendly way of doing this by extracting the `Meter` and an
- instrument into a dedicated class called
- [Instrumentation](../../examples/AspNetCore/Instrumentation.cs) which is then
- added as a `Singleton` service.
-
-- When emitting metrics with tags, DO NOT change the order in which you provide
- tags. Changing the order of tag keys would increase the time taken by the SDK
- to record the measurement.
+### Instruments should be singleton
+
+Instruments SHOULD only be created once and reused throughout the application
+lifetime. This [example](../../docs/metrics/getting-started-console/Program.cs)
+shows how an instrument is created as a `static` field and then used in the
+application. You could also look at this ASP.NET Core
+[example](../../examples/AspNetCore/Program.cs) which shows a more Dependency
+Injection friendly way of doing this by extracting the `Meter` and an instrument
+into a dedicated class called
+[Instrumentation](../../examples/AspNetCore/Instrumentation.cs) which is then
+added as a `Singleton` service.
+
+### Ordering of Tags
+
+When emitting metrics with tags, DO NOT change the order in which you provide
+tags. Changing the order of tag keys would increase the time taken by the SDK to
+record the measurement.
```csharp
// If you emit the tag keys in this order: name -> color -> taste, stick to this order of tag keys for subsequent measurements.
@@ -27,6 +30,8 @@ MyFruitCounter.Add(5, new("name", "apple"), new("color", "red"), new("taste", "s
MyFruitCounter.Add(7, new("color", "red"), new("name", "apple"), new("taste", "sweet")); // <--- DON'T DO THIS
```
+### Use TagList where appropriate
+
For the best performance, it is highly recommended to pass in tags in certain
ways so allocations are only happening on the stack rather than the heap,
which eliminates pressure on the GC (garbage collector):
@@ -49,7 +54,6 @@ var tags = new TagList
// Uses a TagList as there are more than three tags
counter.Add(100, tags); // <--- DO THIS
-
// Avoid the below mentioned approaches when there are more than three tags
var tag1 = new KeyValuePair("DimName1", "DimValue1");
var tag2 = new KeyValuePair("DimName2", "DimValue2");
@@ -63,11 +67,16 @@ counter.Add(100, readOnlySpanOfTags); // <--- DON'T DO THIS
```
- When emitting metrics with more than eight tags, the SDK allocates memory on
- the hot-path. You SHOULD try to keep the number of tags less than or equal to
- eight. Check if you can extract any shared tags such as `MachineName`,
- `Environment` etc. into `Resource` attributes. Refer to this
- [doc](../../docs/metrics/customizing-the-sdk/README.md#resource) for more
- information.
+the hot-path. You SHOULD try to keep the number of tags less than or equal to
+eight. If you are exceeding this, check if you can model some of the tags as
+Resource, as [shown here](#modeling-static-tags-as-resource).
+
+### Modeling static tags as Resource
+
+Tags such as `MachineName`, `Environment` etc. which are static throughout the
+process lifetime should be be modeled as `Resource`, instead of adding them to
+each metric measurement. Refer to this
+[doc](./customizing-the-sdk/README.md#resource) for details and examples.
## Common issues that lead to missing metrics
diff --git a/docs/metrics/customizing-the-sdk/Program.cs b/docs/metrics/customizing-the-sdk/Program.cs
index 36539bd6d57..48c9a975311 100644
--- a/docs/metrics/customizing-the-sdk/Program.cs
+++ b/docs/metrics/customizing-the-sdk/Program.cs
@@ -16,7 +16,12 @@ public class Program
public static void Main()
{
using var meterProvider = Sdk.CreateMeterProviderBuilder()
- .ConfigureResource(res => res.AddService("example-service"))
+ .ConfigureResource(resource => resource.AddAttributes(new List>
+ {
+ new KeyValuePair("static-attribute1", "v1"),
+ new KeyValuePair("static-attribute2", "v2"),
+ }))
+ .ConfigureResource(resource => resource.AddService("MyServiceName"))
.AddMeter(Meter1.Name)
.AddMeter(Meter2.Name)
diff --git a/docs/metrics/customizing-the-sdk/README.md b/docs/metrics/customizing-the-sdk/README.md
index fb41c28cdd9..148d470f0aa 100644
--- a/docs/metrics/customizing-the-sdk/README.md
+++ b/docs/metrics/customizing-the-sdk/README.md
@@ -577,14 +577,27 @@ is to use a resource indicating this
[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service)
and [Telemetry
SDK](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#telemetry-sdk).
-The `ConfigureResource` method on `MeterProviderBuilder` can be used to set a
-configure the resource on the provider. When the provider is built, it
-automatically builds the final `Resource` from the configured `ResourceBuilder`.
-There can only be a single `Resource` associated with a
-provider. It is not possible to change the resource builder *after* the provider
-is built, by calling the `Build()` method on the `MeterProviderBuilder`.
+The `ConfigureResource` method on `MeterProviderBuilder` can be used to
+configure the resource on the provider. `ConfigureResource` accepts an `Action`
+to configure the `ResourceBuilder`. Multiple calls to `ConfigureResource` can be
+made. When the provider is built, it builds the final `Resource` combining all
+the `ConfigureResource` calls. There can only be a single `Resource` associated
+with a provider. It is not possible to change the resource builder *after* the
+provider is built, by calling the `Build()` method on the
+`MeterProviderBuilder`.
+
`ResourceBuilder` offers various methods to construct resource comprising of
-multiple attributes from various sources.
+attributes from various sources. For example, `AddService()` adds
+[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service)
+resource. `AddAttributes` can be used to add any additional attributes to the
+`Resource`. It also allows adding `ResourceDetector`s.
+
+It is recommended to model attributes that are static throughout the lifetime of
+the process as Resources, instead of adding them as attributes(tags) on each
+measurement.
+
+Follow [this](../../trace/extending-the-sdk/README.md#resource-detector) document
+to learn about writing custom resource detectors.
The snippet below shows configuring the `Resource` associated with the provider.
@@ -594,7 +607,12 @@ using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using var meterProvider = Sdk.CreateMeterProviderBuilder()
- .ConfigureResource(r => r.AddService("MyServiceName"))
+ .ConfigureResource(r => r.AddAttributes(new List>
+ {
+ new KeyValuePair("static-attribute1", "v1"),
+ new KeyValuePair("static-attribute2", "v2"),
+ }))
+ .ConfigureResource(resourceBuilder => resourceBuilder.AddService("service-name"))
.Build();
```
diff --git a/docs/trace/README.md b/docs/trace/README.md
index e9925cf6565..2ef0ff07340 100644
--- a/docs/trace/README.md
+++ b/docs/trace/README.md
@@ -2,7 +2,7 @@
## Best Practices
-### ActivitySource singleton
+### ActivitySource should be singleton
`ActivitySource` SHOULD only be created once and reused throughout the
application lifetime. This
@@ -31,6 +31,14 @@ care of propagating/restoring the context across process boundaries. If the
you need, it is generally recommended to enrich the existing Activity with that
information, as opposed to creating a new one.
+### Modelling static tags as Resource
+
+Tags such as `MachineName`, `Environment` etc. which are static throughout the
+process lifetime should be be modelled as `Resource`, instead of adding them
+to each `Activity`. Refer to this
+[doc](./customizing-the-sdk/README.md#resource) for details and
+examples.
+
## Common issues that lead to missing traces
- The `ActivitySource` used to create the `Activity` is not added to the
diff --git a/docs/trace/customizing-the-sdk/Program.cs b/docs/trace/customizing-the-sdk/Program.cs
index 5da5da584f6..fead3aa0be2 100644
--- a/docs/trace/customizing-the-sdk/Program.cs
+++ b/docs/trace/customizing-the-sdk/Program.cs
@@ -33,8 +33,12 @@ public static void Main()
// The following adds subscription to activities from all Activity Sources
// whose name starts with "AbcCompany.XyzProduct.".
.AddSource("AbcCompany.XyzProduct.*")
- .ConfigureResource(resourceBuilder => resourceBuilder.AddTelemetrySdk())
- .ConfigureResource(r => r.AddService("MyServiceName"))
+ .ConfigureResource(resource => resource.AddAttributes(new List>
+ {
+ new KeyValuePair("static-attribute1", "v1"),
+ new KeyValuePair("static-attribute2", "v2"),
+ }))
+ .ConfigureResource(resource => resource.AddService("MyServiceName"))
.AddConsoleExporter()
.Build();
diff --git a/docs/trace/customizing-the-sdk/README.md b/docs/trace/customizing-the-sdk/README.md
index fd16648c820..76f4145d2e3 100644
--- a/docs/trace/customizing-the-sdk/README.md
+++ b/docs/trace/customizing-the-sdk/README.md
@@ -303,12 +303,14 @@ provider is built, by calling the `Build()` method on the
`TracerProviderBuilder`.
`ResourceBuilder` offers various methods to construct resource comprising of
-multiple attributes from various sources. Examples include `AddTelemetrySdk()`
-which adds [Telemetry
-Sdk](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#telemetry-sdk)
-resource, and `AddService()` which adds
+attributes from various sources. For example, `AddService()` adds
[Service](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/README.md#service)
-resource. It also allows adding `ResourceDetector`s.
+resource. `AddAttributes` can be used to add any additional attribute to the
+`Resource`. It also allows adding `ResourceDetector`s.
+
+It is recommended to model attributes that are static throughout the lifetime of
+the process as Resources, instead of adding them as attributes(tags) on each
+`Activity`.
Follow [this](../extending-the-sdk/README.md#resource-detector) document
to learn about writing custom resource detectors.
@@ -321,7 +323,11 @@ using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
var tracerProvider = Sdk.CreateTracerProviderBuilder()
- .ConfigureResource(resourceBuilder => resourceBuilder.AddTelemetrySdk())
+ .ConfigureResource(r => r.AddAttributes(new List>
+ {
+ new KeyValuePair("static-attribute1", "v1"),
+ new KeyValuePair("static-attribute2", "v2"),
+ }))
.ConfigureResource(resourceBuilder => resourceBuilder.AddService("service-name"))
.Build();
```
diff --git a/examples/AspNetCore/Instrumentation.cs b/examples/AspNetCore/Instrumentation.cs
index 190a7d24a6b..4b0ede1157f 100644
--- a/examples/AspNetCore/Instrumentation.cs
+++ b/examples/AspNetCore/Instrumentation.cs
@@ -22,7 +22,7 @@ public Instrumentation()
string? version = typeof(Instrumentation).Assembly.GetName().Version?.ToString();
this.ActivitySource = new ActivitySource(ActivitySourceName, version);
this.meter = new Meter(MeterName, version);
- this.FreezingDaysCounter = this.meter.CreateCounter("weather.days.freezing", "The number of days where the temperature is below freezing");
+ this.FreezingDaysCounter = this.meter.CreateCounter("weather.days.freezing", description: "The number of days where the temperature is below freezing");
}
public ActivitySource ActivitySource { get; }
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 203352a3f75..f7426c0fe37 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -19,4 +19,15 @@
+
+
+
+
+
diff --git a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md
index 809994f353e..ba6c9ba7024 100644
--- a/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md
+++ b/src/OpenTelemetry.Exporter.Prometheus.AspNetCore/CHANGELOG.md
@@ -3,6 +3,9 @@
## Unreleased
* Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107))
+* For requests with OpenMetrics format, scope info is automatically added
+ ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)
+ [#5182](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5182))
## 1.7.0-rc.1
diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md
index 16185be88da..aa2981a5e16 100644
--- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md
+++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/CHANGELOG.md
@@ -3,6 +3,9 @@
## Unreleased
* Export OpenMetrics format from Prometheus exporters ([#5107](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5107))
+* For requests with OpenMetrics format, scope info is automatically added
+ ([#5086](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5086)
+ [#5182](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5182))
## 1.7.0-rc.1
diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs
index e60047739ee..b8b4f4888ae 100644
--- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusCollectionManager.cs
@@ -14,6 +14,7 @@ internal sealed class PrometheusCollectionManager
private readonly int scrapeResponseCacheDurationMilliseconds;
private readonly Func, ExportResult> onCollectRef;
private readonly Dictionary metricsCache;
+ private readonly HashSet scopes;
private int metricsCacheCount;
private byte[] buffer = new byte[85000]; // encourage the object to live in LOH (large object heap)
private int globalLockState;
@@ -29,6 +30,7 @@ public PrometheusCollectionManager(PrometheusExporter exporter)
this.scrapeResponseCacheDurationMilliseconds = this.exporter.ScrapeResponseCacheDurationMilliseconds;
this.onCollectRef = this.OnCollect;
this.metricsCache = new Dictionary();
+ this.scopes = new HashSet();
}
#if NET6_0_OR_GREATER
@@ -170,6 +172,40 @@ private ExportResult OnCollect(Batch metrics)
try
{
+ if (this.exporter.OpenMetricsRequested)
+ {
+ this.scopes.Clear();
+
+ foreach (var metric in metrics)
+ {
+ if (PrometheusSerializer.CanWriteMetric(metric))
+ {
+ if (this.scopes.Add(metric.MeterName))
+ {
+ try
+ {
+ cursor = PrometheusSerializer.WriteScopeInfo(this.buffer, cursor, metric.MeterName);
+
+ break;
+ }
+ catch (IndexOutOfRangeException)
+ {
+ if (!this.IncreaseBufferSize())
+ {
+ // there are two cases we might run into the following condition:
+ // 1. we have many metrics to be exported - in this case we probably want
+ // to put some upper limit and allow the user to configure it.
+ // 2. we got an IndexOutOfRangeException which was triggered by some other
+ // code instead of the buffer[cursor++] - in this case we should give up
+ // at certain point rather than allocating like crazy.
+ throw;
+ }
+ }
+ }
+ }
+ }
+ }
+
foreach (var metric in metrics)
{
if (!PrometheusSerializer.CanWriteMetric(metric))
@@ -194,12 +230,6 @@ private ExportResult OnCollect(Batch metrics)
{
if (!this.IncreaseBufferSize())
{
- // there are two cases we might run into the following condition:
- // 1. we have many metrics to be exported - in this case we probably want
- // to put some upper limit and allow the user to configure it.
- // 2. we got an IndexOutOfRangeException which was triggered by some other
- // code instead of the buffer[cursor++] - in this case we should give up
- // at certain point rather than allocating like crazy.
throw;
}
}
diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs
index 7c3fc57ba0d..69365d4e0ff 100644
--- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializer.cs
@@ -5,6 +5,7 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using OpenTelemetry.Internal;
+using OpenTelemetry.Metrics;
namespace OpenTelemetry.Exporter.Prometheus;
@@ -313,6 +314,31 @@ public static int WriteUnitMetadata(byte[] buffer, int cursor, PrometheusMetric
return cursor;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int WriteScopeInfo(byte[] buffer, int cursor, string scopeName)
+ {
+ if (string.IsNullOrEmpty(scopeName))
+ {
+ return cursor;
+ }
+
+ cursor = WriteAsciiStringNoEscape(buffer, cursor, "# TYPE otel_scope_info info");
+ buffer[cursor++] = ASCII_LINEFEED;
+
+ cursor = WriteAsciiStringNoEscape(buffer, cursor, "# HELP otel_scope_info Scope metadata");
+ buffer[cursor++] = ASCII_LINEFEED;
+
+ cursor = WriteAsciiStringNoEscape(buffer, cursor, "otel_scope_info");
+ buffer[cursor++] = unchecked((byte)'{');
+ cursor = WriteLabel(buffer, cursor, "otel_scope_name", scopeName);
+ buffer[cursor++] = unchecked((byte)'}');
+ buffer[cursor++] = unchecked((byte)' ');
+ buffer[cursor++] = unchecked((byte)'1');
+ buffer[cursor++] = ASCII_LINEFEED;
+
+ return cursor;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool useOpenMetrics)
{
@@ -339,6 +365,37 @@ public static int WriteTimestamp(byte[] buffer, int cursor, long value, bool use
return WriteLong(buffer, cursor, value);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int WriteTags(byte[] buffer, int cursor, Metric metric, ReadOnlyTagCollection tags, bool writeEnclosingBraces = true)
+ {
+ if (writeEnclosingBraces)
+ {
+ buffer[cursor++] = unchecked((byte)'{');
+ }
+
+ cursor = WriteLabel(buffer, cursor, "otel_scope_name", metric.MeterName);
+ buffer[cursor++] = unchecked((byte)',');
+
+ if (!string.IsNullOrEmpty(metric.MeterVersion))
+ {
+ cursor = WriteLabel(buffer, cursor, "otel_scope_version", metric.MeterVersion);
+ buffer[cursor++] = unchecked((byte)',');
+ }
+
+ foreach (var tag in tags)
+ {
+ cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value);
+ buffer[cursor++] = unchecked((byte)',');
+ }
+
+ if (writeEnclosingBraces)
+ {
+ buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra.
+ }
+
+ return cursor;
+ }
+
private static string MapPrometheusType(PrometheusType type)
{
return type switch
diff --git a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs
index fa9885a44b2..1523ef7c160 100644
--- a/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs
+++ b/src/OpenTelemetry.Exporter.Prometheus.HttpListener/Internal/PrometheusSerializerExt.cs
@@ -32,24 +32,11 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
{
foreach (ref readonly var metricPoint in metric.GetMetricPoints())
{
- var tags = metricPoint.Tags;
var timestamp = metricPoint.EndTime.ToUnixTimeMilliseconds();
// Counter and Gauge
cursor = WriteMetricName(buffer, cursor, prometheusMetric);
-
- if (tags.Count > 0)
- {
- buffer[cursor++] = unchecked((byte)'{');
-
- foreach (var tag in tags)
- {
- cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value);
- buffer[cursor++] = unchecked((byte)',');
- }
-
- buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra.
- }
+ cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags);
buffer[cursor++] = unchecked((byte)' ');
@@ -100,12 +87,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
cursor = WriteMetricName(buffer, cursor, prometheusMetric);
cursor = WriteAsciiStringNoEscape(buffer, cursor, "_bucket{");
-
- foreach (var tag in tags)
- {
- cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value);
- buffer[cursor++] = unchecked((byte)',');
- }
+ cursor = WriteTags(buffer, cursor, metric, tags, writeEnclosingBraces: false);
cursor = WriteAsciiStringNoEscape(buffer, cursor, "le=\"");
@@ -131,19 +113,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
// Histogram sum
cursor = WriteMetricName(buffer, cursor, prometheusMetric);
cursor = WriteAsciiStringNoEscape(buffer, cursor, "_sum");
-
- if (tags.Count > 0)
- {
- buffer[cursor++] = unchecked((byte)'{');
-
- foreach (var tag in tags)
- {
- cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value);
- buffer[cursor++] = unchecked((byte)',');
- }
-
- buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra.
- }
+ cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags);
buffer[cursor++] = unchecked((byte)' ');
@@ -157,19 +127,7 @@ public static int WriteMetric(byte[] buffer, int cursor, Metric metric, Promethe
// Histogram count
cursor = WriteMetricName(buffer, cursor, prometheusMetric);
cursor = WriteAsciiStringNoEscape(buffer, cursor, "_count");
-
- if (tags.Count > 0)
- {
- buffer[cursor++] = unchecked((byte)'{');
-
- foreach (var tag in tags)
- {
- cursor = WriteLabel(buffer, cursor, tag.Key, tag.Value);
- buffer[cursor++] = unchecked((byte)',');
- }
-
- buffer[cursor - 1] = unchecked((byte)'}'); // Note: We write the '}' over the last written comma, which is extra.
- }
+ cursor = WriteTags(buffer, cursor, metric, metricPoint.Tags);
buffer[cursor++] = unchecked((byte)' ');
diff --git a/src/OpenTelemetry/CHANGELOG.md b/src/OpenTelemetry/CHANGELOG.md
index faa14974dfe..f760d475655 100644
--- a/src/OpenTelemetry/CHANGELOG.md
+++ b/src/OpenTelemetry/CHANGELOG.md
@@ -2,6 +2,13 @@
## Unreleased
+* Fixed an issue where `LogRecord.Attributes` (or `LogRecord.StateValues` alias)
+ could become out of sync with `LogRecord.State` if either is set directly via
+ the public setters. This was done to further mitigate issues introduced in
+ 1.5.0 causing attributes added using custom processor(s) to be missing after
+ upgrading. For details see:
+ [#5169](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5169)
+
## 1.7.0
Released 2023-Dec-08
diff --git a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLogger.cs b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLogger.cs
index 621a66de30c..acdc08f4fdd 100644
--- a/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLogger.cs
+++ b/src/OpenTelemetry/Logs/ILogger/OpenTelemetryLogger.cs
@@ -79,7 +79,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
LogRecordData.SetActivityContext(ref data, activity);
- var attributes = record.Attributes =
+ var attributes = record.AttributeData =
ProcessState(record, ref iloggerData, in state, this.options.IncludeAttributes, this.options.ParseStateValues);
if (!TryGetOriginalFormatFromAttributes(attributes, out var originalFormat))
@@ -133,7 +133,7 @@ internal static void SetLogRecordSeverityFields(ref LogRecordData logRecordData,
}
}
- private static IReadOnlyList>? ProcessState(
+ internal static IReadOnlyList>? ProcessState(
LogRecord logRecord,
ref LogRecord.LogRecordILoggerData iLoggerData,
in TState state,
diff --git a/src/OpenTelemetry/Logs/LogRecord.cs b/src/OpenTelemetry/Logs/LogRecord.cs
index a2c06667614..877ab83d1dd 100644
--- a/src/OpenTelemetry/Logs/LogRecord.cs
+++ b/src/OpenTelemetry/Logs/LogRecord.cs
@@ -18,6 +18,7 @@ public sealed class LogRecord
{
internal LogRecordData Data;
internal LogRecordILoggerData ILoggerData;
+ internal IReadOnlyList>? AttributeData;
internal List>? AttributeStorage;
internal List