From 027fa839d8cd039574a796ba6ed6d932f85fea6c Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Mon, 20 Feb 2023 16:16:08 +0100 Subject: [PATCH] Added xmldocs and ensure documentation and pdb are available in nugetpackages --- .github/workflows/ci.yml | 2 +- src/Directory.Build.props | 7 ++- src/Elastic.Channels/BufferOptions.cs | 3 + src/Elastic.Channels/BufferedChannelBase.cs | 61 ++++++++++++++++--- .../BufferedChannelExtensions.cs | 15 ----- .../Buffers/IWriteTrackingBuffer.cs | 4 ++ .../Buffers/OutboundBuffer.cs | 5 ++ src/Elastic.Channels/ChannelOptionsBase.cs | 6 +- .../Diagnostics/ChannelListener.cs | 19 ++++++ .../Diagnostics/DiagnosticsBufferedChannel.cs | 12 +++- .../Diagnostics/NoopBufferedChannel.cs | 24 +++++--- .../ResponseItemsBufferedChannelBase.cs | 9 ++- src/Elastic.Ingest.Apm/ApmChannel.cs | 14 ++++- src/Elastic.Ingest.Apm/ApmChannelOptions.cs | 4 ++ src/Elastic.Ingest.Apm/Helpers/Time.cs | 3 + .../Model/IngestResponse.cs | 6 ++ src/Elastic.Ingest.Apm/Model/Transaction.cs | 14 +++++ .../BootstrapMethod.cs | 1 + .../DataStreams/DataStreamChannel.cs | 9 +++ .../DataStreams/DataStreamChannelOptions.cs | 3 + .../DataStreams/DataStreamName.cs | 7 +++ .../Diagnostics/ChannelListener.cs | 8 +++ .../ElasticsearchChannelBase.Bootstrap.cs | 19 ++++++ .../ElasticsearchChannelBase.cs | 20 ++++++ .../ElasticsearchChannelOptionsBase.cs | 8 ++- .../Indices/IndexChannel.cs | 8 +++ .../Indices/IndexChannelOptions.cs | 5 ++ .../Serialization/BulkOperationHeader.cs | 14 +++++ .../Serialization/BulkResponse.cs | 11 ++++ .../Serialization/BulkResponseItem.cs | 4 ++ .../CustomActivityExporter.cs | 3 + .../CustomOtlpTraceExporter.cs | 14 +++++ .../LibraryVersion.cs | 4 ++ .../TransportChannelBase.cs | 9 ++- .../TransportChannelOptionsBase.cs | 7 +++ 35 files changed, 317 insertions(+), 45 deletions(-) delete mode 100644 src/Elastic.Channels/BufferedChannelExtensions.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a754a9..0168fe6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: name: publish canary packages to feedz.io if: github.event_name == 'push' && startswith(github.ref, 'refs/heads') - - run: ./build.sh generatereleasenotes -s true + - run: ./build.sh generatereleasenotes -s true --token ${{secrets.GITHUB_TOKEN}} name: Generate release notes for tag if: github.event_name == 'push' && startswith(github.ref, 'refs/tags') - run: ./build.sh createreleaseongithub -s true --token ${{secrets.GITHUB_TOKEN}} diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a55a2d4..b5d7b69 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -12,12 +12,17 @@ true ..\..\build\keys\keypair.snk - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb true nuget-icon.png True README.md + + + $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + true + true diff --git a/src/Elastic.Channels/BufferOptions.cs b/src/Elastic.Channels/BufferOptions.cs index 14fb453..f4cb11d 100644 --- a/src/Elastic.Channels/BufferOptions.cs +++ b/src/Elastic.Channels/BufferOptions.cs @@ -7,6 +7,9 @@ namespace Elastic.Channels { + /// + /// Controls how data should be buffered in implementations + /// public class BufferOptions { /// diff --git a/src/Elastic.Channels/BufferedChannelBase.cs b/src/Elastic.Channels/BufferedChannelBase.cs index 38ed640..b6bfdd7 100644 --- a/src/Elastic.Channels/BufferedChannelBase.cs +++ b/src/Elastic.Channels/BufferedChannelBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; @@ -11,12 +12,27 @@ namespace Elastic.Channels; +/// Represents a buffered channel implementation +/// The type of data to be written public interface IBufferedChannel : IDisposable { + /// + /// Tries to write to the inbound channel. + /// Returns immediately if successful or unsuccessful + /// + /// A bool indicating if the write was successful bool TryWrite(TEvent item); + /// + /// Waits for availability on the inbound channel before attempting to write . + /// + /// A bool indicating if the write was successful Task WaitToWriteAsync(TEvent item, CancellationToken ctx = default); + /// + /// Waits for availability on the inbound channel before attempting to write each item in . + /// + /// A bool indicating if all writes werwase successful async Task WaitToWriteManyAsync(IEnumerable events, CancellationToken ctx = default) { var allWritten = true; @@ -27,14 +43,22 @@ async Task WaitToWriteManyAsync(IEnumerable events, CancellationTo } return allWritten; } -} -public abstract class BufferedChannelBase : BufferedChannelBase, TEvent, TResponse> - where TResponse : class, new() -{ - protected BufferedChannelBase(ChannelOptionsBase options) : base(options) { } + /// + /// Tries to write many to the channel returning true if ALL messages were written succesfully + /// + public bool TryWriteMany(IEnumerable events) => + events.Select(e => TryWrite(e)).All(b => b); } +/// +/// The common base implementation of that all implementations inherit from. +/// This sets up the and and the implementation that coordinates moving +/// data from one to the other +/// +/// Concrete channel options implementation +/// The type of data we are looking to +/// The type of responses we are expecting to get back from public abstract class BufferedChannelBase : ChannelWriter, IBufferedChannel where TChannelOptions : ChannelOptionsBase @@ -45,6 +69,7 @@ public abstract class BufferedChannelBase private readonly SemaphoreSlim _throttleTasks; private readonly CountdownEvent? _signal; + /// protected BufferedChannelBase(TChannelOptions options) { TokenSource = new CancellationTokenSource(); @@ -91,19 +116,31 @@ await ConsumeInboundEvents(maxOut, BufferOptions.OutboundBufferMaxLifetime) } + /// + /// All subclasses of need to at a minimum + /// implement this method to export buffered collection of + /// + protected abstract Task Export(IReadOnlyCollection buffer, CancellationToken ctx = default); + + + /// The channel options currently in use public TChannelOptions Options { get; } private CancellationTokenSource TokenSource { get; } - protected Channel> OutChannel { get; } - protected Channel InChannel { get; } - protected BufferOptions BufferOptions => Options.BufferOptions; + private Channel> OutChannel { get; } + private Channel InChannel { get; } + private BufferOptions BufferOptions => Options.BufferOptions; + internal InboundBuffer InboundBuffer { get; } + /// public override ValueTask WaitToWriteAsync(CancellationToken ctx = default) => InChannel.Writer.WaitToWriteAsync(ctx); + /// public override bool TryComplete(Exception? error = null) => InChannel.Writer.TryComplete(error); + /// public override bool TryWrite(TEvent item) { if (InChannel.Writer.TryWrite(item)) @@ -115,6 +152,7 @@ public override bool TryWrite(TEvent item) return false; } + /// public virtual async Task WaitToWriteAsync(TEvent item, CancellationToken ctx = default) { ctx = ctx == default ? TokenSource.Token : ctx; @@ -128,10 +166,12 @@ public virtual async Task WaitToWriteAsync(TEvent item, CancellationToken return false; } - protected abstract Task Export(IReadOnlyCollection buffer, CancellationToken ctx = default); - private static readonly IReadOnlyCollection DefaultRetryBuffer = new TEvent[] { }; + /// + /// Subclasses may override this to yield items from that can be retried. + /// The default implementation of this simply always returns an empty collection + /// protected virtual IReadOnlyCollection RetryBuffer( TResponse response, IReadOnlyCollection currentBuffer, @@ -259,6 +299,7 @@ async Task AsyncSlowPath(IOutboundBuffer b) : new ValueTask(AsyncSlowPath(buffer)); } + /// public virtual void Dispose() { InboundBuffer.Dispose(); diff --git a/src/Elastic.Channels/BufferedChannelExtensions.cs b/src/Elastic.Channels/BufferedChannelExtensions.cs deleted file mode 100644 index 74e2e03..0000000 --- a/src/Elastic.Channels/BufferedChannelExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -using System.Collections.Generic; -using System.Linq; - -namespace Elastic.Channels; - -public static class BufferedChannelExtensions -{ - public static bool TryWriteMany(this IBufferedChannel channel, IEnumerable events) => - events.Select(e => channel.TryWrite(e)).All(b => b); - -} diff --git a/src/Elastic.Channels/Buffers/IWriteTrackingBuffer.cs b/src/Elastic.Channels/Buffers/IWriteTrackingBuffer.cs index 1551fd0..4b3d78a 100644 --- a/src/Elastic.Channels/Buffers/IWriteTrackingBuffer.cs +++ b/src/Elastic.Channels/Buffers/IWriteTrackingBuffer.cs @@ -12,6 +12,10 @@ namespace Elastic.Channels.Buffers; /// public interface IWriteTrackingBuffer { + /// The current size of the buffer int Count { get; } + /// + /// The duration since the first write + /// TimeSpan? DurationSinceFirstWrite { get; } } diff --git a/src/Elastic.Channels/Buffers/OutboundBuffer.cs b/src/Elastic.Channels/Buffers/OutboundBuffer.cs index be88837..ee80c26 100644 --- a/src/Elastic.Channels/Buffers/OutboundBuffer.cs +++ b/src/Elastic.Channels/Buffers/OutboundBuffer.cs @@ -7,8 +7,13 @@ namespace Elastic.Channels.Buffers; +/// +/// The buffer to be exported over +/// +/// Due to change as we move this over to use ArrayPool public interface IOutboundBuffer : IWriteTrackingBuffer { + /// public IReadOnlyCollection Items { get; } } diff --git a/src/Elastic.Channels/ChannelOptionsBase.cs b/src/Elastic.Channels/ChannelOptionsBase.cs index 1283f89..bbde5fc 100644 --- a/src/Elastic.Channels/ChannelOptionsBase.cs +++ b/src/Elastic.Channels/ChannelOptionsBase.cs @@ -17,9 +17,13 @@ namespace Elastic.Channels /// public abstract class ChannelOptionsBase { + /// public BufferOptions BufferOptions { get; set; } = new (); - public Func WriteEvent { get; set; } = null!; + /// + /// Optionally provides a custom write implementation to a channel. Concrete channel implementations are not required to adhere to this config + /// + public Func? WriteEvent { get; set; } = null; /// Called if the call to throws. public Action? ExportExceptionCallback { get; set; } diff --git a/src/Elastic.Channels/Diagnostics/ChannelListener.cs b/src/Elastic.Channels/Diagnostics/ChannelListener.cs index 06d4fa0..970fe4e 100644 --- a/src/Elastic.Channels/Diagnostics/ChannelListener.cs +++ b/src/Elastic.Channels/Diagnostics/ChannelListener.cs @@ -7,15 +7,25 @@ namespace Elastic.Channels.Diagnostics; +/// +/// A very rudimentary diagnostics object tracking various important metrics to provide insights into the +/// machinery of . +/// This will be soon be replaced by actual metrics +/// public class ChannelListener { private readonly string? _name; private int _exportedBuffers; + /// + /// Keeps track of the first observed exception to calls to + /// public Exception? ObservedException { get; private set; } + /// Indicates if the overall publishing was successful public virtual bool PublishSuccess => ObservedException == null && _exportedBuffers > 0 && _maxRetriesExceeded == 0 && _items > 0; + /// public ChannelListener(string? name = null) => _name = name; private long _responses; @@ -31,6 +41,9 @@ public class ChannelListener private bool _outboundChannelExited; // ReSharper disable once MemberCanBeProtected.Global + /// + /// Registers callbacks on to keep track metrics. + /// public ChannelListener Register(ChannelOptionsBase options) { options.BufferOptions.ExportBufferCallback = () => Interlocked.Increment(ref _exportedBuffers); @@ -54,8 +67,14 @@ public ChannelListener Register(ChannelOptionsBase + /// Allows subclasses to include more data in the implementation before the exception is printed + /// protected virtual string AdditionalData => string.Empty; + /// + /// Provides a debug message to give insights to the machinery of + /// public override string ToString() => $@"{(!PublishSuccess ? "Failed" : "Successful")} publish over channel: {_name ?? nameof(ChannelListener)}. Exported Buffers: {_exportedBuffers:N0} Exported Items: {_items:N0} diff --git a/src/Elastic.Channels/Diagnostics/DiagnosticsBufferedChannel.cs b/src/Elastic.Channels/Diagnostics/DiagnosticsBufferedChannel.cs index 7a259b1..6546cc1 100644 --- a/src/Elastic.Channels/Diagnostics/DiagnosticsBufferedChannel.cs +++ b/src/Elastic.Channels/Diagnostics/DiagnosticsBufferedChannel.cs @@ -5,14 +5,15 @@ namespace Elastic.Channels.Diagnostics; /// -/// A NOOP implementation of that: -/// -tracks the number of times is invoked under -/// -observes the maximum concurrent calls to under +/// A NOOP implementation of that: +/// -tracks the number of times is invoked under +/// -observes the maximum concurrent calls to under /// public class DiagnosticsBufferedChannel : NoopBufferedChannel { private readonly string? _name; + /// public DiagnosticsBufferedChannel(BufferOptions options, bool observeConcurrency = false, string? name = null) : base(options, observeConcurrency) { @@ -20,8 +21,13 @@ public DiagnosticsBufferedChannel(BufferOptions options, bool observeConcurrency Listener = new ChannelListener(_name).Register(Options); } + /// + // ReSharper disable once MemberCanBePrivate.Global public ChannelListener Listener { get; } + /// + /// Provides a debug message to give insights to the machinery of + /// public override string ToString() => $@"------------------------------------------ {Listener} diff --git a/src/Elastic.Channels/Diagnostics/NoopBufferedChannel.cs b/src/Elastic.Channels/Diagnostics/NoopBufferedChannel.cs index dddc992..7d26638 100644 --- a/src/Elastic.Channels/Diagnostics/NoopBufferedChannel.cs +++ b/src/Elastic.Channels/Diagnostics/NoopBufferedChannel.cs @@ -10,37 +10,47 @@ namespace Elastic.Channels.Diagnostics; /// -/// A NOOP implementation of that: +/// A NOOP implementation of that: /// -tracks the number of times is invoked under /// -observes the maximum concurrent calls to under /// public class NoopBufferedChannel : BufferedChannelBase { + /// Empty event for use with public class NoopEvent { } + + /// Empty response for use with public class NoopResponse { } + /// Provides options how the should behave public class NoopChannelOptions : ChannelOptionsBase { - public bool ObserverConcurrency { get; set; } + /// If set (defaults:false) will track the max observed concurrency to + public bool TrackConcurrency { get; set; } } + /// public NoopBufferedChannel(BufferOptions options, bool observeConcurrency = false) : base(new NoopChannelOptions { - BufferOptions = options, - ObserverConcurrency = observeConcurrency + BufferOptions = options, TrackConcurrency = observeConcurrency }) { } - private long _exportedBuffers; + /// Returns the number of times was called public long ExportedBuffers => _exportedBuffers; - private int _currentMax; + private long _exportedBuffers; + + /// The maximum observed concurrency to calls to , requires to be set public int ObservedConcurrency { get; private set; } + private int _currentMax; + + /// protected override async Task Export(IReadOnlyCollection buffer, CancellationToken ctx = default) { Interlocked.Increment(ref _exportedBuffers); - if (!Options.ObserverConcurrency) return new NoopResponse(); + if (!Options.TrackConcurrency) return new NoopResponse(); var max = Interlocked.Increment(ref _currentMax); await Task.Delay(TimeSpan.FromMilliseconds(1), ctx).ConfigureAwait(false); diff --git a/src/Elastic.Channels/ResponseItemsBufferedChannelBase.cs b/src/Elastic.Channels/ResponseItemsBufferedChannelBase.cs index d86cdc1..fe9da6d 100644 --- a/src/Elastic.Channels/ResponseItemsBufferedChannelBase.cs +++ b/src/Elastic.Channels/ResponseItemsBufferedChannelBase.cs @@ -9,6 +9,9 @@ namespace Elastic.Channels; +/// +/// Channel options to +/// public abstract class ResponseItemsChannelOptionsBase : ChannelOptionsBase { @@ -27,16 +30,17 @@ public abstract class ResponseItemsBufferedChannelBase where TResponse : class, new() { + /// protected ResponseItemsBufferedChannelBase(TChannelOptions options) : base(options) { } - /// Based on should return a bool indicating if retry is needed + /// Based on should return a bool indicating if retry is needed protected abstract bool Retry(TResponse response); /// Indicates that ALL items have to be retried in which case no further special handling is needed protected abstract bool RetryAllItems(TResponse response); /// - /// Implementers have to implement this to align sent to received 's + /// Implementers have to implement this to align sent to received 's /// protected abstract List<(TEvent, TBulkResponseItem)> Zip(TResponse response, IReadOnlyCollection page); @@ -49,6 +53,7 @@ protected ResponseItemsBufferedChannelBase(TChannelOptions options) : base(optio /// protected abstract bool RejectEvent((TEvent, TBulkResponseItem) @event); + /// > protected override IReadOnlyCollection RetryBuffer(TResponse response, IReadOnlyCollection events, IWriteTrackingBuffer consumedBufferStatistics ) diff --git a/src/Elastic.Ingest.Apm/ApmChannel.cs b/src/Elastic.Ingest.Apm/ApmChannel.cs index 2be1f30..ff59a33 100644 --- a/src/Elastic.Ingest.Apm/ApmChannel.cs +++ b/src/Elastic.Ingest.Apm/ApmChannel.cs @@ -10,6 +10,7 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Elastic.Channels; using Elastic.Ingest.Apm.Model; using Elastic.Ingest.Transport; using Elastic.Transport; @@ -33,21 +34,32 @@ internal static class ApmChannelStatics }; } + /// + /// An implementation that sends V2 intake API data + /// to APM server. + /// public class ApmChannel : TransportChannelBase { + /// public ApmChannel(ApmChannelOptions options) : base(options) { } //retry if APM server returns 429 + /// protected override bool Retry(EventIntakeResponse response) => response.ApiCallDetails.HttpStatusCode == 429; + /// protected override bool RetryAllItems(EventIntakeResponse response) => response.ApiCallDetails.HttpStatusCode == 429; //APM does not return the status for all events sent. Therefor we always return an empty set for individual items to retry - private List<(IIntakeObject, IntakeErrorItem)> _emptyZip = new(); + /// protected override List<(IIntakeObject, IntakeErrorItem)> Zip(EventIntakeResponse response, IReadOnlyCollection page) => _emptyZip; + private List<(IIntakeObject, IntakeErrorItem)> _emptyZip = new(); + /// protected override bool RetryEvent((IIntakeObject, IntakeErrorItem) @event) => false; + /// protected override bool RejectEvent((IIntakeObject, IntakeErrorItem) @event) => false; + /// protected override Task Export(HttpTransport transport, IReadOnlyCollection page, CancellationToken ctx = default) => transport.RequestAsync(HttpMethod.POST, "/intake/v2/events", PostData.StreamHandler(page, diff --git a/src/Elastic.Ingest.Apm/ApmChannelOptions.cs b/src/Elastic.Ingest.Apm/ApmChannelOptions.cs index 101427e..6085fc5 100644 --- a/src/Elastic.Ingest.Apm/ApmChannelOptions.cs +++ b/src/Elastic.Ingest.Apm/ApmChannelOptions.cs @@ -8,8 +8,12 @@ namespace Elastic.Ingest.Apm { + /// + /// Channel options for + /// public class ApmChannelOptions : TransportChannelOptionsBase { + /// public ApmChannelOptions(HttpTransport transport) : base(transport) { } } } diff --git a/src/Elastic.Ingest.Apm/Helpers/Time.cs b/src/Elastic.Ingest.Apm/Helpers/Time.cs index bbf51ea..abe7b4c 100644 --- a/src/Elastic.Ingest.Apm/Helpers/Time.cs +++ b/src/Elastic.Ingest.Apm/Helpers/Time.cs @@ -5,6 +5,7 @@ namespace Elastic.Ingest.Apm.Helpers { + /// public static class Epoch { /// @@ -13,7 +14,9 @@ public static class Epoch /// internal static readonly DateTime UnixEpochDateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + /// public static long ToEpoch(this DateTime d) => ToTimestamp(d); + /// public static long UtcNow => DateTime.UtcNow.ToEpoch(); /// diff --git a/src/Elastic.Ingest.Apm/Model/IngestResponse.cs b/src/Elastic.Ingest.Apm/Model/IngestResponse.cs index 28b8932..473ecf7 100644 --- a/src/Elastic.Ingest.Apm/Model/IngestResponse.cs +++ b/src/Elastic.Ingest.Apm/Model/IngestResponse.cs @@ -7,21 +7,27 @@ namespace Elastic.Ingest.Apm.Model { + /// public class EventIntakeResponse : TransportResponse { + /// [JsonPropertyName("accepted")] public long Accepted { get; set; } + /// [JsonPropertyName("errors")] //[JsonConverter(typeof(ResponseItemsConverter))] public IReadOnlyCollection Errors { get; set; } = null!; } + /// public class IntakeErrorItem { + /// [JsonPropertyName("message")] public string Message { get; set; } = null!; + /// [JsonPropertyName("document")] public string Document { get; set; } = null!; } diff --git a/src/Elastic.Ingest.Apm/Model/Transaction.cs b/src/Elastic.Ingest.Apm/Model/Transaction.cs index 9348705..9da60dc 100644 --- a/src/Elastic.Ingest.Apm/Model/Transaction.cs +++ b/src/Elastic.Ingest.Apm/Model/Transaction.cs @@ -6,6 +6,7 @@ namespace Elastic.Ingest.Apm.Model { + /// Marker interface for V2 intake objects public interface IIntakeObject { } /// @@ -14,6 +15,7 @@ public interface IIntakeObject { } /// public class Transaction : IIntakeObject { + /// public Transaction(string type, string id, string traceId, SpanCount spanCount, double duration, long timestamp) { Type = type; @@ -76,6 +78,7 @@ public Transaction(string type, string id, string traceId, SpanCount spanCount, [JsonPropertyName("sampled")] public bool? Sampled { get; set; } + /// [JsonPropertyName("span_count")] public SpanCount SpanCount { get; set; } @@ -93,8 +96,10 @@ public Transaction(string type, string id, string traceId, SpanCount spanCount, public string Type { get; set; } } + /// public class Marks { } + /// public class SpanCount { /// @@ -110,8 +115,10 @@ public class SpanCount public long Started { get; set; } } + /// public class Span : IIntakeObject { + /// public Span(string type, string name, string id, string traceId, string parentId, long timestamp) { Type = type; @@ -205,6 +212,7 @@ public Span(string type, string name, string id, string traceId, string parentId public string Type { get; set; } } + /// public class Context { /// @@ -230,6 +238,7 @@ public class Context public ContextService? Service { get; set; } } + /// public class Db { /// @@ -264,6 +273,7 @@ public class Db public string? User { get; set; } } + /// public class Destination { /// @@ -283,6 +293,7 @@ public class Destination public DestinationService? Service { get; set; } } + /// public class DestinationService { /// @@ -304,6 +315,7 @@ public class DestinationService public string? Type { get; set; } } + /// public class Http { /// @@ -322,6 +334,7 @@ public class Http public string? Url { get; set; } } + /// public class ContextService { /// @@ -335,6 +348,7 @@ public class ContextService public string? Name { get; set; } } + /// public class Agent { /// diff --git a/src/Elastic.Ingest.Elasticsearch/BootstrapMethod.cs b/src/Elastic.Ingest.Elasticsearch/BootstrapMethod.cs index 66cc240..55d3042 100644 --- a/src/Elastic.Ingest.Elasticsearch/BootstrapMethod.cs +++ b/src/Elastic.Ingest.Elasticsearch/BootstrapMethod.cs @@ -4,6 +4,7 @@ namespace Elastic.Ingest.Elasticsearch; +/// Controls how to bootstrap target indices or data streams public enum BootstrapMethod { /// diff --git a/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannel.cs b/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannel.cs index ca6d51e..2b35290 100644 --- a/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannel.cs +++ b/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannel.cs @@ -4,24 +4,30 @@ using System.Collections.Generic; using System.Linq; +using Elastic.Channels; using Elastic.Ingest.Elasticsearch.Serialization; using Elastic.Ingest.Transport; namespace Elastic.Ingest.Elasticsearch.DataStreams { + /// A channel to push messages to Elasticsearch data streams public class DataStreamChannel : ElasticsearchChannelBase> { private readonly CreateOperation _fixedHeader; + /// public DataStreamChannel(DataStreamChannelOptions options) : base(options) { var target = Options.DataStream.ToString(); _fixedHeader = new CreateOperation { Index = target }; } + /// protected override BulkOperationHeader CreateBulkOperationHeader(TEvent @event) => _fixedHeader; + /// protected override string TemplateName => Options.DataStream.GetTemplateName(); + /// protected override string TemplateWildcard => Options.DataStream.GetNamespaceWildcard(); /// @@ -46,6 +52,9 @@ protected override (string, string) GetDefaultIndexTemplate(string name, string return (name, indexTemplateBody); } + /// + /// Yields additional component templates to include in the index template based on the data stream naming scheme + /// protected List GetInferredComponentTemplates() { var additionalComponents = new List { "data-streams-mappings" }; diff --git a/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannelOptions.cs b/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannelOptions.cs index d5bf032..4a731d3 100644 --- a/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannelOptions.cs +++ b/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamChannelOptions.cs @@ -5,11 +5,14 @@ namespace Elastic.Ingest.Elasticsearch.DataStreams { + /// Controls which data stream the channel should write to public class DataStreamChannelOptions : ElasticsearchChannelOptionsBase { + /// public DataStreamChannelOptions(HttpTransport transport) : base(transport) => DataStream = new DataStreamName(typeof(TEvent).Name.ToLowerInvariant()); + /// public DataStreamName DataStream { get; set; } } } diff --git a/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamName.cs b/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamName.cs index 6fd7b1d..60fe433 100644 --- a/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamName.cs +++ b/src/Elastic.Ingest.Elasticsearch/DataStreams/DataStreamName.cs @@ -6,6 +6,9 @@ namespace Elastic.Ingest.Elasticsearch.DataStreams { + /// + /// Strongly types a reference to a data stream using Elastic's data stream naming scheme + /// public record DataStreamName { /// Generic type describing the data @@ -20,6 +23,7 @@ public record DataStreamName private static readonly char[] BadCharacters = { '\\', '/', '*', '?', '"', '<', '>', '|', ' ', ',', '#' }; private static readonly string BadCharactersError = string.Join(", ", BadCharacters.Select(c => $"'{c}'").ToArray()); + /// public DataStreamName(string type, string dataSet = "generic", string @namespace = "default") { if (string.IsNullOrEmpty(type)) throw new ArgumentException($"{nameof(type)} can not be null or empty", nameof(type)); @@ -37,10 +41,13 @@ public DataStreamName(string type, string dataSet = "generic", string @namespace Namespace = @namespace.ToLowerInvariant(); } + /// Returns a good index template name for this data stream public string GetTemplateName() => $"{Type}-{DataSet}"; + /// Returns a good index template wildcard match for this data stream public string GetNamespaceWildcard() => $"{Type}-{DataSet}-*"; private string? _stringValue; + /// > public override string ToString() { if (_stringValue != null) return _stringValue; diff --git a/src/Elastic.Ingest.Elasticsearch/Diagnostics/ChannelListener.cs b/src/Elastic.Ingest.Elasticsearch/Diagnostics/ChannelListener.cs index 541e517..74539e6 100644 --- a/src/Elastic.Ingest.Elasticsearch/Diagnostics/ChannelListener.cs +++ b/src/Elastic.Ingest.Elasticsearch/Diagnostics/ChannelListener.cs @@ -10,6 +10,11 @@ namespace Elastic.Ingest.Elasticsearch.Diagnostics; +/// +/// A very rudimentary diagnostics object tracking various important metrics to provide insights into the +/// machinery of . +/// This will be soon be replaced by actual metrics +/// // ReSharper disable once UnusedType.Global public class ElasticsearchChannelListener : ChannelListener { @@ -17,9 +22,11 @@ public class ElasticsearchChannelListener : ChannelListener public override bool PublishSuccess => base.PublishSuccess && string.IsNullOrEmpty(_firstItemError); // ReSharper disable once UnusedMember.Global + /// public ElasticsearchChannelListener Register(ResponseItemsChannelOptionsBase options) { base.Register(options); @@ -38,6 +45,7 @@ public ElasticsearchChannelListener Register(ResponseItemsChannelOptions return this; } + /// protected override string AdditionalData => $@"Server Rejected Calls: {_serverRejections:N0} Server Rejected Items: {_rejectedItems:N0} First Error: {_firstItemError} diff --git a/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.Bootstrap.cs b/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.Bootstrap.cs index ee87c45..2775ea2 100644 --- a/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.Bootstrap.cs +++ b/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.Bootstrap.cs @@ -11,7 +11,9 @@ namespace Elastic.Ingest.Elasticsearch { public abstract partial class ElasticsearchChannelBase { + /// The index template name should register. protected abstract string TemplateName { get; } + /// The index template wildcard the should register for its index template. protected abstract string TemplateWildcard { get; } /// @@ -21,6 +23,12 @@ public abstract partial class ElasticsearchChannelBase protected abstract (string, string) GetDefaultIndexTemplate(string name, string match, string mappingsName, string settingsName); + /// + /// Bootstrap the target data stream. Will register the appropriate index and component templates + /// + /// Either None (no bootstrapping), Silent (quiet exit), Failure (throw exceptions) + /// Registers a component template that ensures the template is managed by this ilm policy + /// public virtual async Task BootstrapElasticsearchAsync(BootstrapMethod bootstrapMethod, string? ilmPolicy = null, CancellationToken ctx = default) { if (bootstrapMethod == BootstrapMethod.None) return true; @@ -44,6 +52,11 @@ public virtual async Task BootstrapElasticsearchAsync(BootstrapMethod boot return true; } + /// + /// Bootstrap the target data stream. Will register the appropriate index and component templates + /// + /// Either None (no bootstrapping), Silent (quiet exit), Failure (throw exceptions) + /// Registers a component template that ensures the template is managed by this ilm policy public virtual bool BootstrapElasticsearch(BootstrapMethod bootstrapMethod, string? ilmPolicy = null) { if (bootstrapMethod == BootstrapMethod.None) return true; @@ -67,6 +80,7 @@ public virtual bool BootstrapElasticsearch(BootstrapMethod bootstrapMethod, stri return true; } + /// protected bool IndexTemplateExists(string name) { var templateExists = Options.Transport.Request(HttpMethod.HEAD, $"_index_template/{name}"); @@ -74,6 +88,7 @@ protected bool IndexTemplateExists(string name) return statusCode is 200; } + /// protected async Task IndexTemplateExistsAsync(string name, CancellationToken ctx = default) { var templateExists = await Options.Transport.RequestAsync @@ -83,6 +98,7 @@ protected async Task IndexTemplateExistsAsync(string name, CancellationTok return statusCode is 200; } + /// protected bool PutIndexTemplate(BootstrapMethod bootstrapMethod, string name, string body) { var putIndexTemplateResponse = Options.Transport.Request @@ -95,6 +111,7 @@ protected bool PutIndexTemplate(BootstrapMethod bootstrapMethod, string name, st $"Failure to create index templates for {TemplateWildcard}: {putIndexTemplateResponse}"); } + /// protected async Task PutIndexTemplateAsync(BootstrapMethod bootstrapMethod, string name, string body, CancellationToken ctx = default) { var putIndexTemplateResponse = await Options.Transport.RequestAsync @@ -108,6 +125,7 @@ protected async Task PutIndexTemplateAsync(BootstrapMethod bootstrapMethod $"Failure to create index templates for {TemplateWildcard}: {putIndexTemplateResponse}"); } + /// protected bool PutComponentTemplate(BootstrapMethod bootstrapMethod, string name, string body) { var putComponentTemplate = Options.Transport.Request @@ -120,6 +138,7 @@ protected bool PutComponentTemplate(BootstrapMethod bootstrapMethod, string name $"Failure to create component template `${name}` for {TemplateWildcard}: {putComponentTemplate}"); } + /// protected async Task PutComponentTemplateAsync(BootstrapMethod bootstrapMethod, string name, string body, CancellationToken ctx = default) { var putComponentTemplate = await Options.Transport.RequestAsync diff --git a/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.cs b/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.cs index 0833f80..1a90207 100644 --- a/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.cs +++ b/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelBase.cs @@ -8,18 +8,27 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Elastic.Channels; +using Elastic.Ingest.Elasticsearch.DataStreams; +using Elastic.Ingest.Elasticsearch.Indices; using Elastic.Ingest.Elasticsearch.Serialization; using Elastic.Ingest.Transport; using Elastic.Transport; namespace Elastic.Ingest.Elasticsearch { + /// + /// An abstract base class for both and + /// Coordinates most of the sending to- and bootstrapping of Elasticsearch + /// public abstract partial class ElasticsearchChannelBase : TransportChannelBase where TChannelOptions : TransportChannelOptionsBase { + /// protected ElasticsearchChannelBase(TChannelOptions options) : base(options) { } + /// protected override bool Retry(BulkResponse response) { var details = response.ApiCallDetails; @@ -28,17 +37,22 @@ protected override bool Retry(BulkResponse response) return details.HasSuccessfulStatusCode; } + /// protected override bool RetryAllItems(BulkResponse response) => response.ApiCallDetails.HttpStatusCode == 429; + /// protected override List<(TEvent, BulkResponseItem)> Zip(BulkResponse response, IReadOnlyCollection page) => page.Zip(response.Items, (doc, item) => (doc, item)).ToList(); + /// protected override bool RetryEvent((TEvent, BulkResponseItem) @event) => ElasticsearchChannelStatics.RetryStatusCodes.Contains(@event.Item2.Status); + /// protected override bool RejectEvent((TEvent, BulkResponseItem) @event) => @event.Item2.Status < 200 || @event.Item2.Status > 300; + /// protected override Task Export(HttpTransport transport, IReadOnlyCollection page, CancellationToken ctx = default) => transport.RequestAsync(HttpMethod.POST, "/_bulk", PostData.StreamHandler(page, @@ -49,6 +63,9 @@ protected override Task Export(HttpTransport transport, IReadOnlyC async (b, stream, ctx) => { await WriteBufferToStreamAsync(b, stream, ctx).ConfigureAwait(false); }) , ElasticsearchChannelStatics.RequestParams, ctx); + /// + /// Asks implementations to create a based on the being exported. + /// protected abstract BulkOperationHeader CreateBulkOperationHeader(TEvent @event); private async Task WriteBufferToStreamAsync(IReadOnlyCollection b, Stream stream, CancellationToken ctx) @@ -78,10 +95,13 @@ await JsonSerializer.SerializeAsync(stream, @event, typeof(TEvent), Elasticsearc } } + /// protected class HeadIndexTemplateResponse : TransportResponse { } + /// protected class PutIndexTemplateResponse : TransportResponse { } + /// protected class PutComponentTemplateResponse : TransportResponse { } diff --git a/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelOptionsBase.cs b/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelOptionsBase.cs index 200204c..be5240d 100644 --- a/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelOptionsBase.cs +++ b/src/Elastic.Ingest.Elasticsearch/ElasticsearchChannelOptionsBase.cs @@ -7,10 +7,12 @@ namespace Elastic.Ingest.Elasticsearch { + /// + /// Base options implementation for implementations + /// public abstract class ElasticsearchChannelOptionsBase : TransportChannelOptionsBase { - protected ElasticsearchChannelOptionsBase(HttpTransport transport) : base(transport) - { - } + /// + protected ElasticsearchChannelOptionsBase(HttpTransport transport) : base(transport) { } } } diff --git a/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannel.cs b/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannel.cs index 2c9adfe..d8f208b 100644 --- a/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannel.cs +++ b/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannel.cs @@ -2,19 +2,25 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information using System; +using Elastic.Ingest.Elasticsearch.DataStreams; using Elastic.Ingest.Elasticsearch.Serialization; using Elastic.Ingest.Transport; namespace Elastic.Ingest.Elasticsearch.Indices { + /// A channel to push messages to an Elasticsearch index + /// If unsure prefer to use + /// public class IndexChannel : ElasticsearchChannelBase> { + /// public IndexChannel(IndexChannelOptions options) : base(options) { TemplateName = string.Format(Options.IndexFormat, "template"); TemplateWildcard = string.Format(Options.IndexFormat, "*"); } + /// protected override BulkOperationHeader CreateBulkOperationHeader(TEvent @event) { var indexTime = Options.TimestampLookup?.Invoke(@event) ?? DateTimeOffset.Now; @@ -28,7 +34,9 @@ protected override BulkOperationHeader CreateBulkOperationHeader(TEvent @event) : new CreateOperation { Index = index }; } + /// protected override string TemplateName { get; } + /// protected override string TemplateWildcard { get; } /// diff --git a/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannelOptions.cs b/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannelOptions.cs index 7cca8ff..84a701e 100644 --- a/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannelOptions.cs +++ b/src/Elastic.Ingest.Elasticsearch/Indices/IndexChannelOptions.cs @@ -6,8 +6,13 @@ namespace Elastic.Ingest.Elasticsearch.Indices { + /// + /// Provides options to to control how and where data gets written to Elasticsearch + /// + /// public class IndexChannelOptions : ElasticsearchChannelOptionsBase { + /// public IndexChannelOptions(HttpTransport transport) : base(transport) { } /// diff --git a/src/Elastic.Ingest.Elasticsearch/Serialization/BulkOperationHeader.cs b/src/Elastic.Ingest.Elasticsearch/Serialization/BulkOperationHeader.cs index 9f6f1db..bae4fbb 100644 --- a/src/Elastic.Ingest.Elasticsearch/Serialization/BulkOperationHeader.cs +++ b/src/Elastic.Ingest.Elasticsearch/Serialization/BulkOperationHeader.cs @@ -8,33 +8,47 @@ namespace Elastic.Ingest.Elasticsearch.Serialization { + /// Represents the _bulk operation meta header public abstract class BulkOperationHeader { + /// The index or data stream to write to [JsonPropertyName("_index")] public string? Index { get; init; } + /// The id of the object being written [JsonPropertyName("_id")] public string? Id { get; init; } + /// Require to point to an alias [JsonPropertyName("require_alias")] public bool? RequireAlias { get; init; } } + + /// Represents the _bulk create operation meta header [JsonConverter(typeof(BulkOperationHeaderConverter))] public class CreateOperation : BulkOperationHeader { + /// [JsonPropertyName("dynamic_templates")] public Dictionary? DynamicTemplates { get; init; } } + + /// Represents the _bulk index operation meta header [JsonConverter(typeof(BulkOperationHeaderConverter))] public class IndexOperation : BulkOperationHeader { + /// [JsonPropertyName("dynamic_templates")] public Dictionary? DynamicTemplates { get; init; } } + + /// Represents the _bulk delete operation meta header [JsonConverter(typeof(BulkOperationHeaderConverter))] public class DeleteOperation : BulkOperationHeader { } + + /// Represents the _bulk update operation meta header [JsonConverter(typeof(BulkOperationHeaderConverter))] public class UpdateOperation : BulkOperationHeader { diff --git a/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponse.cs b/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponse.cs index 24dfe1a..8418a9a 100644 --- a/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponse.cs +++ b/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponse.cs @@ -12,23 +12,34 @@ namespace Elastic.Ingest.Elasticsearch.Serialization { + /// Represents the _bulk response from Elasticsearch public class BulkResponse : TransportResponse { + /// + /// Individual bulk response items information + /// [JsonPropertyName("items")] [JsonConverter(typeof(ResponseItemsConverter))] public IReadOnlyCollection Items { get; set; } = null!; + /// Overall bulk error from Elasticsearch if any [JsonPropertyName("error")] public ErrorCause? Error { get; set; } + /// + /// Tries and get the error from Elasticsearch as string + /// + /// True if Elasticsearch contained an overall bulk error public bool TryGetServerErrorReason(out string? reason) { reason = Error?.Reason; return !string.IsNullOrWhiteSpace(reason); } + /// public override string ToString() => ApiCallDetails.DebugInformation; } + internal class ResponseItemsConverter : JsonConverter> { public static readonly IReadOnlyCollection EmptyBulkItems = diff --git a/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponseItem.cs b/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponseItem.cs index a6f927a..1490af3 100644 --- a/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponseItem.cs +++ b/src/Elastic.Ingest.Elasticsearch/Serialization/BulkResponseItem.cs @@ -8,11 +8,15 @@ namespace Elastic.Ingest.Elasticsearch.Serialization { + /// Represents a bulk response item [JsonConverter(typeof(ItemConverter))] public class BulkResponseItem { + /// The action that was used for the event (create/index) public string Action { get; internal set; } = null!; + /// Elasticsearch error if any public ErrorCause? Error { get; internal set; } + /// Status code from Elasticsearch writing the event public int Status { get; internal set; } } diff --git a/src/Elastic.Ingest.OpenTelemetry/CustomActivityExporter.cs b/src/Elastic.Ingest.OpenTelemetry/CustomActivityExporter.cs index 1b004ec..26d6895 100644 --- a/src/Elastic.Ingest.OpenTelemetry/CustomActivityExporter.cs +++ b/src/Elastic.Ingest.OpenTelemetry/CustomActivityExporter.cs @@ -6,8 +6,10 @@ namespace Elastic.Ingest.OpenTelemetry { + /// public class CustomActivityProcessor : BatchActivityExportProcessor { + /// public CustomActivityProcessor( BaseExporter exporter, int maxQueueSize = 2048, @@ -21,6 +23,7 @@ public CustomActivityProcessor( Activity.ForceDefaultIdFormat = true; } + /// public void Add(Activity a) => OnExport(a); } diff --git a/src/Elastic.Ingest.OpenTelemetry/CustomOtlpTraceExporter.cs b/src/Elastic.Ingest.OpenTelemetry/CustomOtlpTraceExporter.cs index 850d7de..6551947 100644 --- a/src/Elastic.Ingest.OpenTelemetry/CustomOtlpTraceExporter.cs +++ b/src/Elastic.Ingest.OpenTelemetry/CustomOtlpTraceExporter.cs @@ -15,8 +15,10 @@ namespace Elastic.Ingest.OpenTelemetry { + /// public class CustomOtlpTraceExporter : OtlpTraceExporter { + /// public CustomOtlpTraceExporter(OtlpExporterOptions options, TraceChannelOptions channelOptions) : base(options) { var type = GetType(); @@ -35,20 +37,28 @@ public CustomOtlpTraceExporter(OtlpExporterOptions options, TraceChannelOptions } + /// public class TraceChannelOptions : ChannelOptionsBase { + /// public string? ServiceName { get; set; } + /// public Uri? Endpoint { get; set; } + /// public string? SecretToken { get; set; } } + /// public class TraceExportResult { + /// public ExportResult Result { get; internal set; } } + /// public class TraceChannel : BufferedChannelBase { + /// public TraceChannel(TraceChannelOptions options) : base(options) { var o = new OtlpExporterOptions(); o.Endpoint = options.Endpoint; @@ -80,10 +90,13 @@ public TraceChannel(TraceChannelOptions options) : base(options) { private Func, Batch> BatchCreator { get; } + /// public CustomOtlpTraceExporter TraceExporter { get; } + /// public CustomActivityProcessor Processor { get; } + /// protected override Task Export(IReadOnlyCollection page, CancellationToken ctx = default) { var batch = BatchCreator(page); @@ -91,6 +104,7 @@ protected override Task Export(IReadOnlyCollection return Task.FromResult(new TraceExportResult { Result = result }); } + /// public override void Dispose() { base.Dispose(); diff --git a/src/Elastic.Ingest.Transport/LibraryVersion.cs b/src/Elastic.Ingest.Transport/LibraryVersion.cs index 96677b5..314b2ca 100644 --- a/src/Elastic.Ingest.Transport/LibraryVersion.cs +++ b/src/Elastic.Ingest.Transport/LibraryVersion.cs @@ -11,10 +11,14 @@ namespace Elastic.Ingest.Transport; //TODO make ReflectionVersionInfo in Elastic.Transport not sealed +/// +/// Returns assembly version information based on type. +/// public sealed class LibraryVersion : VersionInfo { private static readonly Regex VersionRegex = new(@"^\d+\.\d+\.\d\-?"); + /// public static readonly LibraryVersion Current = Create(); private LibraryVersion() { } diff --git a/src/Elastic.Ingest.Transport/TransportChannelBase.cs b/src/Elastic.Ingest.Transport/TransportChannelBase.cs index 1afdf54..970f23e 100644 --- a/src/Elastic.Ingest.Transport/TransportChannelBase.cs +++ b/src/Elastic.Ingest.Transport/TransportChannelBase.cs @@ -10,20 +10,27 @@ namespace Elastic.Ingest.Transport { + /// + /// A implementation that provides a common base for channels + /// looking to data + /// over + /// public abstract class TransportChannelBase : ResponseItemsBufferedChannelBase where TChannelOptions : TransportChannelOptionsBase where TResponse : TransportResponse, new() { + /// protected TransportChannelBase(TChannelOptions options) : base(options) { } /// Implement sending the current of the buffer to the output. /// /// Active page of the buffer that needs to be send to the output - /// + /// protected abstract Task Export(HttpTransport transport, IReadOnlyCollection page, CancellationToken ctx = default); + /// > protected override Task Export(IReadOnlyCollection buffer, CancellationToken ctx = default) => Export(Options.Transport, buffer, ctx); } } diff --git a/src/Elastic.Ingest.Transport/TransportChannelOptionsBase.cs b/src/Elastic.Ingest.Transport/TransportChannelOptionsBase.cs index bdeacef..b760808 100644 --- a/src/Elastic.Ingest.Transport/TransportChannelOptionsBase.cs +++ b/src/Elastic.Ingest.Transport/TransportChannelOptionsBase.cs @@ -7,11 +7,18 @@ namespace Elastic.Ingest.Transport { + /// + /// Provides channel options to implementation. + /// public abstract class TransportChannelOptionsBase : ResponseItemsChannelOptionsBase { + /// protected TransportChannelOptionsBase(HttpTransport transport) => Transport = transport; + /// + /// The implementation to be used by the channel + /// public HttpTransport Transport { get; } } }