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

[AzureMonitor] add Log Scopes example to Readme #44749

Merged
merged 14 commits into from
Jul 17, 2024
33 changes: 33 additions & 0 deletions sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,39 @@ The Azure Monitor Distro is a distribution package that facilitates users in sen

Refer to [`Program.cs`](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/tests/Azure.Monitor.OpenTelemetry.AspNetCore.Demo/Program.cs) for a complete demo.

### Log Scopes

Log scopes allow you to add additional properties to the logs generated by your application.
Although the Azure Monitor Exporter does support scopes, this feature is off by default in OpenTelemetry.
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
To leverage log scopes, you must explicitly enable them.

To include the scope with your logs, set `OpenTelemetryLoggerOptions.IncludeScopes` to `true` in your application's configuration:
```csharp
builder.Services.AddOpenTelemetry().UseAzureMonitor();
builder.Services.Configure<OpenTelemetryLoggerOptions>(options =>
{
options.IncludeScopes = true;
});
```

Once enabled, you can define a scope using a dictionary or any enumerable of KeyValuePair<string, object>.
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
All logs written within the context of the scope will include the specified information.
Azure Monitor will add these scope values to the Log's CustomProperties.
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
```csharp
List<KeyValuePair<string, object>> scope =
[
new("scopeKey", "scopeValue")
];

using (logger.BeginScope(scope))
{
logger.LogInformation("Example message.");
}
```

In scenarios involving multiple scopes or a single scope with multiple key-value pairs, if duplicate keys are present, only the first occurrence of the key-value pair from the outermost scope will be recorded.
However, when the same key is utilized both within a logging scope and directly in the log message template, the value specified in the log message template will take precedence.

## Troubleshooting

The Azure Monitor Distro uses EventSource for its own internal logging. The logs are available to any EventListener by opting into the source named "OpenTelemetry-AzureMonitor-Exporter".
Expand Down
38 changes: 36 additions & 2 deletions sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# Azure Monitor Exporter client library for .NET



The [OpenTelemetry .NET](https://github.com/open-telemetry/opentelemetry-dotnet) exporters which send [telemetry data](https://docs.microsoft.com/azure/azure-monitor/app/data-model) to [Azure Monitor](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) following the [OpenTelemetry Specification](https://github.com/open-telemetry/opentelemetry-specification).

## Getting started
Expand Down Expand Up @@ -135,6 +133,42 @@ For more information on the OpenTelemetry project, please review the [OpenTeleme

Refer to [`Program.cs`](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/tests/Azure.Monitor.OpenTelemetry.Exporter.Demo/Program.cs) for a complete demo.

### Log Scopes

Log scopes allow you to add additional properties to the logs generated by your application.
Although the Azure Monitor Exporter does support scopes, this feature is off by default in OpenTelemetry.
To leverage log scopes, you must explicitly enable them.

To include the scope with your logs, set `OpenTelemetryLoggerOptions.IncludeScopes` to `true` in your application's configuration:
```csharp
var loggerFactory = LoggerFactory.Create(builder =>
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
{
builder.AddOpenTelemetry(options =>
{
options.AddAzureMonitorLogExporter(o => o.ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000");
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
options.IncludeScopes = true;
});
});
```

Once enabled, you can define a scope using a dictionary or any enumerable of KeyValuePair<string, object>.
All logs written within the context of the scope will include the specified information.
Azure Monitor will add these scope values to the Log's CustomProperties.
```csharp
List<KeyValuePair<string, object>> scope =
[
new("scopeKey", "scopeValue")
];

using (logger.BeginScope(scope))
{
logger.LogInformation("Example message.");
}
```

In scenarios involving multiple scopes or a single scope with multiple key-value pairs, if duplicate keys are present, only the first occurrence of the key-value pair from the outermost scope will be recorded.
cijothomas marked this conversation as resolved.
Show resolved Hide resolved
However, when the same key is utilized both within a logging scope and directly in the log statement, the value specified in the log message template will take precedence.

## Troubleshooting

The Azure Monitor exporter uses EventSource for its own internal logging. The exporter logs are available to any EventListener by opting into the source named "OpenTelemetry-AzureMonitor-Exporter".
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,36 @@ public void VerifyLog(LogLevel logLevel, string expectedSeverityLevel)
.AddFilter<OpenTelemetryLoggerProvider>(logCategoryName, logLevel)
.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddAttributes(testResourceAttributes));
options.AddAzureMonitorLogExporterForTest(out telemetryItems);
});
});

// ACT
var logger = loggerFactory.CreateLogger(logCategoryName);
logger.Log(
logLevel: logLevel,
eventId: 0,
exception: null,
message: "Hello {name}.",
args: new object[] { "World" });

List<KeyValuePair<string, object>> scope1 = new()
{
new("scopeKey1", "scopeValue1"),
new("scopeKey1", "scopeValue2")
};

List<KeyValuePair<string, object>> scope2 = new()
{
new("scopeKey1", "scopeValue3")
};

using (logger.BeginScope(scope1))
using (logger.BeginScope(scope2))
{
logger.Log(
logLevel: logLevel,
eventId: 0,
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
exception: null,
message: "Hello {name}.",
args: new object[] { "World" });
}

// CLEANUP
loggerFactory.Dispose();
Expand All @@ -83,7 +100,7 @@ public void VerifyLog(LogLevel logLevel, string expectedSeverityLevel)
telemetryItem: telemetryItem!,
expectedSeverityLevel: expectedSeverityLevel,
expectedMessage: "Hello {name}.",
expectedMessageProperties: new Dictionary<string, string> { { "name", "World" }},
expectedMessageProperties: new Dictionary<string, string> { { "name", "World" }, { "scopeKey1", "scopeValue1" } },
expectedSpanId: null,
expectedTraceId: null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,47 @@ public void DuplicateKeysInLogRecordAttributesAndLogScope()
Assert.Equal(expectedAttributeValue, actualAttributeValue);
}

[Fact]
public void DuplicateKeysInLogRecordAttributesAndLogScope2()
{
// Arrange.
var logRecords = new List<LogRecord>(1);
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddOpenTelemetry(options =>
{
options.IncludeScopes = true;
options.AddInMemoryExporter(logRecords);
});
});

var logger = loggerFactory.CreateLogger("Some category");

const string expectedScopeKey = "Some scope key";
const string expectedScopeValue = "Some scope value";
const string duplicateScopeValue = "Some duplicate scope value";
const string duplicateScopeValue2 = "Another duplicate scope value";

// Act.
using (logger.BeginScope(new List<KeyValuePair<string, object>>
{
new KeyValuePair<string, object>(expectedScopeKey, expectedScopeValue),
new KeyValuePair<string, object>(expectedScopeKey, duplicateScopeValue),
}))
{
logger.LogInformation($"Some log information message. {{{expectedScopeKey}}}.", duplicateScopeValue2);
TimothyMothra marked this conversation as resolved.
Show resolved Hide resolved
}

// Assert.
var logRecord = logRecords.Single();
var properties = new ChangeTrackingDictionary<string, string>();
LogsHelper.GetMessageAndSetProperties(logRecords[0], properties);

Assert.Single(properties);
Assert.True(properties.TryGetValue(expectedScopeKey, out string actualScopeValue));
Assert.Equal(duplicateScopeValue2, actualScopeValue);
}

private class CustomObject
{
public override string ToString()
Expand Down