Skip to content

Commit

Permalink
[AzureMonitor] add Log Scopes example to Readme (Azure#44749)
Browse files Browse the repository at this point in the history
* add Log Scopes example to Readme

* pr feedback

* add AzureMonitor to example code

* pr feedback

* Update sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/README.md

Co-authored-by: Cijo Thomas <[email protected]>

* add another test

* pr feedback

* pr feedback

* Update sdk/monitor/Azure.Monitor.OpenTelemetry.AspNetCore/README.md

Co-authored-by: Rajkumar Rangaraj <[email protected]>

* fix

* pr feedback

---------

Co-authored-by: Cijo Thomas <[email protected]>
Co-authored-by: Rajkumar Rangaraj <[email protected]>
  • Loading branch information
3 people authored and tejasm-microsoft committed Jul 22, 2024
1 parent 04b599f commit 100fb1e
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 7 deletions.
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 @@ -301,6 +301,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](https://learn.microsoft.com/dotnet/core/extensions/logging#log-scopes) allow you to add additional properties to the logs generated by your application.
Although the Azure Monitor Distro 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
builder.Services.Configure<OpenTelemetryLoggerOptions>((loggingOptions) =>
{
loggingOptions.IncludeScopes = true;
});
```

When using `ILogger` scopes, use a `List<KeyValuePair<string, object?>>` or `IReadOnlyList<KeyValue<string, object?>>` as the state for best performance.
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.
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 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
37 changes: 37 additions & 0 deletions sdk/monitor/Azure.Monitor.OpenTelemetry.Exporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,43 @@ 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](https://learn.microsoft.com/dotnet/core/extensions/logging#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 =>
{
builder.AddOpenTelemetry(options =>
{
options.AddAzureMonitorLogExporter(o => o.ConnectionString = "InstrumentationKey=00000000-0000-0000-0000-000000000000");
options.IncludeScopes = true;
});
});
```

When using `ILogger` scopes, use a `List<KeyValuePair<string, object?>>` or `IReadOnlyList<KeyValue<string, object?>>` as the state for best performance.
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.
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: 1,
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: 1,
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> { { "EventId", "1" }, { "name", "World" }, { "CategoryName", logCategoryName } },
expectedMessageProperties: new Dictionary<string, string> { { "EventId", "1" }, { "name", "World" }, { "CategoryName", logCategoryName }, { "scopeKey1", "scopeValue1" } },
expectedSpanId: null,
expectedTraceId: null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,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);
}

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

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

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

0 comments on commit 100fb1e

Please sign in to comment.