Skip to content

Commit

Permalink
Merge branch 'main' into vibankwa/organize-otlp-readme
Browse files Browse the repository at this point in the history
  • Loading branch information
vishweshbankwar authored Mar 12, 2024
2 parents c0a67a8 + adc89d9 commit 9b77cf0
Show file tree
Hide file tree
Showing 113 changed files with 4,607 additions and 1,941 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
outputs:
changes: ${{ steps.changes.outputs.changes }}
steps:
- uses: actions/checkout@v4
- uses: AurorNZ/paths-filter@v4
id: changes
with:
Expand Down
20 changes: 15 additions & 5 deletions docs/logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,21 @@ Here is the rule of thumb:
Minutes - Console Application](./getting-started-console/README.md) tutorial
to learn more.

:heavy_check_mark: You should use dot-separated
[UpperCamelCase](https://en.wikipedia.org/wiki/Camel_case) as the log category
name, which makes it convenient to [filter logs](#log-filtering). A common
practice is to use fully qualified class name, and if further categorization is
desired, append a subcategory name. Refer to the [.NET official
document](https://learn.microsoft.com/dotnet/core/extensions/logging#log-category)
to learn more.

```csharp
loggerFactory.CreateLogger<MyClass>(); // this is equivalent to CreateLogger("MyProduct.MyLibrary.MyClass")
loggerFactory.CreateLogger("MyProduct.MyLibrary.MyClass"); // use the fully qualified class name
loggerFactory.CreateLogger("MyProduct.MyLibrary.MyClass.DatabaseOperations"); // append a subcategory name
loggerFactory.CreateLogger("MyProduct.MyLibrary.MyClass.FileOperations"); // append another subcategory name
```

:stop_sign: You should avoid creating loggers too frequently. Although loggers
are not super expensive, they still come with CPU and memory cost, and are meant
to be reused throughout the application. Refer to the [logging performance
Expand Down Expand Up @@ -186,11 +201,6 @@ instances if they are created by you.
API invocation associated with the logger factory could become no-op (i.e. no
logs will be emitted).

:heavy_check_mark: You should use the fully qualified class name as the log
category name. Refer to the [.NET official
document](https://learn.microsoft.com/dotnet/core/extensions/logging#log-category)
to learn more.

## Log Correlation

In OpenTelemetry, logs are automatically correlated to
Expand Down
77 changes: 46 additions & 31 deletions docs/logs/customizing-the-sdk/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,52 @@
using OpenTelemetry.Logs;
using OpenTelemetry.Resources;

namespace CustomizingTheSdk;

public class Program
var loggerFactory = LoggerFactory.Create(builder =>
{
public static void Main()
builder.AddOpenTelemetry(logging =>
{
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
serviceName: "MyService",
serviceVersion: "1.0.0"));
options.AddConsoleExporter();
});
});

var logger = loggerFactory.CreateLogger<Program>();

logger.LogInformation("Hello from {name} {price}.", "tomato", 2.99);
logger.LogWarning("Hello from {name} {price}.", "tomato", 2.99);
logger.LogError("Hello from {name} {price}.", "tomato", 2.99);

// log with scopes
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("store", "Seattle"),
}))
{
logger.LogInformation("Hello from {food} {price}.", "tomato", 2.99);
}
}
logging.IncludeScopes = true;
logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
serviceName: "MyService",
serviceVersion: "1.0.0"));
logging.AddConsoleExporter();
});
});

var logger = loggerFactory.CreateLogger<Program>();

logger.FoodPriceChanged("artichoke", 9.99);

using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>("store", "Seattle"),
}))
{
logger.FoodPriceChanged("truffle", 999.99);
}

logger.FoodRecallNotice(
brandName: "Contoso",
productDescription: "Salads",
productType: "Food & Beverages",
recallReasonDescription: "due to a possible health risk from Listeria monocytogenes",
companyName: "Contoso Fresh Vegetables, Inc.");

// Dispose logger factory before the application ends.
// This will flush the remaining logs and shutdown the logging pipeline.
loggerFactory.Dispose();

internal static partial class LoggerExtensions
{
[LoggerMessage(LogLevel.Information, "Food `{name}` price changed to `{price}`.")]
public static partial void FoodPriceChanged(this ILogger logger, string name, double price);

[LoggerMessage(LogLevel.Critical, "A `{productType}` recall notice was published for `{brandName} {productDescription}` produced by `{companyName}` ({recallReasonDescription}).")]
public static partial void FoodRecallNotice(
this ILogger logger,
string brandName,
string productDescription,
string productType,
string recallReasonDescription,
string companyName);
}
12 changes: 6 additions & 6 deletions docs/logs/customizing-the-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ It is not supported to add Processors after building the `LoggerFactory`.
```csharp
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
builder.AddOpenTelemetry(logging =>
{
options.AddProcessor(...)
logging.AddProcessor(...);
});
});
```
Expand All @@ -72,9 +72,9 @@ The snippet below shows configuring a custom `ResourceBuilder` to the provider.
```csharp
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
builder.AddOpenTelemetry(logging =>
{
options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
logging.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
serviceName: "MyService",
serviceVersion: "1.0.0"));
});
Expand Down Expand Up @@ -107,8 +107,8 @@ and also defines "Warning" as the minimum `LogLevel` for a user defined category
These rules as defined only apply to the `OpenTelemetryLoggerProvider`.

```csharp
ILoggingBuilder.AddFilter<OpenTelemetryLoggerProvider>("*", LogLevel.Error);
ILoggingBuilder.AddFilter<OpenTelemetryLoggerProvider>("category name", LogLevel.Warning);
builder.AddFilter<OpenTelemetryLoggerProvider>("*", LogLevel.Error);
builder.AddFilter<OpenTelemetryLoggerProvider>("MyProduct.MyLibrary.MyClass", LogLevel.Warning);
```

## Learn more
Expand Down
54 changes: 17 additions & 37 deletions docs/metrics/customizing-the-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -412,58 +412,38 @@ exemplars.

#### ExemplarFilter

`ExemplarFilter` determines which measurements are eligible to become an
Exemplar. i.e. `ExemplarFilter` determines which measurements are offered to
`ExemplarReservoir`, which makes the final decision about whether the offered
measurement gets stored as an exemplar. They can be used to control the noise
and overhead associated with Exemplar collection.
`ExemplarFilter` determines which measurements are offered to the configured
`ExemplarReservoir`, which makes the final decision about whether or not the
offered measurement gets recorded as an `Exemplar`. Generally `ExemplarFilter`
is a mechanism to control the overhead associated with `Exemplar` offering.

OpenTelemetry SDK comes with the following Filters:
OpenTelemetry SDK comes with the following `ExemplarFilters` (defined on
`ExemplarFilterType`):

* `AlwaysOnExemplarFilter` - makes all measurements eligible for being an Exemplar.
* `AlwaysOffExemplarFilter` - makes no measurements eligible for being an
Exemplar. Using this is as good as turning off Exemplar feature, and is the current
* `AlwaysOff`: Makes no measurements eligible for becoming an `Exemplar`. Using
this is as good as turning off the `Exemplar` feature and is the current
default.
* `TraceBasedExemplarFilter` - makes those measurements eligible for being an
Exemplar, which are recorded in the context of a sampled parent `Activity`
(span).
* `AlwaysOn`: Makes all measurements eligible for becoming an `Exemplar`.
* `TraceBased`: Makes those measurements eligible for becoming an `Exemplar`
which are recorded in the context of a sampled `Activity` (span).

`SetExemplarFilter` method on `MeterProviderBuilder` can be used to set the
desired `ExemplarFilter`.

The snippet below shows how to set `ExemplarFilter`.
The `SetExemplarFilter` extension method on `MeterProviderBuilder` can be used
to set the desired `ExemplarFilterType` and enable `Exemplar` collection:

```csharp
using OpenTelemetry;
using OpenTelemetry.Metrics;

using var meterProvider = Sdk.CreateMeterProviderBuilder()
// rest of config not shown
.SetExemplarFilter(new TraceBasedExemplarFilter())
.SetExemplarFilter(ExemplarFilterType.TraceBased)
.Build();
```

> [!NOTE]
> As of today, there is no separate toggle for enable/disable Exemplar feature.
Exemplars can be disabled by setting filter as `AlwaysOffExemplarFilter`, which
is also the default (i.e Exemplar feature is disabled by default). Users can
enable the feature by setting filter to anything other than
`AlwaysOffExemplarFilter`. For example: `.SetExemplarFilter(new TraceBasedExemplarFilter())`.

If the built-in `ExemplarFilter`s are not meeting the needs, one may author
custom `ExemplarFilter` as shown
[here](../extending-the-sdk/README.md#exemplarfilter). A custom filter, which
eliminates all un-interesting measurements from becoming Exemplar is a
recommended way to control performance overhead associated with collecting
Exemplars. See
[benchmark](../../../test/Benchmarks/Metrics/ExemplarBenchmarks.cs) to see how
much impact can `ExemplarFilter` have on performance.

#### ExemplarReservoir

`ExemplarReservoir` receives the measurements sampled in by the `ExemplarFilter`
and is responsible for storing Exemplars. `ExemplarReservoir` ultimately decides
which measurements get stored as exemplars. The following are the default
`ExemplarReservoir` receives the measurements sampled by the `ExemplarFilter`
and is responsible for recording `Exemplar`s. The following are the default
reservoirs:

* `AlignedHistogramBucketExemplarReservoir` is the default reservoir used for
Expand All @@ -479,7 +459,7 @@ size (currently defaulting to 1) determines the maximum number of exemplars
stored.

> [!NOTE]
> Currently there is no ability to change or configure Reservoir.
> Currently there is no ability to change or configure `ExemplarReservoir`.
### Instrumentation

Expand Down
39 changes: 1 addition & 38 deletions docs/metrics/extending-the-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,44 +74,7 @@ Not supported.

## ExemplarFilter

OpenTelemetry .NET SDK has provided the following built-in `ExemplarFilter`s:

* [AlwaysOnExemplarFilter](../../../src/OpenTelemetry/Metrics/Exemplar/AlwaysOnExemplarFilter.cs)
* [AlwaysOffExemplarFilter](../../../src/OpenTelemetry/Metrics/Exemplar/AlwaysOffExemplarFilter.cs)
* [TraceBasedExemplarFilter](../../../src/OpenTelemetry/Metrics/Exemplar/TraceBasedExemplarFilter.cs)

Custom exemplar filters can be implemented to achieve filtering based on other criterion:

* `ExemplarFilter` should derive from `OpenTelemetry.ExemplarFilter` (which
belongs to the [OpenTelemetry](../../../src/OpenTelemetry/README.md) package)
and implement the `ShouldSample` method.

One example is a filter, which filters all measurements of value lower
than given threshold is given below. Such a filter prevents any measurements
below the given threshold from ever becoming a `Exemplar`. Such filters could
also incorporate the `TraceBasedExemplarFilter` condition as well, as storing
exemplars for non-sampled traces may be undesired.

```csharp
public sealed class HighValueFilter : ExemplarFilter
{
private readonly double maxValue;

public HighValueFilter(double maxValue)
{
this.maxValue = maxValue;
}
public override bool ShouldSample(long value, ReadOnlySpan<KeyValuePair<string, object>> tags)
{
return Activity.Current?.Recorded && value > this.maxValue;
}

public override bool ShouldSample(double value, ReadOnlySpan<KeyValuePair<string, object>> tags)
{
return Activity.Current?.Recorded && value > this.maxValue;
}
}
```
Not supported.

## ExemplarReservoir

Expand Down
2 changes: 1 addition & 1 deletion examples/AspNetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
builder
.AddMeter(Instrumentation.MeterName)
#if EXPOSE_EXPERIMENTAL_FEATURES
.SetExemplarFilter(new TraceBasedExemplarFilter())
.SetExemplarFilter(ExemplarFilterType.TraceBased)
#endif
.AddRuntimeInstrumentation()
.AddHttpClientInstrumentation()
Expand Down
44 changes: 29 additions & 15 deletions src/OpenTelemetry.Exporter.Console/ConsoleMetricExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,30 +188,44 @@ public override ExportResult Export(in Batch<Metric> batch)
}

var exemplarString = new StringBuilder();
foreach (var exemplar in metricPoint.GetExemplars())
if (metricPoint.TryGetExemplars(out var exemplars))
{
if (exemplar.Timestamp != default)
foreach (ref readonly var exemplar in exemplars)
{
exemplarString.Append("Value: ");
exemplarString.Append(exemplar.DoubleValue);
exemplarString.Append(" Timestamp: ");
exemplarString.Append("Timestamp: ");
exemplarString.Append(exemplar.Timestamp.ToString("yyyy-MM-ddTHH:mm:ss.fffffffZ", CultureInfo.InvariantCulture));
exemplarString.Append(" TraceId: ");
exemplarString.Append(exemplar.TraceId);
exemplarString.Append(" SpanId: ");
exemplarString.Append(exemplar.SpanId);
if (metricType.IsDouble())
{
exemplarString.Append(" Value: ");
exemplarString.Append(exemplar.DoubleValue);
}
else if (metricType.IsLong())
{
exemplarString.Append(" Value: ");
exemplarString.Append(exemplar.LongValue);
}

if (exemplar.FilteredTags != null && exemplar.FilteredTags.Count > 0)
if (exemplar.TraceId != default)
{
exemplarString.Append(" Filtered Tags : ");
exemplarString.Append(" TraceId: ");
exemplarString.Append(exemplar.TraceId.ToHexString());
exemplarString.Append(" SpanId: ");
exemplarString.Append(exemplar.SpanId.ToHexString());
}

foreach (var tag in exemplar.FilteredTags)
bool appendedTagString = false;
foreach (var tag in exemplar.FilteredTags)
{
if (ConsoleTagTransformer.Instance.TryTransformTag(tag, out var result))
{
if (ConsoleTagTransformer.Instance.TryTransformTag(tag, out var result))
if (!appendedTagString)
{
exemplarString.Append(result);
exemplarString.Append(' ');
exemplarString.Append(" Filtered Tags : ");
appendedTagString = true;
}

exemplarString.Append(result);
exemplarString.Append(' ');
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#nullable enable
~OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.get -> OpenTelemetry.BatchExportProcessorOptions<System.Diagnostics.Activity>
~OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.set -> void
~OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.get -> System.Uri
~OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.set -> void
~OpenTelemetry.Exporter.OtlpExporterOptions.Headers.get -> string
~OpenTelemetry.Exporter.OtlpExporterOptions.Headers.set -> void
~OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.get -> System.Func<System.Net.Http.HttpClient>
~OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.set -> void
OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.get -> OpenTelemetry.BatchExportProcessorOptions<System.Diagnostics.Activity!>!
OpenTelemetry.Exporter.OtlpExporterOptions.BatchExportProcessorOptions.set -> void
OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.get -> System.Uri!
OpenTelemetry.Exporter.OtlpExporterOptions.Endpoint.set -> void
OpenTelemetry.Exporter.OtlpExporterOptions.Headers.get -> string?
OpenTelemetry.Exporter.OtlpExporterOptions.Headers.set -> void
OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.get -> System.Func<System.Net.Http.HttpClient!>!
OpenTelemetry.Exporter.OtlpExporterOptions.HttpClientFactory.set -> void
~OpenTelemetry.Exporter.OtlpMetricExporter.OtlpMetricExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void
~OpenTelemetry.Exporter.OtlpTraceExporter.OtlpTraceExporter(OpenTelemetry.Exporter.OtlpExporterOptions options) -> void
~override OpenTelemetry.Exporter.OtlpMetricExporter.Export(in OpenTelemetry.Batch<OpenTelemetry.Metrics.Metric> metrics) -> OpenTelemetry.ExportResult
Expand Down
9 changes: 9 additions & 0 deletions src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@
as it is mandated by the specification.
([#5316](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5268))

* **Experimental (pre-release builds only):** Add support in
`OtlpMetricExporter` for emitting exemplars supplied on Counters, Gauges, and
ExponentialHistograms.
([#5397](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5397))

* Setting `Endpoint` or `HttpClientFactory` properties on `OtlpExporterOptions`
to `null` will now result in an `ArgumentNullException` being thrown.
([#5434](https://github.com/open-telemetry/opentelemetry-dotnet/pull/5434))

## 1.7.0

Released 2023-Dec-08
Expand Down
Loading

0 comments on commit 9b77cf0

Please sign in to comment.