Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OpenTelemetry support #514

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion Bot.Builder.Community.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.31911.260
MinimumVisualStudioVersion = 10.0.40219.1
Expand Down Expand Up @@ -183,6 +183,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapt
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bot.Builder.Community.Adapters.Facebook.Tests", "tests\Bot.Builder.Community.Adapters.Facebook.Tests\Bot.Builder.Community.Adapters.Facebook.Tests.csproj", "{8201DC48-763A-4534-9E51-466E15DF01D8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bot.Builder.Community.OpenTelemetry", "libraries\Bot.Builder.Community.OpenTelemetry\Bot.Builder.Community.OpenTelemetry.csproj", "{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bot.Builder.Community.OpenTelemetry.Tests", "tests\Bot.Builder.Community.OpenTelemetry.Tests\Bot.Builder.Community.OpenTelemetry.Tests.csproj", "{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug - NuGet Packages|Any CPU = Debug - NuGet Packages|Any CPU
Expand Down Expand Up @@ -805,6 +809,22 @@ Global
{8201DC48-763A-4534-9E51-466E15DF01D8}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{8201DC48-763A-4534-9E51-466E15DF01D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8201DC48-763A-4534-9E51-466E15DF01D8}.Release|Any CPU.Build.0 = Release|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE}.Release|Any CPU.Build.0 = Release|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Debug - NuGet Packages|Any CPU.ActiveCfg = Debug|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Debug - NuGet Packages|Any CPU.Build.0 = Debug|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Documentation|Any CPU.ActiveCfg = Debug|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Documentation|Any CPU.Build.0 = Debug|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -893,6 +913,8 @@ Global
{428AD1B4-DF58-4D21-9C19-AB4AB6001A90} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
{3348B9A5-E3CE-4AF8-B059-8B4D7971C25A} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
{8201DC48-763A-4534-9E51-466E15DF01D8} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
{502BFD1C-37E2-46E5-BBA4-5A4BD2C255EE} = {57D9879A-FC9F-468C-B86F-BE45A1472F88}
{E9F0C9BA-DD15-4791-A1C2-93DAA76A6058} = {840D4038-9AB8-4750-9FFE-365386CE47E2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9FE3B75E-BA2B-45BC-BBF0-DDA8BA10C4F0}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove('$(MSBuildThisFileDirectory)../', 'Bot.Builder.Community.sln'))\CommonTargets\library.shared.targets" />

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Description>This library integrates the Microsoft Bot Builder SDK with Open Telemetry</Description>
<Summary>This library provides integration between the Microsoft Bot Builder SDK and Open Telemetry.</Summary>
<Authors>Bot Builder Community</Authors>
<Company>Bot Builder Community</Company>
<PackageLicenseUrl>https://github.com/botbuildercommunity/botbuilder-community-dotnet/blob/master/LICENSE</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/botbuildercommunity/botbuilder-community-dotnet</PackageProjectUrl>
<RepositoryUrl>https://github.com/botbuildercommunity/botbuilder-community-dotnet</RepositoryUrl>
<PackageTags>bot framework;bot builder;azure bot service;dialogs;adaptivedialogs;rest</PackageTags>
<Version>1.0.0</Version>
<FileVersion>1.0.0</FileVersion>
<AssemblyVersion>1.0.0</AssemblyVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Bot.Builder" Version="$(Bot_Builder_Version)" />
<PackageReference Include="OpenTelemetry" Version="1.4.0-beta.2" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.Bot.Builder;
using OpenTelemetry.Trace;

namespace Bot.Builder.Community.OpenTelemetry
{
public class BotOpenTelemetryClient : IBotTelemetryClient, IBotPageViewTelemetryClient
{
private readonly Dictionary<string, Histogram<double>> meters = new Dictionary<string, Histogram<double>>();

public BotOpenTelemetryClient()
{
}

/// <summary>
/// Send information about availability of an application.
/// </summary>
/// <param name="name">Availability test name.</param>
/// <param name="timeStamp">The time when the availability was captured.</param>
/// <param name="duration">The time taken for the availability test to run.</param>
/// <param name="runLocation">Name of the location the availability test was run from.</param>
/// <param name="success">True if the availability test ran successfully.</param>
/// <param name="message">Error message on availability test run failure.</param>
/// <param name="properties">Named string values you can use to classify and search for this availability telemetry.</param>
/// <param name="metrics">Additional values associated with this availability telemetry.</param>
public void TrackAvailability(string name, DateTimeOffset timeStamp, TimeSpan duration, string runLocation, bool success, string message = null, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
{
using (var activity = BotOpenTelemetryHelper.ActivitySource.StartActivity("Availability", ActivityKind.Internal, null, null, null, timeStamp))
{
activity.DisplayName = name;

activity.SetEndTime(timeStamp.Add(duration).DateTime);

activity.AddTag("availability.runLocation", runLocation);
activity.AddTag("availability.success", success);
activity.AddTag("availability.message", message);

if (properties != null)
{
foreach (var property in properties)
{
activity.SetTag(property.Key, property.Value);
}
}

if (metrics != null)
{
foreach (var metric in metrics)
{
var meter = BotOpenTelemetryHelper.Meter.CreateHistogram<double>(metric.Key);
meter.Record(metric.Value);
}
}
}
}

/// <summary>
/// Send information about an external dependency (outgoing call) in the application.
/// </summary>
/// <param name="dependencyTypeName">Name of the command initiated with this dependency call. Low cardinality value.
/// Examples are SQL, Azure table, and HTTP.</param>
/// <param name="target">External dependency target.</param>
/// <param name="dependencyName">Name of the command initiated with this dependency call. Low cardinality value.
/// Examples are stored procedure name and URL path template.</param>
/// <param name="data">Command initiated by this dependency call. Examples are SQL statement and HTTP
/// URL's with all query parameters.</param>
/// <param name="startTime">The time when the dependency was called.</param>
/// <param name="duration">The time taken by the external dependency to handle the call.</param>
/// <param name="resultCode">Result code of dependency call execution.</param>
/// <param name="success">True if the dependency call was handled successfully.</param>
public void TrackDependency(string dependencyTypeName, string target, string dependencyName, string data, DateTimeOffset startTime, TimeSpan duration, string resultCode, bool success)
{
using (var activity = BotOpenTelemetryHelper.ActivitySource.StartActivity("Dependency", ActivityKind.Internal, null, null, null, startTime))
{
activity.DisplayName = dependencyTypeName;

activity.SetEndTime(startTime.Add(duration).DateTime);

activity.AddTag("dependency.target", target);
activity.AddTag("dependency.dependencyName", dependencyName);
activity.AddTag("dependency.resultCode", resultCode);
activity.AddTag("dependency.success", success);
}
}

/// <summary>
/// Logs custom events with extensible named fields.
/// </summary>
/// <param name="eventName">A name for the event.</param>
/// <param name="properties">Named string values you can use to search and classify events.</param>
/// <param name="metrics">Measurements associated with this event.</param>
public void TrackEvent(string eventName, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
{
using (var activity = BotOpenTelemetryHelper.ActivitySource.StartActivity("Event"))
{
activity.DisplayName = eventName;

if (properties != null)
{
foreach (var property in properties)
{
activity.SetTag(property.Key, property.Value);
}
}

if (metrics != null)
{
foreach (var metric in metrics)
{
if (!meters.TryGetValue(metric.Key, out Histogram<double> meter))
{
meter = BotOpenTelemetryHelper.Meter.CreateHistogram<double>(metric.Key);
meters.Add(metric.Key, meter);
}

meter.Record(metric.Value);
activity.SetTag($"metrics.{metric.Key}", metric.Value);
}
}

activity.AddEvent(new ActivityEvent(eventName, DateTimeOffset.Now));
}
}

/// <summary>
/// Logs a system exception.
/// </summary>
/// <param name="exception">The exception to log.</param>
/// <param name="properties">Named string values you can use to classify and search for this exception.</param>
/// <param name="metrics">Additional values associated with this exception.</param>
public void TrackException(Exception exception, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
{
using (var activity = BotOpenTelemetryHelper.ActivitySource.StartActivity("Exception"))
{
activity.DisplayName = exception.Message;

if (properties != null)
{
foreach (var property in properties)
{
activity.SetTag(property.Key, property.Value);
}
}

if (metrics != null)
{
foreach (var metric in metrics)
{
if (!meters.TryGetValue(metric.Key, out Histogram<double> meter))
{
meter = BotOpenTelemetryHelper.Meter.CreateHistogram<double>(metric.Key);
meters.Add(metric.Key, meter);
}

meter.Record(metric.Value);
activity.SetTag($"metrics.{metric.Key}", metric.Value);
}
}

activity?.SetStatus(ActivityStatusCode.Error, exception.Message);
activity?.RecordException(exception);
}
}

/// <summary>
/// Logs a dialog entry / as an Application Insights page view.
/// </summary>
/// <param name="dialogName">The name of the dialog to log the entry / start for.</param>
/// <param name="properties">Named string values you can use to search and classify events.</param>
/// <param name="metrics">Measurements associated with this event.</param>
public void TrackPageView(string dialogName, IDictionary<string, string> properties = null, IDictionary<string, double> metrics = null)
{
using (var activity = BotOpenTelemetryHelper.ActivitySource.StartActivity("PageView"))
{
activity.DisplayName = dialogName;

if (properties != null)
{
foreach (var property in properties)
{
activity.SetTag(property.Key, property.Value);
}
}

if (metrics != null)
{
foreach (var metric in metrics)
{
if (!meters.TryGetValue(metric.Key, out Histogram<double> meter))
{
meter = BotOpenTelemetryHelper.Meter.CreateHistogram<double>(metric.Key);
meters.Add(metric.Key, meter);
}

meter.Record(metric.Value);
activity.SetTag($"metrics.{metric.Key}", metric.Value);
}
}
}
}

/// <summary>
/// Send a trace message.
/// </summary>
/// <param name="message">Message to display.</param>
/// <param name="severityLevel">Trace severity level <see cref="Severity"/>.</param>
/// <param name="properties">Named string values you can use to search and classify events.</param>
public void TrackTrace(string message, Severity severityLevel, IDictionary<string, string> properties)
{
using (var activity = BotOpenTelemetryHelper.ActivitySource.StartActivity("Trace"))
{
activity.DisplayName = message;
activity.AddTag("trace.message", message);
activity.AddTag("trace.severityLevel", severityLevel.ToString());

var eventTags = new ActivityTagsCollection();

if (properties != null)
{
foreach (var property in properties)
{
eventTags.Add(property.Key, property.Value);
}
}
}
}

/// <summary>
/// Flushes the in-memory buffer and any metrics being pre-aggregated.
/// </summary>
public void Flush()
{
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Diagnostics.Metrics;

namespace Bot.Builder.Community.OpenTelemetry
{

internal class BotOpenTelemetryHelper
{
public static readonly AssemblyName AssemblyName = typeof(BotOpenTelemetryHelper).Assembly.GetName();
public static readonly string InstrumentationName = AssemblyName.Name;
public static readonly string ActivityName = InstrumentationName + ".Execute";
private static readonly Version Version = typeof(BotOpenTelemetryHelper).Assembly.GetName().Version;
internal static readonly ActivitySource ActivitySource = new ActivitySource(InstrumentationName, Version.ToString());
internal static readonly Meter Meter = new Meter(InstrumentationName, Version.ToString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using Bot.Builder.Community.OpenTelemetry;
using Microsoft.Extensions.DependencyInjection;
using OpenTelemetry.Internal;
using OpenTelemetry.Metrics;

namespace OpenTelemetry.Trace
{
/// <summary>
/// Extension methods to simplify registering of dependency instrumentation.
/// </summary>
public static class MeterProviderBuilderExtensions
{
/// <summary>
/// Enables Bot Builder instrumentation.
/// </summary>
/// <param name="builder"><see cref="TracerProviderBuilder"/> being configured.</param>
/// <param name="configureBotBuilderInstrumentationOptions">Bot Builder configuration options.</param>
/// <returns>The instance of <see cref="TracerProviderBuilder"/> to chain the calls.</returns>
public static MeterProviderBuilder AddBotBuilderInstrumentation(
this MeterProviderBuilder builder)
{

builder.ConfigureServices(services => services.AddSingleton<BotOpenTelemetryClient>());

builder.AddMeter(BotOpenTelemetryHelper.InstrumentationName);

return builder;
}
}
}
Loading