Enable/disable log instrumentations (#1475)

* Introduce instrumentation types

* extend generator

* support for disabling logs bytecode integrations

* update documentation

* change LoggingBuilder instrumentation name to ILogger

* Support managing log instrumentation in managed code

* update changelog

* PR feedback - config

* add LogsNoneInstrumentations test

Co-authored-by: Chris Ventura <45495992+nrcventura@users.noreply.github.com>
This commit is contained in:
Piotr Kiełkowicz 2022-10-21 22:31:53 +02:00 committed by GitHub
parent b451a89637
commit b4a4120bbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 367 additions and 104 deletions

View File

@ -14,6 +14,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Support ASP.NET Core OpenTelemetry Log exporter related environment variables:
- `OTEL_LOGS_EXPORTER`,
- `OTEL_DOTNET_AUTO_LOGS_CONSOLE_EXPORTER_ENABLED`,
- `OTEL_DOTNET_AUTO_LOGS_ENABLED_INSTRUMENTATIONS`,
- `OTEL_DOTNET_AUTO_LOGS_DISABLED_INSTRUMENTATIONS`,
- `OTEL_DOTNET_AUTO_LOGS_INCLUDE_FORMATTED_MESSAGE`.
- Support `OTEL_DOTNET_AUTO_GRAPHQL_SET_DOCUMENT` (default value: `false`)
environment variable which controls whether `graphql.document` attribute

View File

@ -25,10 +25,12 @@ for more details.
| Environment variable | Description | Default value |
|------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
| `OTEL_DOTNET_AUTO_INTEGRATIONS_FILE` | List of bytecode instrumentations JSON configuration filepaths, delimited by the platform-specific path separator (`;` on Windows, `:` on Linux and macOS). For example: `%ProfilerDirectory%/integrations.json`. It is required for bytecode instrumentations. | |
| `OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS` | Comma-separated list of traces source instrumentations you want to enable. Set `none` to disable all trace instrumentations. | all available instrumentations |
| `OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS` | Comma-separated list of traces source instrumentations you want to enable. Set to `none` to disable all trace instrumentations. | all available instrumentations |
| `OTEL_DOTNET_AUTO_TRACES_DISABLED_INSTRUMENTATIONS` | Comma-separated list of traces source and bytecode instrumentations you want to disable. | |
| `OTEL_DOTNET_AUTO_METRICS_ENABLED_INSTRUMENTATIONS` | Comma-separated list of metrics source instrumentations you want to enable. Set `none` to disable all metric instrumentations. | all available instrumentations |
| `OTEL_DOTNET_AUTO_METRICS_ENABLED_INSTRUMENTATIONS` | Comma-separated list of metrics source instrumentations you want to enable. Set to `none` to disable all metric instrumentations. | all available instrumentations |
| `OTEL_DOTNET_AUTO_METRICS_DISABLED_INSTRUMENTATIONS` | Comma-separated list of metrics source instrumentations you want to disable. | |
| `OTEL_DOTNET_AUTO_LOGS_ENABLED_INSTRUMENTATIONS` | Comma-separated list of logs source instrumentations you want to enable. Set to `none` to disable all metric instrumentations. | all available instrumentations |
| `OTEL_DOTNET_AUTO_LOGS_DISABLED_INSTRUMENTATIONS` | Comma-separated list of logs source instrumentations you want to disable. | |
### Traces instrumentations
@ -62,8 +64,8 @@ for more details.
### Logs instrumentations
| ID | Instrumented library | Supported versions | Instrumentation type |
|----|---------------------------------------------------------------------------------------------------------------------------------|--------------------|------------------------|
| | [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) **Not supported on .NET Framework** | ≥6.0.0 | bytecode or source [1] |
|---------|---------------------------------------------------------------------------------------------------------------------------------|--------------------|------------------------|
| ILogger | [Microsoft.Extensions.Logging](https://www.nuget.org/packages/Microsoft.Extensions.Logging) **Not supported on .NET Framework** | ≥6.0.0 | bytecode or source [1] |
**[1]**: For ASP.NET Core applications, the `LoggingBuilder` instrumentation
can be enabled without using the .NET CLR Profiler by setting

View File

@ -1,6 +1,7 @@
[
{
"name": "GraphQL",
"type": "Trace",
"method_replacements": [
{
"caller": {},
@ -51,7 +52,8 @@
]
},
{
"name": "LoggingBuilder",
"name": "ILogger",
"type": "Log",
"method_replacements": [
{
"caller": {},
@ -80,6 +82,7 @@
},
{
"name": "MongoDB",
"type": "Trace",
"method_replacements": [
{
"caller": {},
@ -108,6 +111,7 @@
},
{
"name": "MySqlData",
"type": "Trace",
"method_replacements": [
{
"caller": {},
@ -135,6 +139,7 @@
},
{
"name": "StackExchangeRedis",
"type": "Trace",
"method_replacements": [
{
"caller": {},

View File

@ -28,14 +28,14 @@ namespace OpenTelemetry.AutoInstrumentation.AspNetCoreBootstrapper;
/// </summary>
internal class BootstrapperHostingStartup : IHostingStartup
{
private readonly LogSettings settings;
private readonly LogSettings _settings;
/// <summary>
/// Initializes a new instance of the <see cref="BootstrapperHostingStartup"/> class.
/// </summary>
public BootstrapperHostingStartup()
{
settings = LogSettings.FromDefaultSources();
_settings = LogSettings.FromDefaultSources();
}
/// <summary>
@ -44,6 +44,12 @@ internal class BootstrapperHostingStartup : IHostingStartup
/// <param name="builder">The <see cref="IWebHostBuilder"/>.</param>
public void Configure(IWebHostBuilder builder)
{
if (!_settings.EnabledInstrumentations.Contains(LogInstrumentation.ILogger))
{
BootstrapperEventSource.Log.Trace($"BootstrapperHostingStartup loaded, but {nameof(LogInstrumentation.ILogger)} instrumentation is disabled. Skipping.");
return;
}
try
{
builder.ConfigureLogging(logging => logging.AddOpenTelemetryLogs());

View File

@ -179,7 +179,12 @@ HRESULT STDMETHODCALLTYPE CorProfiler::Initialize(IUnknown* cor_profiler_info_un
rejit_handler = new RejitHandler(this->info_, callback);
// load all integrations from JSON files
LoadIntegrationsFromEnvironment(integration_methods_, GetEnvironmentValues(environment::enabled_integrations), GetEnvironmentValues(environment::disabled_integrations));
LoadIntegrationsFromEnvironment(
integration_methods_,
GetEnvironmentValues(environment::enabled_traces_integrations),
GetEnvironmentValues(environment::disabled_traces_integrations),
GetEnvironmentValues(environment::enabled_logs_integrations),
GetEnvironmentValues(environment::disabled_logs_integrations));
Logger::Debug("Number of Integrations loaded: ", integration_methods_.size());

View File

@ -37,7 +37,7 @@ const WSTRING exclude_process_names = WStr("OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES")
// Sets a list of integrations to enable. If not set (default), all integrations are enabled.
// Supports multiple values separated with comma, for example:
// "ElasticsearchNet,AspNetWebApi2"
const WSTRING enabled_integrations =
const WSTRING enabled_traces_integrations =
WStr("OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS");
// Sets a list of integrations to disable. Status of other integrations will remain
@ -45,9 +45,24 @@ const WSTRING enabled_integrations =
// then if instrumentation is not explicitly disabled OTEL_DOTNET_AUTO_TRACES_ENABLED_INSTRUMENTATIONS is checked.
// Supports multiple values separated with comma, for example:
// "ElasticsearchNet,AspNetWebApi2"
const WSTRING disabled_integrations =
const WSTRING disabled_traces_integrations =
WStr("OTEL_DOTNET_AUTO_TRACES_DISABLED_INSTRUMENTATIONS");
// Sets a list of integrations to enable. If not set (default), all integrations
// are enabled. Supports multiple values separated with comma, for example:
// "ElasticsearchNet,AspNetWebApi2"
const WSTRING enabled_logs_integrations =
WStr("OTEL_DOTNET_AUTO_LOGS_ENABLED_INSTRUMENTATIONS");
// Sets a list of integrations to disable. Status of other integrations will
// remain unchanged. Calculation order:
// OTEL_DOTNET_AUTO_LOGS_DISABLED_INSTRUMENTATIONS then if instrumentation is
// not explicitly disabled OTEL_DOTNET_AUTO_LOGS_ENABLED_INSTRUMENTATIONS is
// checked. Supports multiple values separated with comma, for example:
// "ElasticsearchNet,AspNetWebApi2"
const WSTRING disabled_logs_integrations =
WStr("OTEL_DOTNET_AUTO_LOGS_DISABLED_INSTRUMENTATIONS");
// Sets the directory for the profiler's log file.
// If not set, default is
// "%ProgramData%"\OpenTelemetry .NET AutoInstrumentation\logs\" on Windows or

View File

@ -1,7 +1,6 @@
#include "integration_loader.h"
#include <exception>
#include <stdexcept>
#include "environment_variables.h"
#include "logger.h"
@ -14,20 +13,25 @@ using json = nlohmann::json;
void LoadIntegrationsFromEnvironment(
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames) {
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames) {
for (const WSTRING& filePath : GetEnvironmentValues(environment::integrations_path, ENV_VAR_PATH_SEPARATOR))
{
Logger::Debug("Loading integrations from file: ", filePath);
LoadIntegrationsFromFile(filePath, integrationMethods, enabledIntegrationNames, disabledIntegrationNames);
LoadIntegrationsFromFile(
filePath, integrationMethods, enabledTraceIntegrationNames, disabledTraceIntegrationNames, enabledLogIntegrationNames, disabledLogIntegrationNames);
}
}
void LoadIntegrationsFromFile(
const WSTRING& file_path,
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames) {
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames) {
try
{
std::ifstream stream(ToString(file_path));
@ -36,8 +40,10 @@ void LoadIntegrationsFromFile(
{
LoadIntegrationsFromStream(stream,
integrationMethods,
enabledIntegrationNames,
disabledIntegrationNames);
enabledTraceIntegrationNames,
disabledTraceIntegrationNames,
enabledLogIntegrationNames,
disabledLogIntegrationNames);
}
else
{
@ -66,8 +72,10 @@ void LoadIntegrationsFromFile(
void LoadIntegrationsFromStream(
std::istream& stream,
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames) {
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames) {
try
{
json j;
@ -78,7 +86,12 @@ void LoadIntegrationsFromStream(
for (const auto& el : j)
{
IntegrationFromJson(el, integrationMethods, enabledIntegrationNames, disabledIntegrationNames);
IntegrationFromJson(el,
integrationMethods,
enabledTraceIntegrationNames,
disabledTraceIntegrationNames,
enabledLogIntegrationNames,
disabledLogIntegrationNames);
}
}
@ -114,15 +127,6 @@ namespace
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames)
{
// LoggingBuilder has to be always enabled.
// Technically it is not an instrumentation but
// it is using the same functionality.
// See https://github.com/open-telemetry/opentelemetry-dotnet-instrumentation/issues/1310.
if (name == WStr("LoggingBuilder"))
{
return true;
}
// check if the integration is disabled
for (const WSTRING& disabledName : disabledIntegrationNames)
{
@ -150,8 +154,10 @@ namespace
void IntegrationFromJson(const json::value_type& src,
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames)
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames)
{
if (!src.is_object())
{
@ -166,8 +172,29 @@ namespace
return;
}
if (!InstrumentationEnabled(name, enabledIntegrationNames, disabledIntegrationNames))
const WSTRING type = ToWSTRING(src.value("type", ""));
if (name.empty())
{
Logger::Warn("Integration type is missing for integration: ", src.dump());
return;
}
if (type == WStr("Trace"))
{
if (!InstrumentationEnabled(name, enabledTraceIntegrationNames, disabledTraceIntegrationNames))
{
return;
}
}
else if (type == WStr("Log"))
{
if (!InstrumentationEnabled(name, enabledLogIntegrationNames, disabledLogIntegrationNames)) {
return;
}
}
else
{
Logger::Warn("Unsupported type for integration: ", src.dump());
return;
}

View File

@ -19,29 +19,37 @@ using json = nlohmann::json;
// in the OTEL_DOTNET_AUTO_INTEGRATIONS_FILE environment variable
void LoadIntegrationsFromEnvironment(
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames);
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames);
// LoadIntegrationsFromFile loads the integrations from a file
void LoadIntegrationsFromFile(
const WSTRING& file_path,
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames);
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames);
// LoadIntegrationsFromFile loads the integrations from a stream
void LoadIntegrationsFromStream(
std::istream& stream,
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames);
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames);
namespace
{
void IntegrationFromJson(const json::value_type& src,
std::vector<IntegrationMethod>& integrationMethods,
const std::vector<WSTRING>& enabledIntegrationNames,
const std::vector<WSTRING>& disabledIntegrationNames);
const std::vector<WSTRING>& enabledTraceIntegrationNames,
const std::vector<WSTRING>& disabledTraceIntegrationNames,
const std::vector<WSTRING>& enabledLogIntegrationNames,
const std::vector<WSTRING>& disabledLogIntegrationNames);
void MethodReplacementFromJson(const json::value_type& src, const WSTRING& integrationName,
std::vector<IntegrationMethod>& integrationMethods);

View File

@ -174,6 +174,16 @@ internal static class ConfigurationKeys
/// should be included on generated <see cref="LogRecord"/>s.
/// </summary>
public const string IncludeFormattedMessage = "OTEL_DOTNET_AUTO_LOGS_INCLUDE_FORMATTED_MESSAGE";
/// <summary>
/// Configuration key for comma separated list of enabled logs instrumentations.
/// </summary>
public const string Instrumentations = "OTEL_DOTNET_AUTO_LOGS_ENABLED_INSTRUMENTATIONS";
/// <summary>
/// Configuration key for comma separated list of disabled logs instrumentations.
/// </summary>
public const string DisabledInstrumentations = "OTEL_DOTNET_AUTO_LOGS_DISABLED_INSTRUMENTATIONS";
}
/// <summary>

View File

@ -0,0 +1,28 @@
// <copyright file="LogInstrumentation.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>
namespace OpenTelemetry.AutoInstrumentation.Configuration;
/// <summary>
/// Enum representing supported meter instrumentations.
/// </summary>
internal enum LogInstrumentation
{
/// <summary>
/// ILogger instrumentation.
/// </summary>
ILogger
}

View File

@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
using OpenTelemetry.AutoInstrumentation.Util;
namespace OpenTelemetry.AutoInstrumentation.Configuration;
@ -35,6 +36,11 @@ internal class LogSettings : Settings
LogExporter = ParseLogExporter(source);
ConsoleExporterEnabled = source.GetBool(ConfigurationKeys.Logs.ConsoleExporterEnabled) ?? false;
IncludeFormattedMessage = source.GetBool(ConfigurationKeys.Logs.IncludeFormattedMessage) ?? false;
EnabledInstrumentations = source.ParseEnabledEnumList<LogInstrumentation>(
enabledConfiguration: ConfigurationKeys.Logs.Instrumentations,
disabledConfiguration: ConfigurationKeys.Logs.DisabledInstrumentations,
error: "The \"{0}\" is not recognized as supported logs instrumentation and cannot be enabled or disabled.");
}
/// <summary>
@ -52,6 +58,11 @@ internal class LogSettings : Settings
/// </summary>
public bool ConsoleExporterEnabled { get; }
/// <summary>
/// Gets the list of enabled instrumentations.
/// </summary>
public IList<LogInstrumentation> EnabledInstrumentations { get; }
internal static LogSettings FromDefaultSources()
{
var configurationSource = new CompositeConfigurationSource

View File

@ -24,5 +24,6 @@ internal class GraphQLExecuteAsyncAttribute : InstrumentMethodAttribute
MethodName = "ExecuteAsync";
ReturnTypeName = "System.Threading.Tasks.Task`1[GraphQL.ExecutionResult]";
ParameterTypeNames = new[] { "GraphQL.Execution.ExecutionContext" };
Type = InstrumentationType.Trace;
}
}

View File

@ -101,4 +101,9 @@ internal class InstrumentMethodAttribute : Attribute
/// Gets or sets the integration name. Allows to group several integration with a single integration name.
/// </summary>
public string IntegrationName { get; set; }
/// <summary>
/// Gets or sets the integration type.
/// </summary>
public InstrumentationType Type { get; set; }
}

View File

@ -0,0 +1,24 @@
// <copyright file="InstrumentationType.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>
namespace OpenTelemetry.AutoInstrumentation.Instrumentations;
internal enum InstrumentationType
{
Trace,
// Metric,
Log
}

View File

@ -30,7 +30,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.Logger;
ParameterTypeNames = new[] { "Microsoft.Extensions.DependencyInjection.IServiceCollection" },
MinimumVersion = "3.1.0",
MaximumVersion = "6.*.*",
IntegrationName = "LoggingBuilder")]
IntegrationName = "ILogger",
Type = InstrumentationType.Log)]
public class LoggingBuilderIntegration
{
/// <summary>

View File

@ -36,7 +36,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.MongoDB;
ParameterTypeNames = new[] { "MongoDB.Driver.MongoClientSettings" },
MinimumVersion = "2.13.3",
MaximumVersion = "2.65535.65535",
IntegrationName = "MongoDB")]
IntegrationName = "MongoDB",
Type = InstrumentationType.Trace)]
public class MongoClientIntegration
{
#if NETCOREAPP3_1_OR_GREATER

View File

@ -30,7 +30,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis;
ParameterTypeNames = new[] { ClrNames.Object, StackExchangeRedisConstants.TextWriterTypeName },
MinimumVersion = StackExchangeRedisConstants.MinimumVersion,
MaximumVersion = StackExchangeRedisConstants.MaximumVersion,
IntegrationName = StackExchangeRedisConstants.IntegrationName)]
IntegrationName = StackExchangeRedisConstants.IntegrationName,
Type = InstrumentationType.Trace)]
[InstrumentMethod(// releases 2.1.50 - 2.5.43
AssemblyName = StackExchangeRedisConstants.AssemblyName,
TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName,
@ -39,7 +40,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis;
ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName },
MinimumVersion = StackExchangeRedisConstants.MinimumVersion,
MaximumVersion = StackExchangeRedisConstants.MaximumVersion,
IntegrationName = StackExchangeRedisConstants.IntegrationName)]
IntegrationName = StackExchangeRedisConstants.IntegrationName,
Type = InstrumentationType.Trace)]
[InstrumentMethod(// releases 2.5.61 - 2.6.48
AssemblyName = StackExchangeRedisConstants.AssemblyName,
TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName,
@ -48,7 +50,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis;
ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName, StackExchangeRedisConstants.NullableServerTypeTypeName },
MinimumVersion = StackExchangeRedisConstants.MinimumVersion,
MaximumVersion = StackExchangeRedisConstants.MaximumVersion,
IntegrationName = StackExchangeRedisConstants.IntegrationName)]
IntegrationName = StackExchangeRedisConstants.IntegrationName,
Type = InstrumentationType.Trace)]
[InstrumentMethod(// releases 2.6.66+
AssemblyName = StackExchangeRedisConstants.AssemblyName,
TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName,
@ -57,7 +60,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis;
ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName, StackExchangeRedisConstants.NullableServerTypeTypeName, StackExchangeRedisConstants.EndPointCollectionTypeName },
MinimumVersion = StackExchangeRedisConstants.MinimumVersion,
MaximumVersion = StackExchangeRedisConstants.MaximumVersion,
IntegrationName = StackExchangeRedisConstants.IntegrationName)]
IntegrationName = StackExchangeRedisConstants.IntegrationName,
Type = InstrumentationType.Trace)]
public class StackExchangeRedisIntegration
{
/// <summary>

View File

@ -30,7 +30,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis;
ParameterTypeNames = new[] { ClrNames.Object, StackExchangeRedisConstants.TextWriterTypeName },
MinimumVersion = StackExchangeRedisConstants.MinimumVersion,
MaximumVersion = StackExchangeRedisConstants.MaximumVersion,
IntegrationName = StackExchangeRedisConstants.IntegrationName)]
IntegrationName = StackExchangeRedisConstants.IntegrationName,
Type = InstrumentationType.Trace)]
[InstrumentMethod(// releases 2.1.50 - 2.5.43
AssemblyName = StackExchangeRedisConstants.AssemblyName,
TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName,
@ -39,7 +40,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis;
ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName },
MinimumVersion = StackExchangeRedisConstants.MinimumVersion,
MaximumVersion = StackExchangeRedisConstants.MaximumVersion,
IntegrationName = StackExchangeRedisConstants.IntegrationName)]
IntegrationName = StackExchangeRedisConstants.IntegrationName,
Type = InstrumentationType.Trace)]
[InstrumentMethod(// releases 2.5.61+
AssemblyName = StackExchangeRedisConstants.AssemblyName,
TypeName = StackExchangeRedisConstants.ConnectionMultiplexerTypeName,
@ -48,7 +50,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.StackExchangeRedis;
ParameterTypeNames = new[] { StackExchangeRedisConstants.ConfigurationOptionsTypeName, StackExchangeRedisConstants.TextWriterTypeName, StackExchangeRedisConstants.NullableServerTypeTypeName },
MinimumVersion = StackExchangeRedisConstants.MinimumVersion,
MaximumVersion = StackExchangeRedisConstants.MaximumVersion,
IntegrationName = StackExchangeRedisConstants.IntegrationName)]
IntegrationName = StackExchangeRedisConstants.IntegrationName,
Type = InstrumentationType.Trace)]
public class StackExchangeRedisIntegrationAsync
{
/// <summary>

View File

@ -33,7 +33,8 @@ namespace OpenTelemetry.AutoInstrumentation.Instrumentations.Validations;
ParameterTypeNames = new string[0],
MinimumVersion = "1.0.0",
MaximumVersion = "1.65535.65535",
IntegrationName = "StrongNamedValidation")]
IntegrationName = "StrongNamedValidation",
Type = InstrumentationType.Trace)]
public class StrongNamedValidation
{
private static readonly ActivitySource ValidationActivitySource = new ActivitySource("TestApplication.StrongNamedValidation");

View File

@ -139,6 +139,15 @@ public class MockLogsCollector : IDisposable
}
}
public void AssertEmpty(TimeSpan? timeout = null)
{
timeout ??= DefaultWaitTimeout;
if (_logs.TryTake(out var logRecord, timeout.Value))
{
Assert.Fail($"Expected nothing, but got: {logRecord}");
}
}
private static void FailExpectations(
List<Expectation> missingExpectations,
List<global::OpenTelemetry.Proto.Logs.V1.LogRecord> expectationsMet,

View File

@ -14,7 +14,6 @@
// limitations under the License.
// </copyright>
using System.Collections.Immutable;
using System.Net;
using System.Net.Http;
using System.Reflection;
@ -272,6 +271,18 @@ public class SmokeTests : TestHelper
collector.AssertExpectations();
}
[Fact]
[Trait("Category", "EndToEnd")]
public async Task LogsNoneInstrumentations()
{
using var collector = await MockLogsCollector.Start(Output);
SetEnvironmentVariable("OTEL_DOTNET_AUTO_LOGS_ENABLED_INSTRUMENTATIONS", "none");
RunTestApplication(logsAgentPort: collector.Port, enableClrProfiler: true);
collector.AssertEmpty(5.Seconds());
}
#endif
private async Task VerifyTestApplicationInstrumented(bool enableStartupHook = true, bool enableClrProfiler = true)

View File

@ -1,6 +1,7 @@
[
{
"name": "StrongNamedValidation",
"type": "Trace",
"method_replacements": [
{
"caller": {},

View File

@ -16,7 +16,7 @@ using namespace trace;
TEST(IntegrationLoaderTest, HandlesMissingFile)
{
std::vector<IntegrationMethod> integrations;
LoadIntegrationsFromFile(L"missing-file", integrations, {}, {});
LoadIntegrationsFromFile(L"missing-file", integrations, {}, {}, {}, {});
EXPECT_EQ(0, integrations.size());
}
@ -24,7 +24,7 @@ TEST(IntegrationLoaderTest, HandlesInvalidIntegrationNoName)
{
std::vector<IntegrationMethod> integrations;
std::stringstream str("[{}]");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
// 0 because name is required
EXPECT_EQ(0, integrations.size());
}
@ -33,7 +33,7 @@ TEST(IntegrationLoaderTest, HandlesInvalidIntegrationBadJson)
{
std::vector<IntegrationMethod> integrations;
std::stringstream str("[");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
EXPECT_EQ(0, integrations.size());
}
@ -41,7 +41,7 @@ TEST(IntegrationLoaderTest, HandlesInvalidIntegrationNotAnObject)
{
std::vector<IntegrationMethod> integrations;
std::stringstream str("[1,2,3]");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
EXPECT_EQ(0, integrations.size());
}
@ -51,7 +51,7 @@ TEST(IntegrationLoaderTest, HandlesInvalidIntegrationNotAnArray)
std::stringstream str(R"TEXT(
{"name": "test-integration"}
)TEXT");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
EXPECT_EQ(0, integrations.size());
}
@ -61,6 +61,7 @@ TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithMethodReplacements)
std::stringstream str(R"TEXT(
[{
"name": "test-integration",
"type": "Trace",
"method_replacements": [{
"caller": { },
"target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "minimum_major": 0, "minimum_minor": 1, "maximum_major": 10, "maximum_minor": 0 },
@ -69,7 +70,7 @@ TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithMethodReplacements)
}]
)TEXT");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
EXPECT_EQ(1, integrations.size());
EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str());
}
@ -80,6 +81,7 @@ TEST(IntegrationLoaderTest, DoesNotCrashWithOutOfRangeVersion)
std::stringstream str(R"TEXT(
[{
"name": "test-integration",
"type": "Trace",
"method_replacements": [{
"caller": { },
"target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "minimum_major": 0, "minimum_minor": 1, "maximum_major": 75555, "maximum_minor": 0 },
@ -88,7 +90,7 @@ TEST(IntegrationLoaderTest, DoesNotCrashWithOutOfRangeVersion)
}]
)TEXT");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
EXPECT_EQ(1, integrations.size());
EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str());
@ -111,6 +113,7 @@ TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithMissingCaller)
std::stringstream str(R"TEXT(
[{
"name": "test-integration",
"type": "Trace",
"method_replacements": [{
"target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "minimum_major": 1, "minimum_minor": 2, "maximum_major": 10, "maximum_minor": 99 },
"wrapper": { "assembly": "Assembly.Two", "type": "Type.Two", "method": "Method.Two", "signature": [0, 1, 1, 28], "action": "CallTargetModification" }
@ -118,7 +121,7 @@ TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithMissingCaller)
}]
)TEXT");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
EXPECT_EQ(1, integrations.size());
EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str());
@ -148,6 +151,7 @@ TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithInvalidTarget)
std::stringstream str(R"TEXT(
[{
"name": "test-integration",
"type": "Trace",
"method_replacements": [{
"target": 1234,
"wrapper": { "assembly": "Assembly.Two", "type": "Type.Two", "method": "Method.Two", "action": "CallTargetModification" }
@ -155,7 +159,7 @@ TEST(IntegrationLoaderTest, HandlesSingleIntegrationWithInvalidTarget)
}]
)TEXT");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
EXPECT_EQ(1, integrations.size());
EXPECT_STREQ(L"test-integration", integrations[0].integration_name.c_str());
@ -172,12 +176,12 @@ TEST(IntegrationLoaderTest, LoadsFromEnvironment)
std::ofstream f;
f.open(temp_name1);
f << R"TEXT(
[{ "name": "test-integration-1", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }]
[{ "name": "test-integration-1", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }]
)TEXT";
f.close();
f.open(temp_name2);
f << R"TEXT(
[{ "name": "test-integration-2", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }]
[{ "name": "test-integration-2", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }]
)TEXT";
f.close();
@ -188,7 +192,7 @@ TEST(IntegrationLoaderTest, LoadsFromEnvironment)
const std::vector<std::wstring> expected_names = {L"test-integration-1", L"test-integration-2"};
std::vector<std::wstring> actual_names;
std::vector<IntegrationMethod> integrations;
LoadIntegrationsFromEnvironment(integrations, {}, {});
LoadIntegrationsFromEnvironment(integrations, {}, {}, {}, {});
for (auto& integration : integrations)
{
actual_names.push_back(integration.integration_name);
@ -205,6 +209,7 @@ TEST(IntegrationLoaderTest, DeserializesSignatureTypeArray)
std::stringstream str(R"TEXT(
[{
"name": "test-integration",
"type": "Trace",
"method_replacements": [{
"caller": { },
"target": { "assembly": "Assembly.One", "type": "Type.One", "method": "Method.One", "signature_types": ["System.Void", "_", "FakeClient.Pipeline'1<T>"] },
@ -213,25 +218,25 @@ TEST(IntegrationLoaderTest, DeserializesSignatureTypeArray)
}]
)TEXT");
LoadIntegrationsFromStream(str, integrations, {}, {});
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {});
const auto target = integrations[0].replacement.target_method;
EXPECT_STREQ(L"System.Void", target.signature_types[0].c_str());
EXPECT_STREQ(L"_", target.signature_types[1].c_str());
EXPECT_STREQ(L"FakeClient.Pipeline'1<T>", target.signature_types[2].c_str());
}
TEST(IntegrationLoaderTest, SupportsEnabledIntegrations) {
TEST(IntegrationLoaderTest, SupportsEnabledTraceIntegrations) {
std::vector<IntegrationMethod> integrations;
std::stringstream str(R"TEXT(
[
{ "name": "test-integration-1", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-integration-2", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
{ "name": "test-trace-integration-1", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-trace-integration-2", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
]
)TEXT");
const std::vector<std::wstring> expected_names = {L"test-integration-2"};
const std::vector<std::wstring> expected_names = {L"test-trace-integration-2"};
std::vector<std::wstring> actual_names;
LoadIntegrationsFromStream(str, integrations, {L"test-integration-2"}, {});
LoadIntegrationsFromStream(str, integrations, {L"test-trace-integration-2"}, {}, {}, {});
for (auto& integration : integrations)
{
actual_names.push_back(integration.integration_name);
@ -239,39 +244,88 @@ TEST(IntegrationLoaderTest, SupportsEnabledIntegrations) {
EXPECT_EQ(expected_names, actual_names);
}
TEST(IntegrationLoaderTest, SupportsDisabledIntegrations) {
TEST(IntegrationLoaderTest, SupportsEnabledLogIntegrations)
{
std::vector<IntegrationMethod> integrations;
std::stringstream str(R"TEXT(
[
{ "name": "test-integration-1", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-integration-2", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
{ "name": "test-log-integration-1", "type": "Log", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-log-integration-2", "type": "Log", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
]
)TEXT");
const std::vector<std::wstring> expected_names = {L"test-integration-1"};
const std::vector<std::wstring> expected_names = {L"test-log-integration-2"};
std::vector<std::wstring> actual_names;
LoadIntegrationsFromStream(str, integrations, {}, {L"test-integration-2"});
for (auto& integration : integrations) {
LoadIntegrationsFromStream(str, integrations, {}, {}, {L"test-log-integration-2"}, {});
for (auto& integration : integrations)
{
actual_names.push_back(integration.integration_name);
}
EXPECT_EQ(expected_names, actual_names);
}
TEST(IntegrationLoaderTest, SupportsEnabledAndDisabledIntegrations) {
TEST(IntegrationLoaderTest, SupportsDisabledTraceIntegrations)
{
std::vector<IntegrationMethod> integrations;
std::stringstream str(R"TEXT(
[
{ "name": "test-integration-1", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-integration-2", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-integration-3", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
{ "name": "test-trace-integration-1", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-trace-integration-2", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
]
)TEXT");
const std::vector<std::wstring> expected_names = {L"test-integration-1"};
const std::vector<std::wstring> expected_names = {L"test-trace-integration-1"};
std::vector<std::wstring> actual_names;
LoadIntegrationsFromStream(str, integrations, {}, {L"test-trace-integration-2"}, {}, {});
for (auto& integration : integrations)
{
actual_names.push_back(integration.integration_name);
}
EXPECT_EQ(expected_names, actual_names);
}
TEST(IntegrationLoaderTest, SupportsDisabledLogIntegrations)
{
std::vector<IntegrationMethod> integrations;
std::stringstream str(R"TEXT(
[
{ "name": "test-log-integration-1", "type": "Log", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-log-integration-2", "type": "Log", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
]
)TEXT");
const std::vector<std::wstring> expected_names = {L"test-log-integration-1"};
std::vector<std::wstring> actual_names;
LoadIntegrationsFromStream(str, integrations, {}, {}, {}, {L"test-log-integration-2"});
for (auto& integration : integrations)
{
actual_names.push_back(integration.integration_name);
}
EXPECT_EQ(expected_names, actual_names);
}
TEST(IntegrationLoaderTest, SupportsEnabledAndDisabledIntegrations)
{
std::vector<IntegrationMethod> integrations;
std::stringstream str(R"TEXT(
[
{ "name": "test-trace-integration-1", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-trace-integration-2", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-trace-integration-3", "type": "Trace", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-log-integration-1", "type": "Log", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-log-integration-2", "type": "Log", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] },
{ "name": "test-log-integration-3", "type": "Log", "method_replacements": [{ "caller": {}, "target": {}, "wrapper": {"action": "CallTargetModification"} }] }
]
)TEXT");
const std::vector<std::wstring> expected_names = {L"test-trace-integration-1", L"test-log-integration-1"};
std::vector<std::wstring> actual_names;
LoadIntegrationsFromStream(str, integrations,
{L"test-integration-1", L"test-integration-2"},
{L"test-integration-2", L"test-integration-3"});
{L"test-trace-integration-1", L"test-trace-integration-2"},
{L"test-trace-integration-2", L"test-trace-integration-3"},
{L"test-log-integration-1", L"test-log-integration-2"},
{L"test-log-integration-2", L"test-log-integration-3"});
for (auto& integration : integrations)
{
actual_names.push_back(integration.integration_name);

View File

@ -98,6 +98,7 @@ public class SettingsTests : IDisposable
settings.LogExporter.Should().Be(LogExporter.Otlp);
settings.OtlpExportProtocol.Should().Be(OtlpExportProtocol.HttpProtobuf);
settings.ConsoleExporterEnabled.Should().BeFalse();
settings.EnabledInstrumentations.Should().NotBeEmpty();
settings.Plugins.Should().BeEmpty();
settings.IncludeFormattedMessage.Should().BeFalse();
settings.Http2UnencryptedSupportEnabled.Should().BeFalse();
@ -231,6 +232,27 @@ public class SettingsTests : IDisposable
settings.EnabledInstrumentations.Should().BeEquivalentTo(new List<MetricInstrumentation> { MetricInstrumentation.NetRuntime });
}
[Theory]
[InlineData(nameof(LogInstrumentation.ILogger), LogInstrumentation.ILogger)]
internal void LogSettings_Instrumentations_SupportedValues(string logInstrumentation, LogInstrumentation expectedLogInstrumentation)
{
Environment.SetEnvironmentVariable(ConfigurationKeys.Metrics.Instrumentations, logInstrumentation);
var settings = LogSettings.FromDefaultSources();
settings.EnabledInstrumentations.Should().BeEquivalentTo(new List<LogInstrumentation> { expectedLogInstrumentation });
}
[Fact]
internal void LogSettings_DisabledInstrumentations()
{
Environment.SetEnvironmentVariable(ConfigurationKeys.Logs.DisabledInstrumentations, nameof(LogInstrumentation.ILogger));
var settings = LogSettings.FromDefaultSources();
settings.EnabledInstrumentations.Should().BeEmpty();
}
[Theory]
[InlineData("true", true)]
[InlineData("false", false)]
@ -297,6 +319,8 @@ public class SettingsTests : IDisposable
{
Environment.SetEnvironmentVariable(ConfigurationKeys.Logs.Exporter, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Logs.IncludeFormattedMessage, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Logs.Instrumentations, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Logs.DisabledInstrumentations, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Metrics.Exporter, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Metrics.Instrumentations, null);
Environment.SetEnvironmentVariable(ConfigurationKeys.Metrics.DisabledInstrumentations, null);

View File

@ -22,6 +22,9 @@ internal class Integration
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("type")]
public string Type { get; set; }
[JsonPropertyName("method_replacements")]
public IList<MethodReplacement> MethodReplacements { get; set; }
}

View File

@ -50,6 +50,7 @@ foreach (var typeInfo in autoInstrumentationLib.GetTypes())
new Integration
{
Name = integration.IntegrationName,
Type = integration.IntegartionType,
MethodReplacements = new List<MethodReplacement> { integration.MethodReplacement }
});
}
@ -88,9 +89,10 @@ bool InheritsFrom(Type type, string baseType)
}
}
(string IntegrationName, MethodReplacement MethodReplacement) ConvertToIntegration(string wrapperTypeName, Attribute attribute)
(string IntegartionType, string IntegrationName, MethodReplacement MethodReplacement) ConvertToIntegration(string wrapperTypeName, Attribute attribute)
{
var integrationName = GetPropertyValue<string>("IntegrationName", attribute);
var integrationType = GetPropertyValue<object>("Type", attribute).ToString();
var methodReplacement = new MethodReplacement
{
@ -133,7 +135,7 @@ bool InheritsFrom(Type type, string baseType)
methodReplacement.Target.MaximumPath = int.Parse(maxVersion[2]);
}
return (integrationName, methodReplacement);
return (integrationType, integrationName, methodReplacement);
}
T GetPropertyValue<T>(string propertyName, Attribute attribute)