[Logs-branch] Port scope buffering fix to logs branch (#3736)

* [Logs] Fix buffered log scopes being reused (#3731)

* Fix buffered log scopes being reused.

* CHANGELOG update.

* Test fixes.

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>

* Update log scope buffering fix for new api.

Co-authored-by: Cijo Thomas <cithomas@microsoft.com>
This commit is contained in:
Mikel Blanchard 2022-10-06 09:54:06 -07:00 committed by GitHub
parent 9c3f53f4c7
commit 9aebae2d90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 127 additions and 37 deletions

View File

@ -7,6 +7,11 @@
## Unreleased
* Fixed an issue where `LogRecord.ForEachScope` may return scopes from a
previous log if accessed in a custom processor before
`BatchLogRecordExportProcessor.OnEnd` is fired.
([#3731](https://github.com/open-telemetry/opentelemetry-dotnet/pull/3731))
## 1.4.0-beta.1
Released 2022-Sep-29

View File

@ -75,6 +75,8 @@ namespace OpenTelemetry.Logs
iloggerData.CategoryName = this.categoryName;
iloggerData.EventId = eventId;
iloggerData.Exception = exception;
iloggerData.ScopeProvider = iloggerProvider.IncludeScopes ? this.ScopeProvider : null;
iloggerData.BufferedScopes = null;
ref LogRecordData data = ref record.Data;
@ -114,9 +116,8 @@ namespace OpenTelemetry.Logs
iloggerData.FormattedMessage = iloggerProvider.IncludeFormattedMessage ? formatter?.Invoke(state, exception) : null;
}
record.ScopeProvider = iloggerProvider.IncludeScopes ? this.ScopeProvider : null;
processor.OnEnd(record);
record.ScopeProvider = null;
iloggerData.ScopeProvider = null;
// Attempt to return the LogRecord to the pool. This will no-op
// if a batch exporter has added a reference.

View File

@ -34,7 +34,7 @@ namespace OpenTelemetry.Logs
internal LogRecordData Data;
internal LogRecordILoggerData ILoggerData;
internal List<KeyValuePair<string, object?>>? AttributeStorage;
internal List<object?>? BufferedScopes;
internal List<object?>? ScopeStorage;
internal int PoolReferenceCount = int.MaxValue;
private static readonly Action<object?, List<object?>> AddScopeToBufferedList = (object? scope, List<object?> state) =>
@ -75,6 +75,7 @@ namespace OpenTelemetry.Logs
EventId = eventId,
Exception = exception,
State = state,
ScopeProvider = scopeProvider,
};
if (stateValues != null && stateValues.Count > 0)
@ -90,8 +91,6 @@ namespace OpenTelemetry.Logs
this.InstrumentationScope = null;
this.Attributes = stateValues;
this.ScopeProvider = scopeProvider;
}
/// <summary>
@ -262,8 +261,6 @@ namespace OpenTelemetry.Logs
/// </summary>
public InstrumentationScope? InstrumentationScope { get; internal set; }
internal IExternalScopeProvider? ScopeProvider { get; set; }
/// <summary>
/// Executes callback for each currently active scope objects in order
/// of creation. All callbacks are guaranteed to be called inline from
@ -281,16 +278,16 @@ namespace OpenTelemetry.Logs
var forEachScopeState = new ScopeForEachState<TState>(callback, state);
if (this.BufferedScopes != null)
if (this.ILoggerData.BufferedScopes != null)
{
foreach (object? scope in this.BufferedScopes)
foreach (object? scope in this.ILoggerData.BufferedScopes)
{
ScopeForEachState<TState>.ForEachScope(scope, forEachScopeState);
}
}
else
{
this.ScopeProvider?.ForEachScope(ScopeForEachState<TState>.ForEachScope, forEachScopeState);
this.ILoggerData.ScopeProvider?.ForEachScope(ScopeForEachState<TState>.ForEachScope, forEachScopeState);
}
}
@ -330,13 +327,14 @@ namespace OpenTelemetry.Logs
// directly below.
this.BufferLogScopes();
return new()
var copy = new LogRecord()
{
Data = this.Data,
ILoggerData = this.ILoggerData,
ILoggerData = this.ILoggerData.Copy(),
Attributes = this.Attributes == null ? null : new List<KeyValuePair<string, object?>>(this.Attributes),
BufferedScopes = this.BufferedScopes == null ? null : new List<object?>(this.BufferedScopes),
};
return copy;
}
/// <summary>
@ -370,16 +368,19 @@ namespace OpenTelemetry.Logs
/// </summary>
private void BufferLogScopes()
{
if (this.ScopeProvider == null)
var scopeProvider = this.ILoggerData.ScopeProvider;
if (scopeProvider == null)
{
return;
}
List<object?> scopes = this.BufferedScopes ??= new List<object?>(LogRecordPoolHelper.DefaultMaxNumberOfScopes);
var scopeStorage = this.ScopeStorage ??= new List<object?>(LogRecordPoolHelper.DefaultMaxNumberOfScopes);
this.ScopeProvider.ForEachScope(AddScopeToBufferedList, scopes);
scopeProvider.ForEachScope(AddScopeToBufferedList, scopeStorage);
this.ScopeProvider = null;
this.ILoggerData.ScopeProvider = null;
this.ILoggerData.BufferedScopes = scopeStorage;
}
internal struct LogRecordILoggerData
@ -390,6 +391,29 @@ namespace OpenTelemetry.Logs
public string? FormattedMessage;
public Exception? Exception;
public object? State;
public IExternalScopeProvider? ScopeProvider;
public List<object?>? BufferedScopes;
public LogRecordILoggerData Copy()
{
var copy = new LogRecordILoggerData
{
TraceState = this.TraceState,
CategoryName = this.CategoryName,
EventId = this.EventId,
FormattedMessage = this.FormattedMessage,
Exception = this.Exception,
State = this.State,
};
var bufferedScopes = this.BufferedScopes;
if (bufferedScopes != null)
{
copy.BufferedScopes = new List<object?>(bufferedScopes);
}
return copy;
}
}
private readonly struct ScopeForEachState<TState>

View File

@ -41,19 +41,19 @@ namespace OpenTelemetry.Logs
}
}
var bufferedScopes = logRecord.BufferedScopes;
if (bufferedScopes != null)
var scopeStorage = logRecord.ScopeStorage;
if (scopeStorage != null)
{
if (bufferedScopes.Count > DefaultMaxNumberOfScopes)
if (scopeStorage.Count > DefaultMaxNumberOfScopes)
{
// Don't allow the pool to grow unconstained.
logRecord.BufferedScopes = null;
logRecord.ScopeStorage = null;
}
else
{
/* List<T>.Clear sets the count/size to 0 but it maintains the
underlying array (capacity). */
bufferedScopes.Clear();
scopeStorage.Clear();
}
}
}

View File

@ -41,7 +41,7 @@ namespace OpenTelemetry.Logs.Tests
var state = new LogRecordTest.DisposingState("Hello world");
logRecord.ScopeProvider = scopeProvider;
logRecord.ILoggerData.ScopeProvider = scopeProvider;
logRecord.StateValues = state;
processor.OnEnd(logRecord);
@ -50,10 +50,10 @@ namespace OpenTelemetry.Logs.Tests
Assert.Empty(exportedItems);
Assert.Null(logRecord.ScopeProvider);
Assert.Null(logRecord.ILoggerData.ScopeProvider);
Assert.False(ReferenceEquals(state, logRecord.StateValues));
Assert.NotNull(logRecord.AttributeStorage);
Assert.NotNull(logRecord.BufferedScopes);
Assert.NotNull(logRecord.ILoggerData.BufferedScopes);
KeyValuePair<string, object> actualState = logRecord.StateValues[0];

View File

@ -136,19 +136,19 @@ namespace OpenTelemetry.Logs.Tests
new KeyValuePair<string, object?>("key1", "value1"),
new KeyValuePair<string, object?>("key2", "value2"),
};
logRecord1.BufferedScopes = new List<object?>(8) { null, null };
logRecord1.ScopeStorage = new List<object?>(8) { null, null };
pool.Return(logRecord1);
Assert.Empty(logRecord1.AttributeStorage);
Assert.Equal(16, logRecord1.AttributeStorage.Capacity);
Assert.Empty(logRecord1.BufferedScopes);
Assert.Equal(8, logRecord1.BufferedScopes.Capacity);
Assert.Empty(logRecord1.ScopeStorage);
Assert.Equal(8, logRecord1.ScopeStorage.Capacity);
logRecord1 = pool.Rent();
Assert.NotNull(logRecord1.AttributeStorage);
Assert.NotNull(logRecord1.BufferedScopes);
Assert.NotNull(logRecord1.ScopeStorage);
for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfAttributes; i++)
{
@ -157,13 +157,13 @@ namespace OpenTelemetry.Logs.Tests
for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfScopes; i++)
{
logRecord1.BufferedScopes!.Add(null);
logRecord1.ScopeStorage!.Add(null);
}
pool.Return(logRecord1);
Assert.Null(logRecord1.AttributeStorage);
Assert.Null(logRecord1.BufferedScopes);
Assert.Null(logRecord1.ScopeStorage);
}
[Theory]

View File

@ -934,6 +934,39 @@ namespace OpenTelemetry.Logs.Tests
Assert.Same("Hello world", actualState.Value);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void ReusedLogRecordScopeTest(bool buffer)
{
var processor = new ScopeProcessor(buffer);
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.AddProcessor(processor);
});
});
var logger = loggerFactory.CreateLogger("TestLogger");
using (var scope1 = logger.BeginScope("scope1"))
{
logger.LogInformation("message1");
}
using (var scope2 = logger.BeginScope("scope2"))
{
logger.LogInformation("message2");
}
Assert.Equal(2, processor.Scopes.Count);
Assert.Equal("scope1", processor.Scopes[0]);
Assert.Equal("scope2", processor.Scopes[1]);
}
private static ILoggerFactory InitializeLoggerFactory(out List<LogRecord> exportedItems, Action<OpenTelemetryLoggerOptions> configure = null)
{
var items = exportedItems = new List<LogRecord>();
@ -1084,5 +1117,32 @@ namespace OpenTelemetry.Logs.Tests
public string Property3 { get; set; }
}
private class ScopeProcessor : BaseProcessor<LogRecord>
{
private readonly bool buffer;
public ScopeProcessor(bool buffer)
{
this.buffer = buffer;
}
public List<object> Scopes { get; } = new();
public override void OnEnd(LogRecord data)
{
data.ForEachScope<object>(
(scope, state) =>
{
this.Scopes.Add(scope.Scope);
},
null);
if (this.buffer)
{
data.Buffer();
}
}
}
}
}

View File

@ -57,19 +57,19 @@ namespace OpenTelemetry.Logs.Tests
new KeyValuePair<string, object?>("key1", "value1"),
new KeyValuePair<string, object?>("key2", "value2"),
};
logRecord1.BufferedScopes = new List<object?>(8) { null, null };
logRecord1.ScopeStorage = new List<object?>(8) { null, null };
LogRecordThreadStaticPool.Instance.Return(logRecord1);
Assert.Empty(logRecord1.AttributeStorage);
Assert.Equal(16, logRecord1.AttributeStorage.Capacity);
Assert.Empty(logRecord1.BufferedScopes);
Assert.Equal(8, logRecord1.BufferedScopes.Capacity);
Assert.Empty(logRecord1.ScopeStorage);
Assert.Equal(8, logRecord1.ScopeStorage.Capacity);
logRecord1 = LogRecordThreadStaticPool.Instance.Rent();
Assert.NotNull(logRecord1.AttributeStorage);
Assert.NotNull(logRecord1.BufferedScopes);
Assert.NotNull(logRecord1.ScopeStorage);
for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfAttributes; i++)
{
@ -78,13 +78,13 @@ namespace OpenTelemetry.Logs.Tests
for (int i = 0; i <= LogRecordPoolHelper.DefaultMaxNumberOfScopes; i++)
{
logRecord1.BufferedScopes!.Add(null);
logRecord1.ScopeStorage!.Add(null);
}
LogRecordThreadStaticPool.Instance.Return(logRecord1);
Assert.Null(logRecord1.AttributeStorage);
Assert.Null(logRecord1.BufferedScopes);
Assert.Null(logRecord1.ScopeStorage);
}
}
}