-
-
Notifications
You must be signed in to change notification settings - Fork 220
feat(logs): add Buffering and Batching #4310
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
base: feat/logs
Are you sure you want to change the base?
Changes from 8 commits
d24d165
aad0599
76fcc1b
2ad33f6
38e1c04
f7a43b8
e6b0b74
a84b78f
53c90ea
6580632
6e2ee9b
0774709
d9ae794
7e1f5ea
365a2fb
c478391
211beea
c699c2d
b21b537
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
namespace Sentry.Internal; | ||
|
||
/// <summary> | ||
/// A slim wrapper over an <see cref="System.Array"/>, | ||
/// intended for buffering. | ||
/// </summary> | ||
internal sealed class BatchBuffer<T> | ||
{ | ||
private readonly T[] _array; | ||
private int _count; | ||
|
||
public BatchBuffer(int capacity) | ||
{ | ||
ThrowIfNegativeOrZero(capacity, nameof(capacity)); | ||
|
||
_array = new T[capacity]; | ||
_count = 0; | ||
} | ||
|
||
internal int Count => _count; | ||
internal int Capacity => _array.Length; | ||
internal bool IsEmpty => _count == 0 && _array.Length != 0; | ||
internal bool IsFull => _count == _array.Length; | ||
|
||
internal bool TryAdd(T item) | ||
{ | ||
if (_count < _array.Length) | ||
{ | ||
_array[_count] = item; | ||
_count++; | ||
return true; | ||
} | ||
|
||
return false; | ||
} | ||
|
||
internal T[] ToArray() | ||
{ | ||
if (_count == 0) | ||
{ | ||
return Array.Empty<T>(); | ||
} | ||
|
||
var array = new T[_count]; | ||
Array.Copy(_array, array, _count); | ||
return array; | ||
} | ||
|
||
internal void Clear() | ||
{ | ||
if (_count == 0) | ||
{ | ||
return; | ||
} | ||
|
||
var count = _count; | ||
_count = 0; | ||
Array.Clear(_array, 0, count); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to go with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
internal T[] ToArrayAndClear() | ||
{ | ||
var array = ToArray(); | ||
Clear(); | ||
return array; | ||
} | ||
|
||
private static void ThrowIfNegativeOrZero(int capacity, string paramName) | ||
{ | ||
if (capacity <= 0) | ||
{ | ||
ThrowNegativeOrZero(capacity, paramName); | ||
} | ||
} | ||
|
||
private static void ThrowNegativeOrZero(int capacity, string paramName) | ||
{ | ||
throw new ArgumentOutOfRangeException(paramName, capacity, "Argument must neither be negative nor zero."); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,93 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
using System.Timers; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
using Sentry.Protocol; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
using Sentry.Protocol.Envelopes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
namespace Sentry.Internal; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// The Sentry Batch Processor. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// This implementation is not complete yet. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// Also, the specification is still work in progress. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// Sentry Specification: <see href="https://develop.sentry.dev/sdk/telemetry/spans/batch-processor/"/>. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// OpenTelemetry spec: <see href="https://github.com/open-telemetry/opentelemetry-collector/blob/main/processor/batchprocessor/README.md"/>. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
internal sealed class BatchProcessor : IDisposable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could think about giving this a more specific/descriptive name. If we weren't worried about long names, I guess it would be a Alternatively, it could be made generic (like the |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private readonly IHub _hub; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private readonly BatchProcessorTimer _timer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private readonly BatchBuffer<SentryLog> _logs; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like we have a Any reason not to use a |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private readonly object _lock; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private DateTime _lastFlush = DateTime.MinValue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public BatchProcessor(IHub hub, int batchCount, TimeSpan batchInterval) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
: this(hub, batchCount, new TimersBatchProcessorTimer(batchInterval)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public BatchProcessor(IHub hub, int batchCount, BatchProcessorTimer timer) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_hub = hub; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer = timer; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer.Elapsed += OnIntervalElapsed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_logs = new BatchBuffer<SentryLog>(batchCount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_lock = new object(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
internal void Enqueue(SentryLog log) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lock (_lock) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
EnqueueCore(log); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private void EnqueueCore(SentryLog log) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is only called from the internal |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var isFirstLog = _logs.IsEmpty; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var added = _logs.TryAdd(log); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Debug.Assert(added, $"Since we currently have no lock-free scenario, it's unexpected to exceed the {nameof(BatchBuffer<SentryLog>)}'s capacity."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (isFirstLog && !_logs.IsFull) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer.Enabled = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
else if (_logs.IsFull) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer.Enabled = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Flush(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're doing way too much work in the critical path. Think about what absolutely must happen inside the lock, and what we can move/copy around to have it happen async. Once the reference that's mutated concurrently is swapped with a new one, new messages can start logging, and you can continue async doing capture stuff. Like allocating the strucutered log, envelope, calling Capture etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might be better off using a couple of arrays and doing some reference swap. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did start out with a lock-free solution for non-flushing logs. Thanks for the feedback ... also thanks @bitsandfoxes. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private void Flush() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_lastFlush = DateTime.UtcNow; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
var logs = _logs.ToArrayAndClear(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_ = _hub.CaptureEnvelope(Envelope.FromLog(new StructuredLog(logs))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
private void OnIntervalElapsed(object? sender, ElapsedEventArgs e) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer.Enabled = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
lock (_lock) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!_logs.IsEmpty && e.SignalTime > _lastFlush) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Flush(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The locking strategy could potentially cause performance issues under high load. Consider whether the lock needs to be held for the entire duration of the flush operation, or if it could be released earlier to allow more items to be enqueued while the flush is in progress.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
public void Dispose() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer.Enabled = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer.Elapsed -= OnIntervalElapsed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_timer.Dispose(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using System.Timers; | ||
|
||
namespace Sentry.Internal; | ||
|
||
internal abstract class BatchProcessorTimer : IDisposable | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
protected BatchProcessorTimer() | ||
{ | ||
} | ||
|
||
public abstract bool Enabled { get; set; } | ||
|
||
public abstract event EventHandler<ElapsedEventArgs> Elapsed; | ||
|
||
public void Dispose() | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
Dispose(true); | ||
GC.SuppressFinalize(this); | ||
} | ||
|
||
protected virtual void Dispose(bool disposing) | ||
{ | ||
} | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
internal sealed class TimersBatchProcessorTimer : BatchProcessorTimer | ||
{ | ||
private readonly System.Timers.Timer _timer; | ||
|
||
public TimersBatchProcessorTimer(TimeSpan interval) | ||
{ | ||
_timer = new System.Timers.Timer(interval.TotalMilliseconds) | ||
{ | ||
AutoReset = false, | ||
Enabled = false, | ||
}; | ||
_timer.Elapsed += OnElapsed; | ||
} | ||
|
||
public override bool Enabled | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
get => _timer.Enabled; | ||
set => _timer.Enabled = value; | ||
} | ||
|
||
public override event EventHandler<ElapsedEventArgs>? Elapsed; | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private void OnElapsed(object? sender, ElapsedEventArgs e) | ||
{ | ||
Elapsed?.Invoke(sender, e); | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
protected override void Dispose(bool disposing) | ||
{ | ||
if (disposing) | ||
{ | ||
_timer.Elapsed -= OnElapsed; | ||
_timer.Dispose(); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,6 +1,5 @@ | ||||||||||||||||||||||||||||||||||||||||
using Sentry.Extensibility; | ||||||||||||||||||||||||||||||||||||||||
using Sentry.Infrastructure; | ||||||||||||||||||||||||||||||||||||||||
using Sentry.Protocol.Envelopes; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
namespace Sentry.Internal; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
|
@@ -10,13 +9,33 @@ internal sealed class DefaultSentryStructuredLogger : SentryStructuredLogger | |||||||||||||||||||||||||||||||||||||||
private readonly SentryOptions _options; | ||||||||||||||||||||||||||||||||||||||||
private readonly ISystemClock _clock; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
private readonly BatchProcessor _batchProcessor; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
internal DefaultSentryStructuredLogger(IHub hub, SentryOptions options, ISystemClock clock) | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
Debug.Assert(options is { Experimental.EnableLogs: true }); | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
_hub = hub; | ||||||||||||||||||||||||||||||||||||||||
_options = options; | ||||||||||||||||||||||||||||||||||||||||
_clock = clock; | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
_batchProcessor = new BatchProcessor(hub, ClampBatchCount(options.Experimental.InternalBatchSize), ClampBatchInterval(options.Experimental.InternalBatchTimeout)); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
private static int ClampBatchCount(int batchCount) | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this go in the setter for the If an invalid value has been configured, should we either throw an exception (we'd only do that if it happened when initialising the options - we don't generally want the SDK to throw exceptions otherwise) or at the very least log a message letting people know they're not configuring things correctly? |
||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
return batchCount <= 0 | ||||||||||||||||||||||||||||||||||||||||
? 1 | ||||||||||||||||||||||||||||||||||||||||
: batchCount > 1_000_000 | ||||||||||||||||||||||||||||||||||||||||
? 1_000_000 | ||||||||||||||||||||||||||||||||||||||||
: batchCount; | ||||||||||||||||||||||||||||||||||||||||
Comment on lines
+24
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
private static TimeSpan ClampBatchInterval(TimeSpan batchInterval) | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, could this be moved to the setter? |
||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
return batchInterval.TotalMilliseconds is <= 0 or > int.MaxValue | ||||||||||||||||||||||||||||||||||||||||
? TimeSpan.FromMilliseconds(int.MaxValue) | ||||||||||||||||||||||||||||||||||||||||
: batchInterval; | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
private protected override void CaptureLog(SentryLogLevel level, string template, object[]? parameters, Action<SentryLog>? configureLog) | ||||||||||||||||||||||||||||||||||||||||
|
@@ -71,9 +90,17 @@ private protected override void CaptureLog(SentryLogLevel level, string template | |||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
if (configuredLog is not null) | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
//TODO: enqueue in Batch-Processor / Background-Worker | ||||||||||||||||||||||||||||||||||||||||
// see https://github.com/getsentry/sentry-dotnet/issues/4132 | ||||||||||||||||||||||||||||||||||||||||
_ = _hub.CaptureEnvelope(Envelope.FromLog(configuredLog)); | ||||||||||||||||||||||||||||||||||||||||
_batchProcessor.Enqueue(configuredLog); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
protected override void Dispose(bool disposing) | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
if (disposing) | ||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||
_batchProcessor.Dispose(); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
base.Dispose(disposing); | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} |
jamescrosswell marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using Sentry.Extensibility; | ||
|
||
namespace Sentry.Protocol; | ||
|
||
/// <summary> | ||
/// Represents the Sentry Log protocol. | ||
/// </summary> | ||
/// <remarks> | ||
/// Sentry Docs: <see href="https://docs.sentry.io/product/explore/logs/"/>. | ||
/// Sentry Developer Documentation: <see href="https://develop.sentry.dev/sdk/telemetry/logs/"/>. | ||
/// </remarks> | ||
internal sealed class StructuredLog : ISentryJsonSerializable | ||
{ | ||
private readonly SentryLog[] _items; | ||
|
||
public StructuredLog(SentryLog log) | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
_items = [log]; | ||
Flash0ver marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public StructuredLog(SentryLog[] logs) | ||
{ | ||
_items = logs; | ||
} | ||
|
||
public int Length => _items.Length; | ||
public ReadOnlySpan<SentryLog> Items => _items; | ||
|
||
public void WriteTo(Utf8JsonWriter writer, IDiagnosticLogger? logger) | ||
{ | ||
writer.WriteStartObject(); | ||
writer.WriteStartArray("items"); | ||
|
||
foreach (var log in _items) | ||
{ | ||
log.WriteTo(writer, logger); | ||
} | ||
|
||
writer.WriteEndArray(); | ||
writer.WriteEndObject(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
Clear()
method has an early return for empty buffers, butArray.Clear()
is safe to call even on empty arrays. Consider removing the early return to simplify the code. However, if the early return is for performance reasons, add a comment explaining this optimization.