Skip to content

Commit 6d45601

Browse files
authored
Merge pull request #938 from aws-powertools/fix/logging-auto-dependency-addpowertoolslogger
chore: Add automatic registration of standard logging services and ILogger
2 parents 3cd704a + cf5f8bd commit 6d45601

File tree

3 files changed

+117
-19
lines changed

3 files changed

+117
-19
lines changed

docs/core/logging.md

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ When debugging in non-production environments, you can instruct Logger to log th
446446
parameter or via `POWERTOOLS_LOGGER_LOG_EVENT` environment variable.
447447

448448
!!! warning
449-
Log event is disabled by default to prevent sensitive info being logged.
449+
Log event is disabled by default to prevent sensitive info being logged.
450450

451451
=== "Function.cs"
452452

@@ -471,8 +471,8 @@ You can set a Correlation ID using `CorrelationIdPath` parameter by passing
471471
a [JSON Pointer expression](https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-03){target="_blank"}.
472472

473473
!!! Attention
474-
The JSON Pointer expression is `case sensitive`. In the bellow example `/headers/my_request_id_header` would work but
475-
`/Headers/my_request_id_header` would not find the element.
474+
The JSON Pointer expression is `case sensitive`. In the bellow example `/headers/my_request_id_header` would work but
475+
`/Headers/my_request_id_header` would not find the element.
476476

477477
=== "Function.cs"
478478

@@ -577,8 +577,8 @@ for known event sources, where either a request ID or X-Ray Trace ID are present
577577
## Appending additional keys
578578

579579
!!! info "Custom keys are persisted across warm invocations"
580-
Always set additional keys as part of your handler to ensure they have the latest value, or explicitly clear them with [
581-
`ClearState=true`](#clearing-all-state).
580+
Always set additional keys as part of your handler to ensure they have the latest value, or explicitly clear them with [
581+
`ClearState=true`](#clearing-all-state).
582582

583583
You can append your own keys to your existing logs via `AppendKey`. Typically this value would be passed into the
584584
function via the event. Appended keys are added to all subsequent log entries in the current execution from the point
@@ -683,7 +683,7 @@ It accepts any dictionary, and all keyword arguments will be added as part of th
683683
log statement.
684684

685685
!!! info
686-
Any keyword argument added using extra keys will not be persisted for subsequent messages.
686+
Any keyword argument added using extra keys will not be persisted for subsequent messages.
687687

688688
=== "Function.cs"
689689

@@ -782,8 +782,8 @@ You can dynamically set a percentage of your logs to **DEBUG** level via env var
782782
via `SamplingRate` parameter on attribute.
783783

784784
!!! info
785-
Configuration on environment variable is given precedence over sampling rate configuration on attribute, provided it's
786-
in valid value range.
785+
Configuration on environment variable is given precedence over sampling rate configuration on attribute, provided it's
786+
in valid value range.
787787

788788
=== "Sampling via attribute parameter"
789789

@@ -915,18 +915,18 @@ We support the following log levels:
915915
### Using AWS Lambda Advanced Logging Controls (ALC)
916916

917917
!!! question "When is it useful?"
918-
When you want to set a logging policy to drop informational or verbose logs for one or all AWS Lambda functions,
919-
regardless of runtime and logger used.
918+
When you want to set a logging policy to drop informational or verbose logs for one or all AWS Lambda functions,
919+
regardless of runtime and logger used.
920920

921921
With [AWS Lambda Advanced Logging Controls (ALC)](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html#monitoring-cloudwatchlogs-advanced)
922922
{target="_blank"}, you can enforce a minimum log level that Lambda will accept from your application code.
923923

924924
When enabled, you should keep `Logger` and ALC log level in sync to avoid data loss.
925925

926926
!!! warning "When using AWS Lambda Advanced Logging Controls (ALC)"
927-
- When Powertools Logger output is set to `PascalCase` **`Level`** property name will be replaced by **`LogLevel`** as
928-
a property name.
929-
- ALC takes precedence over **`POWERTOOLS_LOG_LEVEL`** and when setting it in code using **`[Logging(LogLevel = )]`**
927+
- When Powertools Logger output is set to `PascalCase` **`Level`** property name will be replaced by **`LogLevel`** as
928+
a property name.
929+
- ALC takes precedence over **`POWERTOOLS_LOG_LEVEL`** and when setting it in code using **`[Logging(LogLevel = )]`**
930930

931931
Here's a sequence diagram to demonstrate how ALC will drop both `Information` and `Debug` logs emitted from `Logger`,
932932
when ALC log level is stricter than `Logger`.
@@ -985,8 +985,8 @@ customize the serialization of Powertools Logger.
985985
This is useful when you want to change the casing of the property names or use a different naming convention.
986986

987987
!!! info
988-
If you want to preserve the original casing of the property names (keys), you can set the `DictionaryKeyPolicy` to
989-
`null`.
988+
If you want to preserve the original casing of the property names (keys), you can set the `DictionaryKeyPolicy` to
989+
`null`.
990990

991991
```csharp
992992
builder.Logging.AddPowertoolsLogger(options =>
@@ -999,6 +999,19 @@ builder.Logging.AddPowertoolsLogger(options =>
999999
});
10001000
```
10011001

1002+
!!! warning
1003+
When using `builder.Logging.AddPowertoolsLogger` method it will use any already configured logging providers (file loggers, database loggers, third-party providers).
1004+
1005+
If you want to use Powertools Logger as the only logging provider, you should call `builder.Logging.ClearProviders()` before adding Powertools Logger or the new method override
1006+
1007+
```csharp
1008+
builder.Logging.AddPowertoolsLogger(config =>
1009+
{
1010+
config.Service = "TestService";
1011+
config.LoggerOutputCase = LoggerOutputCase.PascalCase;
1012+
}, clearExistingProviders: true);
1013+
```
1014+
10021015
### Custom Log formatter (Bring Your Own Formatter)
10031016

10041017
You can customize the structure (keys and values) of your log entries by implementing a custom log formatter and

libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ internal static PowertoolsLoggerConfiguration GetCurrentConfiguration()
5656
/// Adds the Powertools logger to the logging builder with default configuration.
5757
/// </summary>
5858
/// <param name="builder">The logging builder to configure.</param>
59+
/// <param name="clearExistingProviders">Opt-in to clear providers for Powertools-only output</param>
5960
/// <returns>The logging builder for further configuration.</returns>
6061
/// <remarks>
6162
/// This method registers the Powertools logger with default settings. The logger will output
@@ -78,14 +79,24 @@ internal static PowertoolsLoggerConfiguration GetCurrentConfiguration()
7879
/// </code>
7980
/// </example>
8081
public static ILoggingBuilder AddPowertoolsLogger(
81-
this ILoggingBuilder builder)
82+
this ILoggingBuilder builder,
83+
bool clearExistingProviders = false)
8284
{
85+
if (clearExistingProviders)
86+
{
87+
builder.ClearProviders();
88+
}
89+
8390
builder.AddConfiguration();
84-
91+
8592
builder.Services.TryAddSingleton<IPowertoolsEnvironment, PowertoolsEnvironment>();
8693
builder.Services.TryAddSingleton<IPowertoolsConfigurations>(sp =>
8794
new PowertoolsConfigurations(sp.GetRequiredService<IPowertoolsEnvironment>()));
8895

96+
// automatically register ILogger
97+
builder.Services.TryAddSingleton<ILogger>(provider =>
98+
provider.GetRequiredService<ILoggerFactory>().CreatePowertoolsLogger());
99+
89100
builder.Services.TryAddEnumerable(
90101
ServiceDescriptor.Singleton<ILoggerProvider, PowertoolsLoggerProvider>(provider =>
91102
{
@@ -111,6 +122,7 @@ public static ILoggingBuilder AddPowertoolsLogger(
111122
/// </summary>
112123
/// <param name="builder">The logging builder to configure.</param>
113124
/// <param name="configure"></param>
125+
/// <param name="clearExistingProviders">Opt-in to clear providers for Powertools-only output</param>
114126
/// <returns>The logging builder for further configuration.</returns>
115127
/// <remarks>
116128
/// This method registers the Powertools logger with default settings. The logger will output
@@ -155,10 +167,11 @@ public static ILoggingBuilder AddPowertoolsLogger(
155167
/// </example>
156168
public static ILoggingBuilder AddPowertoolsLogger(
157169
this ILoggingBuilder builder,
158-
Action<PowertoolsLoggerConfiguration> configure)
170+
Action<PowertoolsLoggerConfiguration> configure,
171+
bool clearExistingProviders = false)
159172
{
160173
// Add configuration
161-
builder.AddPowertoolsLogger();
174+
builder.AddPowertoolsLogger(clearExistingProviders);
162175

163176
// Create initial configuration
164177
var options = new PowertoolsLoggerConfiguration();
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Linq;
3+
using AWS.Lambda.Powertools.Logging.Internal;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.Logging;
6+
using Xunit;
7+
8+
namespace AWS.Lambda.Powertools.Logging.Tests;
9+
10+
public class PowertoolsLoggerExtensionsTests
11+
{
12+
[Fact]
13+
public void AddPowertoolsLogger_WithClearExistingProviders_False_KeepsExistingProviders()
14+
{
15+
// Arrange
16+
var serviceCollection = new ServiceCollection();
17+
serviceCollection.AddLogging(builder =>
18+
{
19+
// Add a mock existing provider first
20+
builder.Services.AddSingleton<ILoggerProvider, MockLoggerProvider>();
21+
22+
// Act
23+
builder.AddPowertoolsLogger(clearExistingProviders: false);
24+
});
25+
26+
var serviceProvider = serviceCollection.BuildServiceProvider();
27+
var loggerProviders = serviceProvider.GetServices<ILoggerProvider>();
28+
29+
// Assert
30+
var collection = loggerProviders as ILoggerProvider[] ?? loggerProviders.ToArray();
31+
Assert.Contains(collection, p => p is MockLoggerProvider);
32+
Assert.Contains(collection, p => p is PowertoolsLoggerProvider);
33+
Assert.True(collection.Count() >= 2); // Should have both providers
34+
}
35+
36+
[Fact]
37+
public void AddPowertoolsLogger_WithClearExistingProviders_True_RemovesExistingProviders()
38+
{
39+
// Arrange
40+
var serviceCollection = new ServiceCollection();
41+
serviceCollection.AddLogging(builder =>
42+
{
43+
// Add a mock existing provider first
44+
builder.Services.AddSingleton<ILoggerProvider, MockLoggerProvider>();
45+
46+
// Act
47+
builder.AddPowertoolsLogger(clearExistingProviders: true);
48+
});
49+
50+
var serviceProvider = serviceCollection.BuildServiceProvider();
51+
var loggerProviders = serviceProvider.GetServices<ILoggerProvider>();
52+
53+
// Assert
54+
var collection = loggerProviders as ILoggerProvider[] ?? loggerProviders.ToArray();
55+
Assert.DoesNotContain(collection, p => p is MockLoggerProvider);
56+
Assert.Contains(collection, p => p is PowertoolsLoggerProvider);
57+
Assert.Single(collection); // Should only have Powertools provider
58+
}
59+
60+
private class MockLoggerProvider : ILoggerProvider
61+
{
62+
public ILogger CreateLogger(string categoryName) => new MockLogger();
63+
public void Dispose() { }
64+
}
65+
66+
private class MockLogger : ILogger
67+
{
68+
public IDisposable BeginScope<TState>(TState state) => null;
69+
public bool IsEnabled(LogLevel logLevel) => true;
70+
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { }
71+
}
72+
}

0 commit comments

Comments
 (0)