Logs: Scope improvements (#2026)
This commit is contained in:
parent
9805587151
commit
6cdfcea67c
|
|
@ -44,12 +44,21 @@ internal class MyExporter : BaseExporter<LogRecord>
|
|||
|
||||
sb.Append($"{record}(");
|
||||
|
||||
int scopeDepth = -1;
|
||||
|
||||
record.ForEachScope(ProcessScope, sb);
|
||||
|
||||
static void ProcessScope(object scope, StringBuilder builder)
|
||||
=> builder.Append($"{scope}");
|
||||
void ProcessScope(LogRecordScope scope, StringBuilder builder)
|
||||
{
|
||||
if (++scopeDepth > 0)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
|
||||
sb.Append($")");
|
||||
builder.Append($"{scope.Scope}");
|
||||
}
|
||||
|
||||
sb.Append(')');
|
||||
}
|
||||
|
||||
Console.WriteLine($"{this.name}.Export([{sb.ToString()}])");
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ using System.Linq;
|
|||
using System.Net.Http;
|
||||
using Examples.AspNetCore.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Examples.AspNetCore.Controllers
|
||||
{
|
||||
|
|
@ -32,23 +33,39 @@ namespace Examples.AspNetCore.Controllers
|
|||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching",
|
||||
};
|
||||
|
||||
private static HttpClient httpClient = new HttpClient();
|
||||
private static readonly HttpClient HttpClient = new HttpClient();
|
||||
|
||||
private readonly ILogger<WeatherForecastController> logger;
|
||||
|
||||
public WeatherForecastController(ILogger<WeatherForecastController> logger)
|
||||
{
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IEnumerable<WeatherForecast> Get()
|
||||
{
|
||||
using var scope = this.logger.BeginScope("{Id}", Guid.NewGuid().ToString("N"));
|
||||
|
||||
// Making an http call here to serve as an example of
|
||||
// how dependency calls will be captured and treated
|
||||
// automatically as child of incoming request.
|
||||
var res = httpClient.GetStringAsync("http://google.com").Result;
|
||||
var res = HttpClient.GetStringAsync("http://google.com").Result;
|
||||
var rng = new Random();
|
||||
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast
|
||||
{
|
||||
Date = DateTime.Now.AddDays(index),
|
||||
TemperatureC = rng.Next(-20, 55),
|
||||
Summary = Summaries[rng.Next(Summaries.Length)],
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
this.logger.LogInformation(
|
||||
"WeatherForecasts generated {count}: {forecasts}",
|
||||
forecast.Length,
|
||||
forecast);
|
||||
|
||||
return forecast;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@
|
|||
// </copyright>
|
||||
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Logs;
|
||||
|
||||
namespace Examples.AspNetCore
|
||||
{
|
||||
|
|
@ -31,6 +34,23 @@ namespace Examples.AspNetCore
|
|||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
})
|
||||
.ConfigureLogging((context, builder) =>
|
||||
{
|
||||
builder.ClearProviders();
|
||||
builder.AddConsole();
|
||||
|
||||
var useLogging = context.Configuration.GetValue<bool>("UseLogging");
|
||||
if (useLogging)
|
||||
{
|
||||
builder.AddOpenTelemetry(options =>
|
||||
{
|
||||
options.IncludeScopes = true;
|
||||
options.ParseStateValues = true;
|
||||
options.IncludeFormattedMessage = true;
|
||||
options.AddConsoleExporter();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
},
|
||||
"AllowedHosts": "*",
|
||||
"UseExporter": "console",
|
||||
"UseLogging": true,
|
||||
"Jaeger": {
|
||||
"ServiceName": "jaeger-test",
|
||||
"AgentHost": "localhost",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#if NET461 || NETSTANDARD2_0
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Resources;
|
||||
|
||||
|
|
@ -64,10 +65,22 @@ namespace OpenTelemetry.Exporter
|
|||
this.WriteLine($"{"LogRecord.Exception:".PadRight(RightPaddingLength)}{logRecord.Exception?.Message}");
|
||||
}
|
||||
|
||||
int scopeDepth = -1;
|
||||
|
||||
logRecord.ForEachScope(ProcessScope, this);
|
||||
|
||||
static void ProcessScope(object scope, ConsoleLogRecordExporter exporter)
|
||||
=> exporter.WriteLine($"{"LogRecord.Scope:".PadRight(RightPaddingLength)}{scope}");
|
||||
void ProcessScope(LogRecordScope scope, ConsoleLogRecordExporter exporter)
|
||||
{
|
||||
if (++scopeDepth == 0)
|
||||
{
|
||||
exporter.WriteLine("LogRecord.ScopeValues (Key:Value):");
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, object> scopeItem in scope)
|
||||
{
|
||||
exporter.WriteLine($"[Scope.{scopeDepth}]:{scopeItem.Key.PadRight(RightPaddingLength)}{scopeItem.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
var resource = this.ParentProvider.GetResource();
|
||||
if (resource != Resource.Empty)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
OpenTelemetry.Logs.LogRecord.ForEachScope<TState>(System.Action<object, TState> callback, TState state) -> void
|
||||
OpenTelemetry.Logs.LogRecord.ForEachScope<TState>(System.Action<OpenTelemetry.Logs.LogRecordScope, TState> callback, TState state) -> void
|
||||
OpenTelemetry.Logs.LogRecord.FormattedMessage.get -> string
|
||||
OpenTelemetry.Logs.LogRecord.StateValues.get -> System.Collections.Generic.IReadOnlyList<System.Collections.Generic.KeyValuePair<string, object>>
|
||||
OpenTelemetry.Logs.LogRecordScope
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair<string, object>
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Dispose() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator(object scope) -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.MoveNext() -> bool
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Reset() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.GetEnumerator() -> OpenTelemetry.Logs.LogRecordScope.Enumerator
|
||||
OpenTelemetry.Logs.LogRecordScope.LogRecordScope() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Scope.get -> object
|
||||
OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.get -> bool
|
||||
OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.set -> void
|
||||
OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeScopes.get -> bool
|
||||
|
|
@ -16,6 +27,7 @@ OpenTelemetry.Trace.TracerProviderBuilderBase
|
|||
OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(string instrumentationName, string instrumentationVersion, System.Func<object> instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider
|
||||
OpenTelemetry.Trace.TracerProviderBuilderBase.TracerProviderBuilderBase() -> void
|
||||
override OpenTelemetry.BatchLogRecordExportProcessor.OnEnd(OpenTelemetry.Logs.LogRecord data) -> void
|
||||
override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation<TInstrumentation>(System.Func<TInstrumentation> instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
override OpenTelemetry.Trace.TracerProviderBuilderBase.AddSource(params string[] names) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
|
|
|
|||
|
|
@ -1,6 +1,17 @@
|
|||
OpenTelemetry.Logs.LogRecord.ForEachScope<TState>(System.Action<object, TState> callback, TState state) -> void
|
||||
OpenTelemetry.Logs.LogRecord.ForEachScope<TState>(System.Action<OpenTelemetry.Logs.LogRecordScope, TState> callback, TState state) -> void
|
||||
OpenTelemetry.Logs.LogRecord.FormattedMessage.get -> string
|
||||
OpenTelemetry.Logs.LogRecord.StateValues.get -> System.Collections.Generic.IReadOnlyList<System.Collections.Generic.KeyValuePair<string, object>>
|
||||
OpenTelemetry.Logs.LogRecordScope
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Current.get -> System.Collections.Generic.KeyValuePair<string, object>
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Dispose() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Enumerator(object scope) -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.MoveNext() -> bool
|
||||
OpenTelemetry.Logs.LogRecordScope.Enumerator.Reset() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.GetEnumerator() -> OpenTelemetry.Logs.LogRecordScope.Enumerator
|
||||
OpenTelemetry.Logs.LogRecordScope.LogRecordScope() -> void
|
||||
OpenTelemetry.Logs.LogRecordScope.Scope.get -> object
|
||||
OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.get -> bool
|
||||
OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeFormattedMessage.set -> void
|
||||
OpenTelemetry.Logs.OpenTelemetryLoggerOptions.IncludeScopes.get -> bool
|
||||
|
|
@ -16,6 +27,7 @@ OpenTelemetry.Trace.TracerProviderBuilderBase
|
|||
OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation(string instrumentationName, string instrumentationVersion, System.Func<object> instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
OpenTelemetry.Trace.TracerProviderBuilderBase.Build() -> OpenTelemetry.Trace.TracerProvider
|
||||
OpenTelemetry.Trace.TracerProviderBuilderBase.TracerProviderBuilderBase() -> void
|
||||
override OpenTelemetry.BatchLogRecordExportProcessor.OnEnd(OpenTelemetry.Logs.LogRecord data) -> void
|
||||
override OpenTelemetry.Trace.TracerProviderBuilderBase.AddInstrumentation<TInstrumentation>(System.Func<TInstrumentation> instrumentationFactory) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
override OpenTelemetry.Trace.TracerProviderBuilderBase.AddLegacySource(string operationName) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
override OpenTelemetry.Trace.TracerProviderBuilderBase.AddSource(params string[] names) -> OpenTelemetry.Trace.TracerProviderBuilder
|
||||
|
|
|
|||
|
|
@ -35,6 +35,13 @@ namespace OpenTelemetry
|
|||
maxExportBatchSize)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEnd(LogRecord data)
|
||||
{
|
||||
data.BufferLogScopes();
|
||||
|
||||
base.OnEnd(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ please check the latest changes
|
|||
public API
|
||||
([#2019](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2019))
|
||||
|
||||
* Fixed an issue causing inconsistent log scopes when using
|
||||
`BatchLogRecordExportProcessor`. To make parsing scopes easier the
|
||||
`LogRecord.ForEachScope` signature has been changed to receive instances of
|
||||
`LogRecordScope` (a new type which implements
|
||||
`IEnumerator<KeyValuePair<string, object>>` for accessing scope items)
|
||||
([#2026](https://github.com/open-telemetry/opentelemetry-dotnet/pull/2026))
|
||||
|
||||
## 1.1.0-beta2
|
||||
|
||||
Released 2021-Apr-23
|
||||
|
|
|
|||
|
|
@ -18,16 +18,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace OpenTelemetry.Logs
|
||||
{
|
||||
/// <summary>
|
||||
/// Log record base class.
|
||||
/// Stores details about a log message.
|
||||
/// </summary>
|
||||
public sealed class LogRecord
|
||||
{
|
||||
private readonly IExternalScopeProvider scopeProvider;
|
||||
private static readonly Action<object, List<object>> AddScopeToBufferedList = (object scope, List<object> state) =>
|
||||
{
|
||||
state.Add(scope);
|
||||
};
|
||||
|
||||
private List<object> bufferedScopes;
|
||||
|
||||
internal LogRecord(
|
||||
IExternalScopeProvider scopeProvider,
|
||||
|
|
@ -40,7 +46,7 @@ namespace OpenTelemetry.Logs
|
|||
Exception exception,
|
||||
IReadOnlyList<KeyValuePair<string, object>> stateValues)
|
||||
{
|
||||
this.scopeProvider = scopeProvider;
|
||||
this.ScopeProvider = scopeProvider;
|
||||
|
||||
var activity = Activity.Current;
|
||||
if (activity != null)
|
||||
|
|
@ -79,23 +85,92 @@ namespace OpenTelemetry.Logs
|
|||
|
||||
public string FormattedMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw state attached to the log. Set to <see
|
||||
/// langword="null"/> when <see
|
||||
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled.
|
||||
/// </summary>
|
||||
public object State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parsed state values attached to the log. Set when <see
|
||||
/// cref="OpenTelemetryLoggerOptions.ParseStateValues"/> is enabled
|
||||
/// otherwise <see langword="null"/>.
|
||||
/// </summary>
|
||||
public IReadOnlyList<KeyValuePair<string, object>> StateValues { get; }
|
||||
|
||||
public Exception Exception { get; }
|
||||
|
||||
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
|
||||
/// this method.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note: Scopes are only available during the lifecycle of the log
|
||||
/// message being written. If you need to capture scopes to be used
|
||||
/// later (for example in batching scenarios), call <see
|
||||
/// cref="BufferLogScopes"/> to safely capture the values (incurs
|
||||
/// allocation).
|
||||
/// </remarks>
|
||||
/// <typeparam name="TState">State.</typeparam>
|
||||
/// <param name="callback">The callback to be executed for every scope object.</param>
|
||||
/// <param name="state">The state object to be passed into the callback.</param>
|
||||
public void ForEachScope<TState>(Action<object, TState> callback, TState state)
|
||||
public void ForEachScope<TState>(Action<LogRecordScope, TState> callback, TState state)
|
||||
{
|
||||
this.scopeProvider?.ForEachScope(callback, state);
|
||||
var forEachScopeState = new ScopeForEachState<TState>(callback, state);
|
||||
|
||||
if (this.bufferedScopes != null)
|
||||
{
|
||||
foreach (object scope in this.bufferedScopes)
|
||||
{
|
||||
ScopeForEachState<TState>.ForEachScope(scope, forEachScopeState);
|
||||
}
|
||||
}
|
||||
else if (this.ScopeProvider != null)
|
||||
{
|
||||
this.ScopeProvider.ForEachScope(ScopeForEachState<TState>.ForEachScope, forEachScopeState);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Buffers the scopes attached to the log into a list so that they can
|
||||
/// be safely processed after the log message lifecycle has ended.
|
||||
/// </summary>
|
||||
internal void BufferLogScopes()
|
||||
{
|
||||
if (this.ScopeProvider == null || this.bufferedScopes != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<object> scopes = new List<object>();
|
||||
|
||||
this.ScopeProvider?.ForEachScope(AddScopeToBufferedList, scopes);
|
||||
|
||||
this.bufferedScopes = scopes;
|
||||
}
|
||||
|
||||
private readonly struct ScopeForEachState<TState>
|
||||
{
|
||||
public static readonly Action<object, ScopeForEachState<TState>> ForEachScope = (object scope, ScopeForEachState<TState> state) =>
|
||||
{
|
||||
LogRecordScope logRecordScope = new LogRecordScope(scope);
|
||||
|
||||
state.Callback(logRecordScope, state.UserState);
|
||||
};
|
||||
|
||||
public readonly Action<LogRecordScope, TState> Callback;
|
||||
|
||||
public readonly TState UserState;
|
||||
|
||||
public ScopeForEachState(Action<LogRecordScope, TState> callback, TState state)
|
||||
{
|
||||
this.Callback = callback;
|
||||
this.UserState = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
// <copyright file="LogRecordScope.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>
|
||||
|
||||
#if NET461 || NETSTANDARD2_0
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenTelemetry.Logs
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores details about a scope attached to a log message.
|
||||
/// </summary>
|
||||
public readonly struct LogRecordScope
|
||||
{
|
||||
internal LogRecordScope(object scope)
|
||||
{
|
||||
this.Scope = scope;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw scope value.
|
||||
/// </summary>
|
||||
public object Scope { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IEnumerator"/> for looping over the inner values
|
||||
/// of the scope.
|
||||
/// </summary>
|
||||
/// <returns><see cref="Enumerator"/>.</returns>
|
||||
public Enumerator GetEnumerator() => new Enumerator(this.Scope);
|
||||
|
||||
/// <summary>
|
||||
/// LogRecordScope enumerator.
|
||||
/// </summary>
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<string, object>>
|
||||
{
|
||||
private readonly IReadOnlyList<KeyValuePair<string, object>> scope;
|
||||
private int position;
|
||||
|
||||
public Enumerator(object scope)
|
||||
{
|
||||
if (scope is IReadOnlyList<KeyValuePair<string, object>> scopeList)
|
||||
{
|
||||
this.scope = scopeList;
|
||||
}
|
||||
else if (scope is IEnumerable<KeyValuePair<string, object>> scopeEnumerable)
|
||||
{
|
||||
this.scope = new List<KeyValuePair<string, object>>(scopeEnumerable);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.scope = new List<KeyValuePair<string, object>>
|
||||
{
|
||||
new KeyValuePair<string, object>(string.Empty, scope),
|
||||
};
|
||||
}
|
||||
|
||||
this.position = 0;
|
||||
this.Current = default;
|
||||
}
|
||||
|
||||
public KeyValuePair<string, object> Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => this.Current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (this.position < this.scope.Count)
|
||||
{
|
||||
this.Current = this.scope[this.position++];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
=> throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -60,6 +60,8 @@ namespace OpenTelemetry.Logs
|
|||
options.ParseStateValues ? this.ParseState(state) : null);
|
||||
|
||||
processor.OnEnd(record);
|
||||
|
||||
record.ScopeProvider = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
// <copyright file="LogScopeBenchmarks.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>
|
||||
|
||||
#if NETCOREAPP3_1
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenTelemetry.Logs;
|
||||
|
||||
namespace Benchmarks.Logs
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class LogScopeBenchmarks
|
||||
{
|
||||
private readonly LoggerExternalScopeProvider scopeProvider = new LoggerExternalScopeProvider();
|
||||
|
||||
private readonly Action<LogRecordScope, object> callback = (LogRecordScope scope, object state) =>
|
||||
{
|
||||
foreach (KeyValuePair<string, object> scopeItem in scope)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
private readonly LogRecord logRecord;
|
||||
|
||||
public LogScopeBenchmarks()
|
||||
{
|
||||
this.scopeProvider.Push(new ReadOnlyCollection<KeyValuePair<string, object>>(
|
||||
new List<KeyValuePair<string, object>>
|
||||
{
|
||||
new KeyValuePair<string, object>("item1", "value1"),
|
||||
new KeyValuePair<string, object>("item2", "value2"),
|
||||
}));
|
||||
this.scopeProvider.Push(new ReadOnlyCollection<KeyValuePair<string, object>>(
|
||||
new List<KeyValuePair<string, object>>
|
||||
{
|
||||
new KeyValuePair<string, object>("item3", "value3"),
|
||||
}));
|
||||
this.scopeProvider.Push(new ReadOnlyCollection<KeyValuePair<string, object>>(
|
||||
new List<KeyValuePair<string, object>>
|
||||
{
|
||||
new KeyValuePair<string, object>("item4", "value4"),
|
||||
new KeyValuePair<string, object>("item5", "value5"),
|
||||
}));
|
||||
|
||||
this.logRecord = new LogRecord(
|
||||
this.scopeProvider,
|
||||
DateTime.UtcNow,
|
||||
"Benchmark",
|
||||
LogLevel.Information,
|
||||
0,
|
||||
"Message",
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ForEachScope()
|
||||
{
|
||||
this.logRecord.ForEachScope(this.callback, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -32,7 +32,7 @@ using Xunit;
|
|||
|
||||
namespace OpenTelemetry.Tests.Logs
|
||||
{
|
||||
public class LogRecordTest : IDisposable
|
||||
public sealed class LogRecordTest : IDisposable
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly List<LogRecord> exportedItems = new List<LogRecord>();
|
||||
|
|
@ -48,7 +48,7 @@ namespace OpenTelemetry.Tests.Logs
|
|||
public LogRecordTest()
|
||||
{
|
||||
this.exporter = new InMemoryExporter<LogRecord>(this.exportedItems);
|
||||
this.processor = new SimpleLogRecordExportProcessor(this.exporter);
|
||||
this.processor = new TestLogRecordProcessor(this.exporter);
|
||||
#if NETCOREAPP2_1
|
||||
var serviceCollection = new ServiceCollection().AddLogging(builder =>
|
||||
#else
|
||||
|
|
@ -348,7 +348,7 @@ namespace OpenTelemetry.Tests.Logs
|
|||
var logRecord = this.exportedItems[0];
|
||||
|
||||
List<object> scopes = new List<object>();
|
||||
logRecord.ForEachScope<object>((scope, state) => scopes.Add(scope), null);
|
||||
logRecord.ForEachScope<object>((scope, state) => scopes.Add(scope.Scope), null);
|
||||
Assert.Empty(scopes);
|
||||
|
||||
this.options.IncludeScopes = true;
|
||||
|
|
@ -357,21 +357,85 @@ namespace OpenTelemetry.Tests.Logs
|
|||
this.logger.LogInformation("OpenTelemetry!");
|
||||
logRecord = this.exportedItems[1];
|
||||
|
||||
logRecord.ForEachScope<object>((scope, state) => scopes.Add(scope), null);
|
||||
int reachedDepth = -1;
|
||||
logRecord.ForEachScope<object>(
|
||||
(scope, state) =>
|
||||
{
|
||||
reachedDepth++;
|
||||
scopes.Add(scope.Scope);
|
||||
foreach (KeyValuePair<string, object> item in scope)
|
||||
{
|
||||
Assert.Equal(string.Empty, item.Key);
|
||||
Assert.Equal("string_scope", item.Value);
|
||||
}
|
||||
},
|
||||
null);
|
||||
Assert.Single(scopes);
|
||||
Assert.Equal(0, reachedDepth);
|
||||
Assert.Equal("string_scope", scopes[0]);
|
||||
|
||||
scopes.Clear();
|
||||
|
||||
using var scope2 = this.logger.BeginScope(2021);
|
||||
List<KeyValuePair<string, object>> expectedScope2 = new List<KeyValuePair<string, object>>
|
||||
{
|
||||
new KeyValuePair<string, object>("item1", "value1"),
|
||||
new KeyValuePair<string, object>("item2", "value2"),
|
||||
};
|
||||
using var scope2 = this.logger.BeginScope(expectedScope2);
|
||||
|
||||
this.logger.LogInformation("OpenTelemetry!");
|
||||
logRecord = this.exportedItems[2];
|
||||
|
||||
logRecord.ForEachScope<object>((scope, state) => scopes.Add(scope), null);
|
||||
reachedDepth = -1;
|
||||
logRecord.ForEachScope<object>(
|
||||
(scope, state) =>
|
||||
{
|
||||
scopes.Add(scope.Scope);
|
||||
if (reachedDepth++ == 1)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> item in scope)
|
||||
{
|
||||
Assert.Contains(item, expectedScope2);
|
||||
}
|
||||
}
|
||||
},
|
||||
null);
|
||||
Assert.Equal(2, scopes.Count);
|
||||
Assert.Equal(1, reachedDepth);
|
||||
Assert.Equal("string_scope", scopes[0]);
|
||||
Assert.Equal(2021, scopes[1]);
|
||||
Assert.Same(expectedScope2, scopes[1]);
|
||||
|
||||
scopes.Clear();
|
||||
|
||||
KeyValuePair<string, object>[] expectedScope3 = new KeyValuePair<string, object>[]
|
||||
{
|
||||
new KeyValuePair<string, object>("item3", "value3"),
|
||||
new KeyValuePair<string, object>("item4", "value4"),
|
||||
};
|
||||
using var scope3 = this.logger.BeginScope(expectedScope3);
|
||||
|
||||
this.logger.LogInformation("OpenTelemetry!");
|
||||
logRecord = this.exportedItems[3];
|
||||
|
||||
reachedDepth = -1;
|
||||
logRecord.ForEachScope<object>(
|
||||
(scope, state) =>
|
||||
{
|
||||
scopes.Add(scope.Scope);
|
||||
if (reachedDepth++ == 2)
|
||||
{
|
||||
foreach (KeyValuePair<string, object> item in scope)
|
||||
{
|
||||
Assert.Contains(item, expectedScope3);
|
||||
}
|
||||
}
|
||||
},
|
||||
null);
|
||||
Assert.Equal(3, scopes.Count);
|
||||
Assert.Equal(2, reachedDepth);
|
||||
Assert.Equal("string_scope", scopes[0]);
|
||||
Assert.Same(expectedScope2, scopes[1]);
|
||||
Assert.Same(expectedScope3, scopes[2]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -596,6 +660,21 @@ namespace OpenTelemetry.Tests.Logs
|
|||
{
|
||||
public string Property { get; set; }
|
||||
}
|
||||
|
||||
private class TestLogRecordProcessor : SimpleExportProcessor<LogRecord>
|
||||
{
|
||||
public TestLogRecordProcessor(BaseExporter<LogRecord> exporter)
|
||||
: base(exporter)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEnd(LogRecord data)
|
||||
{
|
||||
data.BufferLogScopes();
|
||||
|
||||
base.OnEnd(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue