Skip to content

Commit fbf5ccf

Browse files
authored
Merge branch 'main' into instrumentation-sqlclient-drop-db.statement_type
2 parents 6344e5d + 9fd01f7 commit fbf5ccf

5 files changed

Lines changed: 149 additions & 8 deletions

File tree

docs/metrics/README.md

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
* [Pre-Aggregation](#pre-aggregation)
1313
* [Cardinality Limits](#cardinality-limits)
1414
* [Memory Preallocation](#memory-preallocation)
15+
* [Metrics Correlation](#metrics-correlation)
16+
* [Metrics Enrichment](#metrics-enrichment)
1517

1618
</details>
1719
<!-- markdownlint-enable MD033 -->
@@ -138,9 +140,9 @@ Here is the rule of thumb:
138140

139141
> [!NOTE]
140142
> When reporting measurements with more than 8 tags, the API allocates memory on
141-
the hot-path. You SHOULD try to keep the number of tags less than or equal to 8.
142-
If you are exceeding this, check if you can model some of the tags as Resource,
143-
as [shown here](#modeling-static-tags-as-resource).
143+
the hot-path. You SHOULD try to keep the number of tags less than or equal to 8.
144+
If you are exceeding this, check if you can model some of the tags as Resource,
145+
as [shown here](#metrics-enrichment).
144146

145147
## MeterProvider Management
146148

@@ -397,12 +399,60 @@ SDK to reclaim unused metric points.
397399

398400
### Memory Preallocation
399401

400-
### Modeling static tags as Resource
402+
OpenTelemetry .NET SDK aims to avoid memory allocation on the hot code path.
403+
When this is combined with [proper use of Metrics API](#metrics-api), heap
404+
allocation can be avoided on the hot code path. Refer to the [metrics benchmark
405+
results](../../test/Benchmarks/Metrics/MetricsBenchmarks.cs) to learn more.
406+
407+
:heavy_check_mark: You should measure memory allocation on hot code path, and
408+
ideally avoid any heap allocation while using the metrics API and SDK,
409+
especially when you use metrics to measure the performance of your application
410+
(for example, you do not want to spend 2 seconds doing [garbage
411+
collection](https://learn.microsoft.com/dotnet/standard/garbage-collection/)
412+
while measuring an operation which normally takes 10 milliseconds).
413+
414+
## Metrics Correlation
415+
416+
In OpenTelemetry, metrics can be correlated to [traces](../trace/README.md) via
417+
[exemplars](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exemplar).
418+
Check the [Exemplars](./exemplars/README.md) tutorial to learn more.
419+
420+
## Metrics Enrichment
421+
422+
When the metrics are being collected, they normally get stored in a [time series
423+
database](https://en.wikipedia.org/wiki/Time_series_database). From storage and
424+
consumption perspective, metrics can be multi-dimensional. Taking the [fruit
425+
example](#example), there are two dimensions - "name" and "color". For basic
426+
scenarios, all the dimensions can be reported during the [Metrics
427+
API](#metrics-api) invocation, however, for less trivial scenarios, the
428+
dimensions can come from different sources:
429+
430+
* [Measurements](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#measurement)
431+
reported via the [Metrics API](#metrics-api).
432+
* [Resources](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md)
433+
configured at the `MeterProvider` level. Refer to this
434+
[doc](./customizing-the-sdk/README.md#resource) for details and examples.
435+
* Additional attributes provided by the exporter or collector. For example,
436+
[jobs and instances](https://prometheus.io/docs/concepts/jobs_instances/) in
437+
Prometheus.
438+
439+
Here is the rule of thumb when modeling the dimensions:
440+
441+
* If the dimension value is static throughout the process lifetime (e.g. the
442+
name of the machine, data center), model it as Resource, or even better, let
443+
the collector add these dimensions if feasible (e.g. a collector running in
444+
the same data center should know the name of the data center, rather than
445+
relying on / trusting each service instance to report the data center name).
446+
* If the dimension value is dynamic, report it via the [Metrics
447+
API](#metrics-api).
401448

402-
Tags such as `MachineName`, `Environment` etc. which are static throughout the
403-
process lifetime should be be modeled as `Resource`, instead of adding them to
404-
each metric measurement. Refer to this
405-
[doc](./customizing-the-sdk/README.md#resource) for details and examples.
449+
> [!NOTE]
450+
> There were discussions around adding a new concept called
451+
`MeasurementProcessor`, which allows dimensions to be added to / removed from
452+
measurements dynamically. This idea did not get traction due to the complexity
453+
and performance implications, refer to this [pull
454+
request](https://github.com/open-telemetry/opentelemetry-specification/pull/1938)
455+
for more context.
406456

407457
## Common issues that lead to missing metrics
408458

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#if !EXPOSE_EXPERIMENTAL_FEATURES
5+
using System.Runtime.CompilerServices;
6+
7+
[assembly: InternalsVisibleTo("OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests" + AssemblyInfo.PublicKey)]
8+
#endif
9+
10+
#if SIGNED
11+
file static class AssemblyInfo
12+
{
13+
public const string PublicKey = ", PublicKey=002400000480000094000000060200000024000052534131000400000100010051C1562A090FB0C9F391012A32198B5E5D9A60E9B80FA2D7B434C9E5CCB7259BD606E66F9660676AFC6692B8CDC6793D190904551D2103B7B22FA636DCBB8208839785BA402EA08FC00C8F1500CCEF28BBF599AA64FFB1E1D5DC1BF3420A3777BADFE697856E9D52070A50C3EA5821C80BEF17CA3ACFFA28F89DD413F096F898";
14+
}
15+
#else
16+
file static class AssemblyInfo
17+
{
18+
public const string PublicKey = "";
19+
}
20+
#endif

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
will be automatically included in exports.
1616
([#5258](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5258))
1717

18+
* Updated `OtlpLogExporter` to set `body` on the data model from
19+
`LogRecord.Body` if `{OriginalFormat}` attribute is NOT found and
20+
`FormattedMessage` is `null`. This is typically the case when using the
21+
experimental Logs Bridge API.
22+
([#5268](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5268))
23+
1824
## 1.7.0
1925

2026
Released 2023-Dec-08

src/OpenTelemetry.Exporter.OpenTelemetryProtocol/Implementation/OtlpLogRecordTransformer.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ internal OtlpLogs.LogRecord ToOtlpLog(LogRecord logRecord)
170170
AddAttribute(otlpLogRecord, result, attributeCountLimit);
171171
}
172172
}
173+
174+
// Supports setting Body directly on LogRecord for the Logs Bridge API.
175+
if (otlpLogRecord.Body == null && logRecord.Body != null)
176+
{
177+
// If {OriginalFormat} is not present in the attributes,
178+
// use logRecord.Body if it is set.
179+
otlpLogRecord.Body = new OtlpCommon.AnyValue { StringValue = logRecord.Body };
180+
}
173181
}
174182

175183
if (logRecord.TraceId != default && logRecord.SpanId != default)

test/OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests/OtlpLogExporterTests.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,63 @@ public void CheckToOtlpLogRecordBodyIsPopulated(bool includeFormattedMessage)
589589
Assert.Equal("state", otlpLogRecord.Body.StringValue);
590590
}
591591

592+
[Theory]
593+
[InlineData(true)]
594+
[InlineData(false)]
595+
public void LogRecordBodyIsExportedWhenUsingBridgeApi(bool isBodySet)
596+
{
597+
LogRecordAttributeList attributes = default;
598+
attributes.Add("name", "tomato");
599+
attributes.Add("price", 2.99);
600+
attributes.Add("{OriginalFormat}", "Hello from {name} {price}.");
601+
602+
var logRecords = new List<LogRecord>();
603+
604+
using (var loggerProvider = Sdk.CreateLoggerProviderBuilder()
605+
.AddInMemoryExporter(logRecords)
606+
.Build())
607+
{
608+
var logger = loggerProvider.GetLogger();
609+
610+
logger.EmitLog(new LogRecordData()
611+
{
612+
Body = isBodySet ? "Hello world" : null,
613+
});
614+
615+
logger.EmitLog(new LogRecordData(), attributes);
616+
}
617+
618+
Assert.Equal(2, logRecords.Count);
619+
620+
var otlpLogRecordTransformer = new OtlpLogRecordTransformer(DefaultSdkLimitOptions, new());
621+
622+
var otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecords[0]);
623+
624+
if (isBodySet)
625+
{
626+
Assert.Equal("Hello world", otlpLogRecord.Body?.StringValue);
627+
}
628+
else
629+
{
630+
Assert.Null(otlpLogRecord.Body);
631+
}
632+
633+
otlpLogRecord = otlpLogRecordTransformer.ToOtlpLog(logRecords[1]);
634+
635+
Assert.Equal(2, otlpLogRecord.Attributes.Count);
636+
637+
var index = 0;
638+
var attribute = otlpLogRecord.Attributes[index];
639+
Assert.Equal("name", attribute.Key);
640+
Assert.Equal("tomato", attribute.Value.StringValue);
641+
642+
attribute = otlpLogRecord.Attributes[++index];
643+
Assert.Equal("price", attribute.Key);
644+
Assert.Equal(2.99, attribute.Value.DoubleValue);
645+
646+
Assert.Equal("Hello from {name} {price}.", otlpLogRecord.Body.StringValue);
647+
}
648+
592649
[Fact]
593650
public void CheckToOtlpLogRecordExceptionAttributes()
594651
{

0 commit comments

Comments
 (0)