Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[otlp] Add log exception attributes under feature flag #4892

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

* Added ability to export attributes corresponding to `LogRecord.Exception` i.e.
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
`exception.type`, `exception.message` and `exception.stacktrace`. These
attributes will be exported when
`OTEL_DOTNET_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES` environment variable will be
set to `true`.
([#4892](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4892))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move this below the other 2 "unreleased" entries right? I've been telling people recently to order things chronologically hopefully I'm not mistaken 🤣

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I vote for 1) the latest release should be at the top, the oldest release should be at the bottom 2) within a release, changes are ordered based on when the PR got merged (the latest one should be at the bottom).

Example for 1) https://github.com/dotnet/core/tree/main/release-notes/8.0#releases

Example for 2) https://github.com/dotnet/core/blob/main/release-notes/8.0/preview/8.0.0-rc.1.md#notable-fixes

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vishweshbankwar The unreleased section is at the top, yes, but the new entry goes into the bottom of that section 😄

Copy link
Member Author

@vishweshbankwar vishweshbankwar Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to get my eyes checked😆 . Fixed the sequence.


* Bumped the version of `Google.Protobuf` used by the project to `3.22.5` so
that consuming applications can be published as NativeAOT successfully. Also,
a new performance feature can be used instead of reflection emit, which is
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// <copyright file="ExperimentalOptions.cs" company="OpenTelemetry Authors">
// 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.
// </copyright>

#nullable enable

using Microsoft.Extensions.Configuration;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;

internal sealed class ExperimentalOptions
{
public const string EMITLOGEXCEPTIONATTRIBUTES = "OTEL_DOTNET_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES";

public ExperimentalOptions()
: this(new ConfigurationBuilder().AddEnvironmentVariables().Build())
{
}

public ExperimentalOptions(IConfiguration configuration)
{
if (configuration.TryGetBoolValue(EMITLOGEXCEPTIONATTRIBUTES, out var emitLogExceptionAttributes))
{
this.EmitLogExceptionAttributes = emitLogExceptionAttributes;
}
}

/// <summary>
/// Gets or sets a value indicating whether log exception attributes should be exported.
/// </summary>
public bool EmitLogExceptionAttributes { get; set; } = false;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// <copyright file="LogRecordExtensions.cs" company="OpenTelemetry Authors">
// <copyright file="OtlpLogRecordProcessor.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -16,22 +16,33 @@

using System.Runtime.CompilerServices;
using Google.Protobuf;
using OpenTelemetry.Internal;
using OpenTelemetry.Logs;
using OpenTelemetry.Trace;
using OtlpCollector = OpenTelemetry.Proto.Collector.Logs.V1;
using OtlpCommon = OpenTelemetry.Proto.Common.V1;
using OtlpLogs = OpenTelemetry.Proto.Logs.V1;
using OtlpResource = OpenTelemetry.Proto.Resource.V1;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;

internal static class LogRecordExtensions
internal sealed class OtlpLogRecordProcessor
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
{
internal static void AddBatch(
this OtlpCollector.ExportLogsServiceRequest request,
SdkLimitOptions sdkLimitOptions,
private readonly SdkLimitOptions sdkLimitOptions;
private readonly ExperimentalOptions experimentalOptions;

public OtlpLogRecordProcessor(SdkLimitOptions sdkLimitOptions, ExperimentalOptions experimentalOptions)
{
this.sdkLimitOptions = sdkLimitOptions;
this.experimentalOptions = experimentalOptions;
}

internal OtlpCollector.ExportLogsServiceRequest BuildExportRequest(
OtlpResource.Resource processResource,
in Batch<LogRecord> logRecordBatch)
{
var request = new OtlpCollector.ExportLogsServiceRequest();

var resourceLogs = new OtlpLogs.ResourceLogs
{
Resource = processResource,
Expand All @@ -43,16 +54,18 @@ internal static void AddBatch(

foreach (var logRecord in logRecordBatch)
{
var otlpLogRecord = logRecord.ToOtlpLog(sdkLimitOptions);
var otlpLogRecord = this.ToOtlpLog(logRecord);
if (otlpLogRecord != null)
{
scopeLogs.LogRecords.Add(otlpLogRecord);
}
}

return request;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitOptions sdkLimitOptions)
internal OtlpLogs.LogRecord ToOtlpLog(LogRecord logRecord)
{
OtlpLogs.LogRecord otlpLogRecord = null;

Expand All @@ -75,8 +88,8 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitO
otlpLogRecord.SeverityText = logRecord.Severity.Value.ToShortName();
}

var attributeValueLengthLimit = sdkLimitOptions.LogRecordAttributeValueLengthLimit;
var attributeCountLimit = sdkLimitOptions.LogRecordAttributeCountLimit ?? int.MaxValue;
var attributeValueLengthLimit = this.sdkLimitOptions.LogRecordAttributeValueLengthLimit;
var attributeCountLimit = this.sdkLimitOptions.LogRecordAttributeCountLimit ?? int.MaxValue;

/*
// Removing this temporarily for stable release
Expand Down Expand Up @@ -104,14 +117,14 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitO
{
otlpLogRecord.AddStringAttribute(nameof(logRecord.EventId.Name), logRecord.EventId.Name, attributeValueLengthLimit, attributeCountLimit);
}
*/

if (logRecord.Exception != null)
if (this.experimentalOptions.EmitLogExceptionAttributes && logRecord.Exception != null)
{
otlpLogRecord.AddStringAttribute(SemanticConventions.AttributeExceptionType, logRecord.Exception.GetType().Name, attributeValueLengthLimit, attributeCountLimit);
otlpLogRecord.AddStringAttribute(SemanticConventions.AttributeExceptionMessage, logRecord.Exception.Message, attributeValueLengthLimit, attributeCountLimit);
otlpLogRecord.AddStringAttribute(SemanticConventions.AttributeExceptionStacktrace, logRecord.Exception.ToInvariantString(), attributeValueLengthLimit, attributeCountLimit);
AddStringAttribute(otlpLogRecord, SemanticConventions.AttributeExceptionType, logRecord.Exception.GetType().Name, attributeValueLengthLimit, attributeCountLimit);
AddStringAttribute(otlpLogRecord, SemanticConventions.AttributeExceptionMessage, logRecord.Exception.Message, attributeValueLengthLimit, attributeCountLimit);
AddStringAttribute(otlpLogRecord, SemanticConventions.AttributeExceptionStacktrace, logRecord.Exception.ToInvariantString(), attributeValueLengthLimit, attributeCountLimit);
}
*/

bool bodyPopulatedFromFormattedMessage = false;
if (logRecord.FormattedMessage != null)
Expand All @@ -133,7 +146,7 @@ internal static OtlpLogs.LogRecord ToOtlpLog(this LogRecord logRecord, SdkLimitO
}
else if (OtlpKeyValueTransformer.Instance.TryTransformTag(attribute, out var result, attributeValueLengthLimit))
{
otlpLogRecord.AddAttribute(result, attributeCountLimit);
AddAttribute(otlpLogRecord, result, attributeCountLimit);
}
}
}
Expand Down Expand Up @@ -183,7 +196,7 @@ void ProcessScope(LogRecordScope scope, OtlpLogs.LogRecord otlpLog)
{
if (OtlpKeyValueTransformer.Instance.TryTransformTag(scopeItem, out var result, attributeValueLengthLimit))
{
otlpLog.AddAttribute(result, attributeCountLimit);
AddAttribute(otlpLog, result, attributeCountLimit);
}
}
}
Expand All @@ -198,7 +211,7 @@ void ProcessScope(LogRecordScope scope, OtlpLogs.LogRecord otlpLog)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddAttribute(this OtlpLogs.LogRecord logRecord, OtlpCommon.KeyValue attribute, int maxAttributeCount)
private static void AddAttribute(OtlpLogs.LogRecord logRecord, OtlpCommon.KeyValue attribute, int maxAttributeCount)
{
if (logRecord.Attributes.Count < maxAttributeCount)
{
Expand All @@ -211,22 +224,22 @@ private static void AddAttribute(this OtlpLogs.LogRecord logRecord, OtlpCommon.K
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddStringAttribute(this OtlpLogs.LogRecord logRecord, string key, string value, int? maxValueLength, int maxAttributeCount)
private static void AddStringAttribute(OtlpLogs.LogRecord logRecord, string key, string value, int? maxValueLength, int maxAttributeCount)
{
var attributeItem = new KeyValuePair<string, object>(key, value);
if (OtlpKeyValueTransformer.Instance.TryTransformTag(attributeItem, out var result, maxValueLength))
{
logRecord.AddAttribute(result, maxAttributeCount);
AddAttribute(logRecord, result, maxAttributeCount);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void AddIntAttribute(this OtlpLogs.LogRecord logRecord, string key, int value, int maxAttributeCount)
private static void AddIntAttribute(OtlpLogs.LogRecord logRecord, string key, int value, int maxAttributeCount)
{
var attributeItem = new KeyValuePair<string, object>(key, value);
if (OtlpKeyValueTransformer.Instance.TryTransformTag(attributeItem, out var result))
{
logRecord.AddAttribute(result, maxAttributeCount);
AddAttribute(logRecord, result, maxAttributeCount);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ namespace OpenTelemetry.Exporter;
/// </summary>
internal sealed class OtlpLogExporter : BaseExporter<LogRecord>
{
private readonly SdkLimitOptions sdkLimitOptions;
private readonly IExportClient<OtlpCollector.ExportLogsServiceRequest> exportClient;
private readonly OtlpLogRecordProcessor otlpLogRecordProcessor;

private OtlpResource.Resource processResource;

Expand All @@ -58,8 +58,6 @@ internal OtlpLogExporter(
Debug.Assert(exporterOptions != null, "exporterOptions was null");
Debug.Assert(sdkLimitOptions != null, "sdkLimitOptions was null");

this.sdkLimitOptions = sdkLimitOptions;

// Each of the Otlp exporters: Traces, Metrics, and Logs set the same value for `OtlpKeyValueTransformer.LogUnsupportedAttributeType`
// and `ConfigurationExtensions.LogInvalidEnvironmentVariable` so it should be fine even if these exporters are used together.
OtlpKeyValueTransformer.LogUnsupportedAttributeType = (string tagValueType, string tagKey) =>
Expand All @@ -80,6 +78,8 @@ internal OtlpLogExporter(
{
this.exportClient = exporterOptions.GetLogExportClient();
}

this.otlpLogRecordProcessor = new OtlpLogRecordProcessor(sdkLimitOptions, new());
}

internal OtlpResource.Resource ProcessResource => this.processResource ??= this.ParentProvider.GetResource().ToOtlpResource();
Expand All @@ -90,11 +90,9 @@ public override ExportResult Export(in Batch<LogRecord> logRecordBatch)
// Prevents the exporter's gRPC and HTTP operations from being instrumented.
using var scope = SuppressInstrumentationScope.Begin();

var request = new OtlpCollector.ExportLogsServiceRequest();

try
{
request.AddBatch(this.sdkLimitOptions, this.ProcessResource, logRecordBatch);
var request = this.otlpLogRecordProcessor.BuildExportRequest(this.ProcessResource, logRecordBatch);

if (!this.exportClient.SendExportRequest(request))
{
Expand Down
10 changes: 10 additions & 0 deletions src/OpenTelemetry.Exporter.OpenTelemetryProtocol/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,16 @@ values of the log record limits
* `OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT`
* `OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT`

## Environment Variables for Experimental Features

### Otlp Log Exporter

* `OTEL_DOTNET_OTLP_EMIT_EXCEPTION_LOG_ATTRIBUTES`
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved

When set to `true`, it enables export of attributes corresponding to
`LogRecord.Exception`. The attributes are `exception.type`, `exception.message`
vishweshbankwar marked this conversation as resolved.
Show resolved Hide resolved
and `exception.stacktrace`.

## Configure HttpClient

The `HttpClientFactory` option is provided on `OtlpExporterOptions` for users
Expand Down
Loading