Add an InMemoryExporter<T> (#1335)

* skeleton of the in-memory exporter

* add log helper

* configuration

* simple

* update the test cases

* mdl

* simplify readme

* better names

* minor tweak on the flow

* contravariance

* Revert "contravariance"

This reverts commit 94a474f46c.
This commit is contained in:
Reiley Yang 2020-10-09 14:41:16 -07:00 committed by GitHub
parent e7b61e3938
commit 4ce2beccdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 232 additions and 89 deletions

View File

@ -0,0 +1,5 @@
# Changelog
## Unreleased
* Initial release

View File

@ -0,0 +1,48 @@
// <copyright file="InMemoryExporter.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>
using System;
using System.Collections.Generic;
using OpenTelemetry;
namespace OpenTelemetry.Exporter
{
public class InMemoryExporter<T> : BaseExporter<T>
where T : class
{
private readonly ICollection<object> exportedItems;
public InMemoryExporter(InMemoryExporterOptions options = null)
{
this.exportedItems = options?.ExportedItems;
}
public override ExportResult Export(in Batch<T> batch)
{
if (this.exportedItems == null)
{
return ExportResult.Failure;
}
foreach (var data in batch)
{
this.exportedItems.Add(data);
}
return ExportResult.Success;
}
}
}

View File

@ -0,0 +1,63 @@
// <copyright file="InMemoryExporterHelperExtensions.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>
using System;
using System.Diagnostics;
using OpenTelemetry.Exporter;
#if NETSTANDARD2_0
using OpenTelemetry.Logs;
#endif
using OpenTelemetry.Trace;
namespace OpenTelemetry
{
public static class InMemoryExporterHelperExtensions
{
/// <summary>
/// Adds InMemory exporter to the TracerProvider.
/// </summary>
/// <param name="builder"><see cref="TracerProviderBuilder"/> builder to use.</param>
/// <param name="configure">Exporter configuration options.</param>
/// <returns>The instance of <see cref="TracerProviderBuilder"/> to chain the calls.</returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The objects should not be disposed.")]
public static TracerProviderBuilder AddInMemoryExporter(this TracerProviderBuilder builder, Action<InMemoryExporterOptions> configure = null)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
var options = new InMemoryExporterOptions();
configure?.Invoke(options);
return builder.AddProcessor(new SimpleExportProcessor<Activity>(new InMemoryExporter<Activity>(options)));
}
#if NETSTANDARD2_0
public static OpenTelemetryLoggerOptions AddInMemoryExporter(this OpenTelemetryLoggerOptions loggerOptions, Action<InMemoryExporterOptions> configure = null)
{
if (loggerOptions == null)
{
throw new ArgumentNullException(nameof(loggerOptions));
}
var options = new InMemoryExporterOptions();
configure?.Invoke(options);
return loggerOptions.AddProcessor(new SimpleExportProcessor<LogRecord>(new InMemoryExporter<LogRecord>(options)));
}
#endif
}
}

View File

@ -0,0 +1,25 @@
// <copyright file="InMemoryExporterOptions.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>
using System.Collections.Generic;
namespace OpenTelemetry.Exporter
{
public class InMemoryExporterOptions
{
public ICollection<object> ExportedItems { get; set; }
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;net46;netstandard2.0</TargetFrameworks>
<Description>In-memory exporter for OpenTelemetry .NET</Description>
<PackageTags>$(PackageTags)</PackageTags>
</PropertyGroup>
<PropertyGroup>
<NoWarn>$(NoWarn),1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,26 @@
# In-memory Exporter for OpenTelemetry .NET
[![NuGet](https://img.shields.io/nuget/v/OpenTelemetry.Exporter.InMemory.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.InMemory)
[![NuGet](https://img.shields.io/nuget/dt/OpenTelemetry.Exporter.InMemory.svg)](https://www.nuget.org/packages/OpenTelemetry.Exporter.InMemory)
The in-memory exporter stores data in a user provided memory buffer.
## Installation
```shell
dotnet add package OpenTelemetry.Exporter.InMemory
```
## Configuration
```csharp
var list = new List<object>();
var activityExporter = new InMemoryExporter<Activity>(
new InMemoryExporterOptions { ExportedItems = list });
var logExporter = new InMemoryExporter<LogRecord>(
new InMemoryExporterOptions { ExportedItems = list });
```
## References
* [OpenTelemetry Project](https://opentelemetry.io/)

View File

@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry\OpenTelemetry.csproj" />
<ProjectReference Include="$(RepoRoot)\src\OpenTelemetry.Exporter.InMemory\OpenTelemetry.Exporter.InMemory.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -1,54 +0,0 @@
// <copyright file="TestActivityExporter.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>
using System.Collections.Concurrent;
using System.Diagnostics;
using OpenTelemetry;
using OpenTelemetry.Trace;
namespace OpenTelemetry.Tests.Shared
{
internal class TestActivityExporter : BaseExporter<Activity>
{
internal readonly BlockingCollection<Activity> Exported = new BlockingCollection<Activity>();
private bool disposed;
public override ExportResult Export(in Batch<Activity> batch)
{
foreach (var activity in batch)
{
this.Exported.Add(activity);
}
return ExportResult.Success;
}
protected override void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
this.Exported.Dispose();
}
this.disposed = true;
}
base.Dispose(disposing);
}
}
}

View File

@ -15,9 +15,10 @@
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using OpenTelemetry.Tests.Shared;
using OpenTelemetry.Exporter;
using Xunit;
namespace OpenTelemetry.Trace.Tests
@ -33,17 +34,18 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void CheckConstructorWithInvalidValues()
{
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new TestActivityExporter(), maxQueueSize: 0));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new TestActivityExporter(), maxExportBatchSize: 0));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new TestActivityExporter(), maxQueueSize: 1, maxExportBatchSize: 2049));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new TestActivityExporter(), scheduledDelayMilliseconds: 0));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new TestActivityExporter(), exporterTimeoutMilliseconds: -1));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new InMemoryExporter<Activity>(), maxQueueSize: 0));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new InMemoryExporter<Activity>(), maxExportBatchSize: 0));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new InMemoryExporter<Activity>(), maxQueueSize: 1, maxExportBatchSize: 2049));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new InMemoryExporter<Activity>(), scheduledDelayMilliseconds: 0));
Assert.Throws<ArgumentOutOfRangeException>(() => new BatchExportProcessor<Activity>(new InMemoryExporter<Activity>(), exporterTimeoutMilliseconds: -1));
}
[Fact]
public void CheckIfBatchIsExportingOnQueueLimit()
{
using var exporter = new TestActivityExporter();
var exportedItems = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exportedItems });
using var processor = new BatchExportProcessor<Activity>(
exporter,
maxQueueSize: 1,
@ -52,12 +54,12 @@ namespace OpenTelemetry.Trace.Tests
processor.OnEnd(new Activity("start"));
for (int i = 0; i < 10 && exporter.Exported.Count == 0; i++)
for (int i = 0; i < 10 && exportedItems.Count == 0; i++)
{
Thread.Sleep(500);
}
Assert.Single(exporter.Exported);
Assert.Single(exportedItems);
Assert.Equal(1, processor.ProcessedCount);
Assert.Equal(1, processor.ReceivedCount);
@ -67,7 +69,7 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void CheckForceFlushWithInvalidTimeout()
{
using var exporter = new TestActivityExporter();
using var exporter = new InMemoryExporter<Activity>();
using var processor = new BatchExportProcessor<Activity>(exporter, maxQueueSize: 2, maxExportBatchSize: 1);
Assert.Throws<ArgumentOutOfRangeException>(() => processor.ForceFlush(-2));
}
@ -78,7 +80,8 @@ namespace OpenTelemetry.Trace.Tests
[InlineData(1)]
public void CheckForceFlushExport(int timeout)
{
using var exporter = new TestActivityExporter();
var exportedItems = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exportedItems });
using var processor = new BatchExportProcessor<Activity>(
exporter,
maxQueueSize: 3,
@ -92,7 +95,7 @@ namespace OpenTelemetry.Trace.Tests
// waiting to see if time is triggering the exporter
Thread.Sleep(1_000);
Assert.Empty(exporter.Exported);
Assert.Empty(exportedItems);
// forcing flush
processor.ForceFlush(timeout);
@ -103,7 +106,7 @@ namespace OpenTelemetry.Trace.Tests
Thread.Sleep(1_000);
}
Assert.Equal(2, exporter.Exported.Count);
Assert.Equal(2, exportedItems.Count);
Assert.Equal(2, processor.ProcessedCount);
Assert.Equal(2, processor.ReceivedCount);
@ -116,7 +119,8 @@ namespace OpenTelemetry.Trace.Tests
[InlineData(1)]
public void CheckShutdownExport(int timeout)
{
using var exporter = new TestActivityExporter();
var exportedItems = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exportedItems });
using var processor = new BatchExportProcessor<Activity>(
exporter,
maxQueueSize: 3,
@ -132,7 +136,7 @@ namespace OpenTelemetry.Trace.Tests
Thread.Sleep(1_000);
}
Assert.Single(exporter.Exported);
Assert.Single(exportedItems);
Assert.Equal(1, processor.ProcessedCount);
Assert.Equal(1, processor.ReceivedCount);

View File

@ -15,9 +15,10 @@
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using OpenTelemetry.Tests.Shared;
using OpenTelemetry.Exporter;
using Xunit;
namespace OpenTelemetry.Trace.Tests
@ -33,14 +34,15 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void CheckExportedOnEnd()
{
using var exporter = new TestActivityExporter();
var exportedItems = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exportedItems });
using var processor = new ReentrantExportProcessor<Activity>(exporter);
processor.OnEnd(new Activity("start1"));
Assert.Single(exporter.Exported);
Assert.Single(exportedItems);
processor.OnEnd(new Activity("start2"));
Assert.Equal(2, exporter.Exported.Count);
Assert.Equal(2, exportedItems.Count);
}
[Theory]
@ -49,18 +51,19 @@ namespace OpenTelemetry.Trace.Tests
[InlineData(1)]
public void CheckForceFlushExport(int timeout)
{
using var exporter = new TestActivityExporter();
var exportedItems = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exportedItems });
using var processor = new ReentrantExportProcessor<Activity>(exporter);
processor.OnEnd(new Activity("start1"));
processor.OnEnd(new Activity("start2"));
// checking before force flush
Assert.Equal(2, exporter.Exported.Count);
Assert.Equal(2, exportedItems.Count);
// forcing flush
processor.ForceFlush(timeout);
Assert.Equal(2, exporter.Exported.Count);
Assert.Equal(2, exportedItems.Count);
}
[Theory]
@ -69,16 +72,17 @@ namespace OpenTelemetry.Trace.Tests
[InlineData(1)]
public void CheckShutdownExport(int timeout)
{
using var exporter = new TestActivityExporter();
var exportedItems = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exportedItems });
using var processor = new ReentrantExportProcessor<Activity>(exporter);
processor.OnEnd(new Activity("start"));
// checking before shutdown
Assert.Single(exporter.Exported);
Assert.Single(exportedItems);
processor.Shutdown(timeout);
Assert.Single(exporter.Exported);
Assert.Single(exportedItems);
}
}
}

View File

@ -15,9 +15,10 @@
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using OpenTelemetry.Tests.Shared;
using OpenTelemetry.Exporter;
using Xunit;
namespace OpenTelemetry.Trace.Tests
@ -33,14 +34,15 @@ namespace OpenTelemetry.Trace.Tests
[Fact]
public void CheckExportedOnEnd()
{
using var exporter = new TestActivityExporter();
var exported = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exported });
using var processor = new SimpleExportProcessor<Activity>(exporter);
processor.OnEnd(new Activity("start1"));
Assert.Single(exporter.Exported);
Assert.Single(exported);
processor.OnEnd(new Activity("start2"));
Assert.Equal(2, exporter.Exported.Count);
Assert.Equal(2, exported.Count);
}
[Theory]
@ -49,18 +51,19 @@ namespace OpenTelemetry.Trace.Tests
[InlineData(1)]
public void CheckForceFlushExport(int timeout)
{
using var exporter = new TestActivityExporter();
var exported = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exported });
using var processor = new SimpleExportProcessor<Activity>(exporter);
processor.OnEnd(new Activity("start1"));
processor.OnEnd(new Activity("start2"));
// checking before force flush
Assert.Equal(2, exporter.Exported.Count);
Assert.Equal(2, exported.Count);
// forcing flush
processor.ForceFlush(timeout);
Assert.Equal(2, exporter.Exported.Count);
Assert.Equal(2, exported.Count);
}
[Theory]
@ -69,16 +72,17 @@ namespace OpenTelemetry.Trace.Tests
[InlineData(1)]
public void CheckShutdownExport(int timeout)
{
using var exporter = new TestActivityExporter();
var exported = new List<object>();
using var exporter = new InMemoryExporter<Activity>(new InMemoryExporterOptions { ExportedItems = exported });
using var processor = new SimpleExportProcessor<Activity>(exporter);
processor.OnEnd(new Activity("start"));
// checking before shutdown
Assert.Single(exporter.Exported);
Assert.Single(exported);
processor.Shutdown(timeout);
Assert.Single(exporter.Exported);
Assert.Single(exported);
}
}
}