diff --git a/.github/workflows/package-Preview.yml b/.github/workflows/package-Preview.yml
new file mode 100644
index 0000000000..6c8c0584f8
--- /dev/null
+++ b/.github/workflows/package-Preview.yml
@@ -0,0 +1,50 @@
+name: Pack OpenTelemetry.Contrib.Preview
+
+on:
+ workflow_dispatch:
+ inputs:
+ logLevel:
+ description: 'Log level'
+ required: true
+ default: 'warning'
+ push:
+ tags:
+ - 'Preview-*' # trigger when we create a tag with prefix "Preview-"
+
+jobs:
+ build-test-pack:
+ runs-on: ${{ matrix.os }}
+ env:
+ PROJECT: OpenTelemetry.Contrib.Preview
+
+ strategy:
+ matrix:
+ os: [windows-latest]
+
+ steps:
+ - uses: actions/checkout@v2
+ with:
+ fetch-depth: 0 # fetching all
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: dotnet build ${{env.PROJECT}}
+ run: dotnet build src/${{env.PROJECT}} --configuration Release --no-restore -p:Deterministic=true
+
+ - name: dotnet test ${{env.PROJECT}}
+ run: dotnet test test/${{env.PROJECT}}.Tests
+
+ - name: dotnet pack ${{env.PROJECT}}
+ run: dotnet pack src/${{env.PROJECT}} --configuration Release --no-build
+
+ - name: Publish Artifacts
+ uses: actions/upload-artifact@v2
+ with:
+ name: ${{env.PROJECT}}-packages
+ path: '**/${{env.PROJECT}}/bin/**/*.*nupkg'
+
+ - name: Publish Nuget
+ run: |
+ nuget setApiKey ${{ secrets.NUGET_TOKEN }} -Source https://api.nuget.org/v3/index.json
+ nuget push **/${{env.PROJECT}}/bin/**/*.nupkg -Source https://api.nuget.org/v3/index.json
\ No newline at end of file
diff --git a/CODEOWNERS b/CODEOWNERS
index dcd32ecc61..e1c0efabde 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -12,6 +12,7 @@ src/OpenTelemetry.Contrib.Instrumentation.MassTransit/ @ope
src/OpenTelemetry.Contrib.Instrumentation.GrpcCore/ @open-telemetry/dotnet-contrib-approvers @pcwiese
src/OpenTelemetry.Contrib.Instrumentation.Wcf/ @open-telemetry/dotnet-contrib-approvers @codeblanch
src/OpenTelemetry.Contrib.Instrumentation.MySqlData/ @open-telemetry/dotnet-contrib-approvers @moonheart
+src/OpenTelemetry.Contrib.Preview/ @open-telemetry/dotnet-contrib-approvers @codeblanch
test/OpenTelemetry.Contrib.Exporter.Stackdriver.Tests/ @open-telemetry/dotnet-contrib-approvers @SergeyKanzhelev
test/OpenTelemetry.Contrib.Extensions.AWSXRay.Tests/ @open-telemetry/dotnet-contrib-approvers @srprash @lupengamzn
@@ -19,5 +20,6 @@ test/OpenTelemetry.Contrib.Instrumentation.Elasticsearch.Tests/ @ope
test/OpenTelemetry.Contrib.Instrumentation.EntityFrameworkCoreTests/ @open-telemetry/dotnet-contrib-approvers
test/OpenTelemetry.Contrib.Instrumentation.MassTransit.Tests/ @open-telemetry/dotnet-contrib-approvers @alexvaluyskiy
test/OpenTelemetry.Contrib.Instrumentation.GrpcCore.Tests/ @open-telemetry/dotnet-contrib-approvers @pcwiese
-src/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/ @open-telemetry/dotnet-contrib-approvers @codeblanch
-src/OpenTelemetry.Contrib.Instrumentation.MySqlData.Tests/ @open-telemetry/dotnet-contrib-approvers @moonheart
+test/OpenTelemetry.Contrib.Instrumentation.Wcf.Tests/ @open-telemetry/dotnet-contrib-approvers @codeblanch
+test/OpenTelemetry.Contrib.Instrumentation.MySqlData.Tests/ @open-telemetry/dotnet-contrib-approvers @moonheart
+test/OpenTelemetry.Contrib.Preview.Tests/ @open-telemetry/dotnet-contrib-approvers @codeblanch
diff --git a/Directory.Build.props b/Directory.Build.props
deleted file mode 100644
index 47342704d0..0000000000
--- a/Directory.Build.props
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
- 8.0
-
-
-
-
-
-
-
diff --git a/Directory.Build.targets b/Directory.Build.targets
deleted file mode 100644
index 63c8e0171e..0000000000
--- a/Directory.Build.targets
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/build/Common.nonprod.props b/build/Common.nonprod.props
index 8a9fbf4742..b43fa19ccf 100644
--- a/build/Common.nonprod.props
+++ b/build/Common.nonprod.props
@@ -14,7 +14,6 @@
-
@@ -28,8 +27,7 @@
-->
[0.12.1,0.13)
[2.3.1,3.0)
- [5.0.0-preview.8.20407.11]
- [5.0.0-preview.8.20407.11]
+ [5.0,6.0)
[16.7.1,17.0)
[4.14.5,5.0)
[2.4.3,3.0)
diff --git a/build/Common.prod.props b/build/Common.prod.props
index 6dad44268e..41dc196e13 100644
--- a/build/Common.prod.props
+++ b/build/Common.prod.props
@@ -25,12 +25,12 @@
-
runtime; build; native; contentfiles; analyzers; buildtransitive
all
+
@@ -58,4 +58,12 @@
true
+
+
+
+
+
+
+
+
diff --git a/build/Common.props b/build/Common.props
index 0225580e34..1adbdf0ae9 100644
--- a/build/Common.props
+++ b/build/Common.props
@@ -1,6 +1,6 @@
- 8.0
+ 9.0
true
$([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory)).Parent.FullName)
$(MSBuildThisFileDirectory)debug.snk
@@ -22,27 +22,29 @@
Refer to https://docs.microsoft.com/en-us/nuget/concepts/package-versioning for semver syntax.
-->
[2.4.0,3.0)
- [3.3.0]
+ [5.0.3]
[16.7.1]
[2.1.0,5.0)
[1.0.0,2.0)
+ [3.3.2]
[1.0.0,2.0)
[1.1.118,2.0)
+
+
+ $(NoWarn);nullable
+
+
-
- All
-
+
-
+
diff --git a/build/Common.targets b/build/Common.targets
new file mode 100644
index 0000000000..6cf892c169
--- /dev/null
+++ b/build/Common.targets
@@ -0,0 +1,8 @@
+
+
+
+ AllEnabledByDefault
+ latest
+
+
+
diff --git a/examples/Directory.Build.targets b/examples/Directory.Build.targets
new file mode 100644
index 0000000000..da3eb65ef4
--- /dev/null
+++ b/examples/Directory.Build.targets
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/opentelemetry-dotnet-contrib.sln b/opentelemetry-dotnet-contrib.sln
index 86f4ba2de6..4856b3ead4 100644
--- a/opentelemetry-dotnet-contrib.sln
+++ b/opentelemetry-dotnet-contrib.sln
@@ -10,8 +10,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution items", "Solution items", "{07AA0F83-22F6-4B8C-921D-029D3384CB17}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
- Directory.Build.props = Directory.Build.props
- Directory.Build.targets = Directory.Build.targets
NuGet.config = NuGet.config
opentelemetry-dotnet-contrib.proj = opentelemetry-dotnet-contrib.proj
README.md = README.md
@@ -39,6 +37,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\package-Instrumentation.MassTransit.yml = .github\workflows\package-Instrumentation.MassTransit.yml
.github\workflows\package-Instrumentation.MySqlData.yml = .github\workflows\package-Instrumentation.MySqlData.yml
.github\workflows\package-Instrumentation.Wcf.yml = .github\workflows\package-Instrumentation.Wcf.yml
+ .github\workflows\package-Preview.yml = .github\workflows\package-Preview.yml
.github\workflows\pr_build.yml = .github\workflows\pr_build.yml
.github\workflows\sanitycheck.yml = .github\workflows\sanitycheck.yml
EndProjectSection
@@ -48,6 +47,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{824BD1DE
build\Common.nonprod.props = build\Common.nonprod.props
build\Common.prod.props = build\Common.prod.props
build\Common.props = build\Common.props
+ build\Common.targets = build\Common.targets
build\debug.snk = build\debug.snk
build\docfx.cmd = build\docfx.cmd
build\opentelemetry-icon-color.png = build\opentelemetry-icon-color.png
@@ -133,7 +133,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Instr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Instrumentation.AWSLambda.Tests", "test\OpenTelemetry.Contrib.Instrumentation.AWSLambda.Tests\OpenTelemetry.Contrib.Instrumentation.AWSLambda.Tests.csproj", "{08EDD935-8B4E-4CF5-8840-200DEBA8E110}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenTelemetry.Contrib.Tests.Shared", "test\OpenTelemetry.Contrib.Tests.Shared\OpenTelemetry.Contrib.Tests.Shared.csproj", "{C33F2D9D-89A6-459C-9A51-79BA5A9EF194}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Tests.Shared", "test\OpenTelemetry.Contrib.Tests.Shared\OpenTelemetry.Contrib.Tests.Shared.csproj", "{C33F2D9D-89A6-459C-9A51-79BA5A9EF194}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Preview", "src\OpenTelemetry.Contrib.Preview\OpenTelemetry.Contrib.Preview.csproj", "{B978939B-278C-43A3-AD12-32EA9BBD27D0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetry.Contrib.Preview.Tests", "test\OpenTelemetry.Contrib.Preview.Tests\OpenTelemetry.Contrib.Preview.Tests.csproj", "{D2C68560-C252-41A9-B742-2CEB7D760E0F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -249,6 +253,14 @@ Global
{C33F2D9D-89A6-459C-9A51-79BA5A9EF194}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C33F2D9D-89A6-459C-9A51-79BA5A9EF194}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C33F2D9D-89A6-459C-9A51-79BA5A9EF194}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B978939B-278C-43A3-AD12-32EA9BBD27D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B978939B-278C-43A3-AD12-32EA9BBD27D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B978939B-278C-43A3-AD12-32EA9BBD27D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B978939B-278C-43A3-AD12-32EA9BBD27D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D2C68560-C252-41A9-B742-2CEB7D760E0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D2C68560-C252-41A9-B742-2CEB7D760E0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D2C68560-C252-41A9-B742-2CEB7D760E0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D2C68560-C252-41A9-B742-2CEB7D760E0F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -286,6 +298,8 @@ Global
{87FE0ED4-56A5-4775-9F63-DD532F2200BD} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}
{08EDD935-8B4E-4CF5-8840-200DEBA8E110} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
{C33F2D9D-89A6-459C-9A51-79BA5A9EF194} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
+ {B978939B-278C-43A3-AD12-32EA9BBD27D0} = {22DF5DC0-1290-4E83-A9D8-6BB7DE3B3E63}
+ {D2C68560-C252-41A9-B742-2CEB7D760E0F} = {2097345F-4DD3-477D-BC54-A922F9B2B402}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B0816796-CDB3-47D7-8C3C-946434DE3B66}
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index 0cf8dd57d8..a582103dd0 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -1,6 +1,7 @@
-
+
+
all
diff --git a/src/OpenTelemetry.Contrib.Preview/.publicApi/net461/PublicAPI.Shipped.txt b/src/OpenTelemetry.Contrib.Preview/.publicApi/net461/PublicAPI.Shipped.txt
new file mode 100644
index 0000000000..7dc5c58110
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/.publicApi/net461/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/OpenTelemetry.Contrib.Preview/.publicApi/net461/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Contrib.Preview/.publicApi/net461/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000000..c654303a3a
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/.publicApi/net461/PublicAPI.Unshipped.txt
@@ -0,0 +1,9 @@
+#nullable enable
+Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions
+OpenTelemetry.Logs.LogToActivityEventConversionOptions
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.LogToActivityEventConversionOptions() -> void
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.ScopeConverter.get -> System.Action!
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.ScopeConverter.set -> void
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.StateConverter.get -> System.Action>!>!
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.StateConverter.set -> void
+static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AttachLogsToActivityEvent(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action? configure = null) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions!
diff --git a/src/OpenTelemetry.Contrib.Preview/.publicApi/net5.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Contrib.Preview/.publicApi/net5.0/PublicAPI.Shipped.txt
new file mode 100644
index 0000000000..7dc5c58110
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/.publicApi/net5.0/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/OpenTelemetry.Contrib.Preview/.publicApi/net5.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Contrib.Preview/.publicApi/net5.0/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000000..c654303a3a
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/.publicApi/net5.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,9 @@
+#nullable enable
+Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions
+OpenTelemetry.Logs.LogToActivityEventConversionOptions
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.LogToActivityEventConversionOptions() -> void
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.ScopeConverter.get -> System.Action!
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.ScopeConverter.set -> void
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.StateConverter.get -> System.Action>!>!
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.StateConverter.set -> void
+static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AttachLogsToActivityEvent(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action? configure = null) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions!
diff --git a/src/OpenTelemetry.Contrib.Preview/.publicApi/netstandard2.0/PublicAPI.Shipped.txt b/src/OpenTelemetry.Contrib.Preview/.publicApi/netstandard2.0/PublicAPI.Shipped.txt
new file mode 100644
index 0000000000..7dc5c58110
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/.publicApi/netstandard2.0/PublicAPI.Shipped.txt
@@ -0,0 +1 @@
+#nullable enable
diff --git a/src/OpenTelemetry.Contrib.Preview/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/OpenTelemetry.Contrib.Preview/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
new file mode 100644
index 0000000000..c654303a3a
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/.publicApi/netstandard2.0/PublicAPI.Unshipped.txt
@@ -0,0 +1,9 @@
+#nullable enable
+Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions
+OpenTelemetry.Logs.LogToActivityEventConversionOptions
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.LogToActivityEventConversionOptions() -> void
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.ScopeConverter.get -> System.Action!
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.ScopeConverter.set -> void
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.StateConverter.get -> System.Action>!>!
+OpenTelemetry.Logs.LogToActivityEventConversionOptions.StateConverter.set -> void
+static Microsoft.Extensions.Logging.OpenTelemetryLoggingExtensions.AttachLogsToActivityEvent(this OpenTelemetry.Logs.OpenTelemetryLoggerOptions! loggerOptions, System.Action? configure = null) -> OpenTelemetry.Logs.OpenTelemetryLoggerOptions!
diff --git a/src/OpenTelemetry.Contrib.Preview/AssemblyInfo.cs b/src/OpenTelemetry.Contrib.Preview/AssemblyInfo.cs
new file mode 100644
index 0000000000..fb9851b200
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+//
+// 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.
+//
+
+using System;
+
+[assembly: CLSCompliant(false)]
diff --git a/src/OpenTelemetry.Contrib.Preview/CHANGELOG.md b/src/OpenTelemetry.Contrib.Preview/CHANGELOG.md
new file mode 100644
index 0000000000..1512c42162
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Changelog
+
+## Unreleased
diff --git a/src/OpenTelemetry.Contrib.Preview/Internal/ActivityEventAttachingLogProcessor.cs b/src/OpenTelemetry.Contrib.Preview/Internal/ActivityEventAttachingLogProcessor.cs
new file mode 100644
index 0000000000..c9e1b936db
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/Internal/ActivityEventAttachingLogProcessor.cs
@@ -0,0 +1,110 @@
+//
+// 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.
+//
+
+#if NET461_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Trace;
+
+namespace OpenTelemetry.Logs
+{
+ internal sealed class ActivityEventAttachingLogProcessor : BaseProcessor
+ {
+ private static readonly Action ProcessScope = (LogRecordScope scope, State state) =>
+ {
+ try
+ {
+ state.Processor.options.ScopeConverter?.Invoke(state.Tags, state.Depth++, scope);
+ }
+ catch (Exception ex)
+ {
+ OpenTelemetryExtensionsEventSource.Log.LogProcessorException($"Processing scope of type [{scope.GetType().FullName}]", ex);
+ }
+ };
+
+ private readonly LogToActivityEventConversionOptions options;
+
+ public ActivityEventAttachingLogProcessor(LogToActivityEventConversionOptions options)
+ {
+ this.options = options ?? throw new ArgumentNullException(nameof(options));
+ }
+
+ public override void OnEnd(LogRecord data)
+ {
+ Activity? activity = Activity.Current;
+
+ if (activity?.IsAllDataRequested == true)
+ {
+ var tags = new ActivityTagsCollection
+ {
+ { nameof(data.CategoryName), data.CategoryName },
+ { nameof(data.LogLevel), data.LogLevel },
+ };
+
+ if (data.EventId != 0)
+ {
+ tags[nameof(data.EventId)] = data.EventId;
+ }
+
+ var activityEvent = new ActivityEvent("log", data.Timestamp, tags);
+
+ data.ForEachScope(ProcessScope, new State(tags, this));
+
+ if (data.StateValues != null)
+ {
+ try
+ {
+ this.options.StateConverter?.Invoke(tags, data.StateValues);
+ }
+#pragma warning disable CA1031 // Do not catch general exception types
+ catch (Exception ex)
+#pragma warning restore CA1031 // Do not catch general exception types
+ {
+ OpenTelemetryExtensionsEventSource.Log.LogProcessorException($"Processing state of type [{data.State.GetType().FullName}]", ex);
+ }
+ }
+
+ if (!string.IsNullOrEmpty(data.FormattedMessage))
+ {
+ tags[nameof(data.FormattedMessage)] = data.FormattedMessage;
+ }
+
+ activity.AddEvent(activityEvent);
+
+ if (data.Exception != null)
+ {
+ activity.RecordException(data.Exception);
+ }
+ }
+ }
+
+ private class State
+ {
+ public State(ActivityTagsCollection tags, ActivityEventAttachingLogProcessor processor)
+ {
+ this.Tags = tags;
+ this.Processor = processor;
+ }
+
+ public ActivityTagsCollection Tags { get; }
+
+ public ActivityEventAttachingLogProcessor Processor { get; }
+
+ public int Depth { get; set; }
+ }
+ }
+}
+#endif
diff --git a/src/OpenTelemetry.Contrib.Preview/Internal/DefaultLogStateConverter.cs b/src/OpenTelemetry.Contrib.Preview/Internal/DefaultLogStateConverter.cs
new file mode 100644
index 0000000000..76d64d64f3
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/Internal/DefaultLogStateConverter.cs
@@ -0,0 +1,68 @@
+//
+// 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.
+//
+
+#if NET461_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace OpenTelemetry.Logs
+{
+ internal static class DefaultLogStateConverter
+ {
+ public static void ConvertState(ActivityTagsCollection tags, IReadOnlyList> state)
+ {
+ for (int i = 0; i < state.Count; i++)
+ {
+ KeyValuePair stateItem = state[i];
+
+ object value = stateItem.Value;
+ if (value != null)
+ {
+ if (string.IsNullOrEmpty(stateItem.Key))
+ {
+ tags["state"] = value;
+ }
+ else
+ {
+ tags[$"state.{stateItem.Key}"] = value;
+ }
+ }
+ }
+ }
+
+ public static void ConvertScope(ActivityTagsCollection tags, int depth, LogRecordScope scope)
+ {
+ string prefix = $"scope[{depth}]";
+
+ foreach (KeyValuePair scopeItem in scope)
+ {
+ object value = scopeItem.Value;
+ if (value != null)
+ {
+ if (string.IsNullOrEmpty(scopeItem.Key))
+ {
+ tags[prefix] = value;
+ }
+ else
+ {
+ tags[$"{prefix}.{scopeItem.Key}"] = value;
+ }
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/src/OpenTelemetry.Contrib.Preview/Internal/OpenTelemetryExtensionsEventSource.cs b/src/OpenTelemetry.Contrib.Preview/Internal/OpenTelemetryExtensionsEventSource.cs
new file mode 100644
index 0000000000..0815f0b2b0
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/Internal/OpenTelemetryExtensionsEventSource.cs
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+
+using System;
+using System.Diagnostics.Tracing;
+using OpenTelemetry.Internal;
+
+namespace OpenTelemetry
+{
+ ///
+ /// EventSource implementation for OpenTelemetry SDK extensions implementation.
+ ///
+ [EventSource(Name = "OpenTelemetry-Extensions")]
+ internal class OpenTelemetryExtensionsEventSource : EventSource
+ {
+ public static OpenTelemetryExtensionsEventSource Log = new OpenTelemetryExtensionsEventSource();
+
+ [NonEvent]
+ public void LogProcessorException(string @event, Exception ex)
+ {
+ if (this.IsEnabled(EventLevel.Error, (EventKeywords)(-1)))
+ {
+ this.LogProcessorException(@event, ex.ToInvariantString());
+ }
+ }
+
+ [Event(1, Message = "Unknown error in LogProcessor event '{0}': '{1}'.", Level = EventLevel.Error)]
+ public void LogProcessorException(string @event, string exception)
+ {
+ this.WriteEvent(1, @event, exception);
+ }
+ }
+}
diff --git a/src/OpenTelemetry.Contrib.Preview/Logs/LogToActivityEventConversionOptions.cs b/src/OpenTelemetry.Contrib.Preview/Logs/LogToActivityEventConversionOptions.cs
new file mode 100644
index 0000000000..d2342ce410
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/Logs/LogToActivityEventConversionOptions.cs
@@ -0,0 +1,40 @@
+//
+// 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.
+//
+
+#if NET461_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace OpenTelemetry.Logs
+{
+ ///
+ /// Stores options used to convert log messages into s.
+ ///
+ public class LogToActivityEventConversionOptions
+ {
+ ///
+ /// Gets or sets the callback action used to convert log state into tags.
+ ///
+ public Action>> StateConverter { get; set; } = DefaultLogStateConverter.ConvertState;
+
+ ///
+ /// Gets or sets the callback action used to convert log scopes into tags.
+ ///
+ public Action ScopeConverter { get; set; } = DefaultLogStateConverter.ConvertScope;
+ }
+}
+#endif
diff --git a/src/OpenTelemetry.Contrib.Preview/Logs/OpenTelemetryLoggingExtensions.cs b/src/OpenTelemetry.Contrib.Preview/Logs/OpenTelemetryLoggingExtensions.cs
new file mode 100644
index 0000000000..7d3049181e
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/Logs/OpenTelemetryLoggingExtensions.cs
@@ -0,0 +1,56 @@
+//
+// 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.
+//
+
+#if NET461_OR_GREATER || NETSTANDARD2_0 || NET5_0_OR_GREATER
+using System;
+using System.Diagnostics;
+using OpenTelemetry.Logs;
+
+namespace Microsoft.Extensions.Logging
+{
+ ///
+ /// Contains OpenTelemetry logging SDK extensions.
+ ///
+ public static class OpenTelemetryLoggingExtensions
+ {
+ ///
+ /// Adds a processor to the OpenTelemetry which will convert log messages
+ /// into s attached to the active when the message is written.
+ ///
+ /// options to use.
+ /// .
+ /// The instance of to chain the calls.
+ /// is null.
+ public static OpenTelemetryLoggerOptions AttachLogsToActivityEvent(
+ this OpenTelemetryLoggerOptions loggerOptions,
+ Action? configure = null)
+ {
+ if (loggerOptions == null)
+ {
+ throw new ArgumentNullException(nameof(loggerOptions));
+ }
+
+ var options = new LogToActivityEventConversionOptions();
+ configure?.Invoke(options);
+#pragma warning disable CA2000 // Dispose objects before losing scope
+ return loggerOptions.AddProcessor(new ActivityEventAttachingLogProcessor(options));
+#pragma warning restore CA2000 // Dispose objects before losing scope
+ }
+ }
+}
+#endif
diff --git a/src/OpenTelemetry.Contrib.Preview/OpenTelemetry.Contrib.Preview.csproj b/src/OpenTelemetry.Contrib.Preview/OpenTelemetry.Contrib.Preview.csproj
new file mode 100644
index 0000000000..7557b2d3f2
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/OpenTelemetry.Contrib.Preview.csproj
@@ -0,0 +1,21 @@
+
+
+
+ net461;netstandard2.0
+ $(TargetFrameworks);net5.0
+ OpenTelemetry .NET SDK preview features and extensions
+ Preview-
+ enable
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenTelemetry.Contrib.Preview/README.md b/src/OpenTelemetry.Contrib.Preview/README.md
new file mode 100644
index 0000000000..90857a6c7f
--- /dev/null
+++ b/src/OpenTelemetry.Contrib.Preview/README.md
@@ -0,0 +1,14 @@
+# OpenTelemetry .NET SDK preview features and extensions
+
+[![nuget](https://img.shields.io/nuget/v/OpenTelemetry.Contrib.Preview.svg)](https://www.nuget.org/packages/OpenTelemetry.Contrib.Preview/)
+
+Contains useful features and extensions to the OpenTelemetry .NET SDK that are
+not part of the official OpenTelemetry specification but might be added in the
+future.
+
+## Logging
+
+### AttachLogsToActivityEvent
+
+Adds a log processor which will convert log messages into events and attach them
+to the currently running Activity.
diff --git a/test/Directory.Build.targets b/test/Directory.Build.targets
index 18398cd3b1..da3eb65ef4 100644
--- a/test/Directory.Build.targets
+++ b/test/Directory.Build.targets
@@ -1,3 +1,4 @@
+
\ No newline at end of file
diff --git a/test/OpenTelemetry.Contrib.Preview.Tests/ActivityEventAttachingLogProcessorTests.cs b/test/OpenTelemetry.Contrib.Preview.Tests/ActivityEventAttachingLogProcessorTests.cs
new file mode 100644
index 0000000000..455590b28f
--- /dev/null
+++ b/test/OpenTelemetry.Contrib.Preview.Tests/ActivityEventAttachingLogProcessorTests.cs
@@ -0,0 +1,190 @@
+//
+// 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.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Microsoft.Extensions.Logging;
+using OpenTelemetry.Logs;
+using Xunit;
+
+namespace OpenTelemetry.Contrib.Preview.Tests
+{
+ public sealed class ActivityEventAttachingLogProcessorTests : IDisposable
+ {
+ private readonly ActivitySource activitySource = new ActivitySource("Test");
+ private readonly ActivityListener activityListener = new ActivityListener
+ {
+ ShouldListenTo = source => true,
+ };
+
+ private readonly ILogger logger;
+ private readonly ILoggerFactory loggerFactory;
+ private OpenTelemetryLoggerOptions options;
+ private bool sampled;
+
+ public ActivityEventAttachingLogProcessorTests()
+ {
+ this.activityListener.Sample = (ref ActivityCreationOptions options) =>
+ {
+ return this.sampled
+ ? ActivitySamplingResult.AllDataAndRecorded
+ : ActivitySamplingResult.PropagationData;
+ };
+
+ ActivitySource.AddActivityListener(this.activityListener);
+
+ this.loggerFactory = LoggerFactory.Create(builder =>
+ {
+ builder.AddOpenTelemetry(options =>
+ {
+ this.options = options;
+ options.AttachLogsToActivityEvent();
+ });
+ builder.AddFilter(typeof(ActivityEventAttachingLogProcessorTests).FullName, LogLevel.Trace);
+ });
+
+ this.logger = this.loggerFactory.CreateLogger();
+ }
+
+ public void Dispose()
+ {
+ this.activitySource.Dispose();
+ this.activityListener.Dispose();
+ this.loggerFactory.Dispose();
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ [InlineData(true, 18, true, true, true)]
+ [InlineData(true, 0, false, false, true, true)]
+ public void AttachLogsToActivityEventTest(
+ bool sampled,
+ int eventId = 0,
+ bool includeFormattedMessage = false,
+ bool parseStateValues = false,
+ bool includeScopes = false,
+ bool recordException = false)
+ {
+ this.sampled = sampled;
+ this.options.IncludeFormattedMessage = includeFormattedMessage;
+ this.options.ParseStateValues = parseStateValues;
+ this.options.IncludeScopes = includeScopes;
+
+ Activity activity = this.activitySource.StartActivity("Test");
+
+ using IDisposable scope = this.logger.BeginScope("{NodeId}", 99);
+
+ this.logger.LogInformation(eventId, "Hello OpenTelemetry {UserId}!", 8);
+
+ Activity innerActivity = null;
+ if (recordException)
+ {
+ innerActivity = this.activitySource.StartActivity("InnerTest");
+
+ using IDisposable innerScope = this.logger.BeginScope("{RequestId}", "1234");
+
+ this.logger.LogError(new InvalidOperationException("Goodbye OpenTelemetry."), "Exception event.");
+
+ innerActivity.Dispose();
+ }
+
+ activity.Dispose();
+
+ if (sampled)
+ {
+ ActivityEvent? logEvent = activity.Events.FirstOrDefault();
+
+ Assert.NotNull(logEvent);
+ Assert.Equal("log", logEvent.Value.Name);
+
+ Dictionary tags = logEvent.Value.Tags?.ToDictionary(i => i.Key, i => i.Value);
+ Assert.NotNull(tags);
+
+ Assert.Equal("OpenTelemetry.Contrib.Preview.Tests.ActivityEventAttachingLogProcessorTests", tags[nameof(LogRecord.CategoryName)]);
+ Assert.Equal(LogLevel.Information, tags[nameof(LogRecord.LogLevel)]);
+
+ if (eventId != 0)
+ {
+ Assert.Equal((EventId)eventId, tags[nameof(LogRecord.EventId)]);
+ }
+ else
+ {
+ Assert.DoesNotContain(tags, kvp => kvp.Key == nameof(LogRecord.EventId));
+ }
+
+ if (includeFormattedMessage)
+ {
+ Assert.Equal("Hello OpenTelemetry 8!", tags[nameof(LogRecord.FormattedMessage)]);
+ }
+ else
+ {
+ Assert.DoesNotContain(tags, kvp => kvp.Key == nameof(LogRecord.FormattedMessage));
+ }
+
+ if (parseStateValues)
+ {
+ Assert.Equal(8, tags["state.UserId"]);
+ }
+ else
+ {
+ Assert.DoesNotContain(tags, kvp => kvp.Key == "state.UserId");
+ }
+
+ if (includeScopes)
+ {
+ Assert.Equal(99, tags["scope[0].NodeId"]);
+ }
+ else
+ {
+ Assert.DoesNotContain(tags, kvp => kvp.Key == "scope[0].NodeId");
+ }
+
+ if (recordException)
+ {
+ ActivityEvent? exLogEvent = innerActivity.Events.FirstOrDefault();
+
+ Assert.NotNull(exLogEvent);
+ Assert.Equal("log", exLogEvent.Value.Name);
+
+ Dictionary exLogTags = exLogEvent.Value.Tags?.ToDictionary(i => i.Key, i => i.Value);
+ Assert.NotNull(exLogTags);
+
+ Assert.Equal(99, exLogTags["scope[0].NodeId"]);
+ Assert.Equal("1234", exLogTags["scope[1].RequestId"]);
+
+ ActivityEvent? exEvent = innerActivity.Events.Skip(1).FirstOrDefault();
+
+ Assert.NotNull(exEvent);
+ Assert.Equal("exception", exEvent.Value.Name);
+
+ Dictionary exTags = exEvent.Value.Tags?.ToDictionary(i => i.Key, i => i.Value);
+ Assert.NotNull(exTags);
+
+ Assert.Equal("System.InvalidOperationException", exTags["exception.type"]);
+ Assert.Equal("Goodbye OpenTelemetry.", exTags["exception.message"]);
+ Assert.Contains(exTags, kvp => kvp.Key == "exception.stacktrace");
+ }
+ }
+ else
+ {
+ Assert.Empty(activity.Events);
+ }
+ }
+ }
+}
diff --git a/test/OpenTelemetry.Contrib.Preview.Tests/OpenTelemetry.Contrib.Preview.Tests.csproj b/test/OpenTelemetry.Contrib.Preview.Tests/OpenTelemetry.Contrib.Preview.Tests.csproj
new file mode 100644
index 0000000000..71a93e8b02
--- /dev/null
+++ b/test/OpenTelemetry.Contrib.Preview.Tests/OpenTelemetry.Contrib.Preview.Tests.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Unit test project for OpenTelemetry .NET SDK preview features and extensions
+ net5.0
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers
+
+
+
+
+
+
+
+
+
+
+
+
+
+