Skip to content

Commit 40f136a

Browse files
committed
docs(logger): clarify child loggers side effects; cleanup over-used banners
1 parent f93fd77 commit 40f136a

File tree

2 files changed

+29
-31
lines changed

2 files changed

+29
-31
lines changed

.markdownlint.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ MD038: false
182182
MD039: true
183183

184184
# MD040/fenced-code-language - Fenced code blocks should have a language specified
185-
MD040: true
185+
MD040: false
186186

187187
# MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading
188188
MD041:

docs/core/logger.md

+28-30
Original file line numberDiff line numberDiff line change
@@ -475,9 +475,9 @@ You can use any of the following built-in JMESPath expressions as part of [injec
475475

476476
### Reusing Logger across your code
477477

478-
Similar to [Tracer](./tracer.md#reusing-tracer-across-your-code){target="_blank"}, a new instance that uses the same `service` name - env var or explicit parameter - will reuse a previous Logger instance. Just like `logging.getLogger("logger_name")` would in the standard library if called with the same logger name.
478+
Similar to [Tracer](./tracer.md#reusing-tracer-across-your-code){target="_blank"}, a new instance that uses the same `service` name will reuse a previous Logger instance.
479479

480-
Notice in the CloudWatch Logs output how `payment_id` appeared as expected when logging in `collect.py`.
480+
Notice in the CloudWatch Logs output how `payment_id` appears as expected when logging in `collect.py`.
481481

482482
=== "logger_reuse.py"
483483

@@ -497,17 +497,6 @@ Notice in the CloudWatch Logs output how `payment_id` appeared as expected when
497497
--8<-- "examples/logger/src/logger_reuse_output.json"
498498
```
499499

500-
???+ note "Note: About Child Loggers"
501-
Coming from standard library, you might be used to use `logging.getLogger(__name__)`. This will create a new instance of a Logger with a different name.
502-
503-
In Powertools, you can have the same effect by using `child=True` parameter: `Logger(child=True)`. This creates a new Logger instance named after `service.<module>`. All state changes will be propagated bi-directionally between Child and Parent.
504-
505-
For that reason, there could be side effects depending on the order the Child Logger is instantiated, because Child Loggers don't have a handler.
506-
507-
For example, if you instantiated a Child Logger and immediately used `logger.append_keys/remove_keys/set_correlation_id` to update logging state, this might fail if the Parent Logger wasn't instantiated.
508-
509-
In this scenario, you can either ensure any calls manipulating state are only called when a Parent Logger is instantiated (example above), or refrain from using `child=True` parameter altogether.
510-
511500
### Sampling debug logs
512501

513502
Use sampling when you want to dynamically change your log level to **DEBUG** based on a **percentage of your concurrent/cold start invocations**.
@@ -582,31 +571,40 @@ You can use import and use them as any other Logger formatter via `logger_format
582571

583572
### Migrating from other Loggers
584573

585-
If you're migrating from other Loggers, there are few key points to be aware of: [Service parameter](#the-service-parameter), [Inheriting Loggers](#inheriting-loggers), [Overriding Log records](#overriding-log-records), and [Logging exceptions](#logging-exceptions).
574+
If you're migrating from other Loggers, there are few key points to be aware of: [Service parameter](#the-service-parameter), [Child Loggers](#child-loggers), [Overriding Log records](#overriding-log-records), and [Logging exceptions](#logging-exceptions).
586575

587576
#### The service parameter
588577

589578
Service is what defines the Logger name, including what the Lambda function is responsible for, or part of (e.g payment service).
590579

591580
For Logger, the `service` is the logging key customers can use to search log operations for one or more functions - For example, **search for all errors, or messages like X, where service is payment**.
592581

593-
#### Inheriting Loggers
582+
#### Child Loggers
594583

595-
??? tip "Tip: Prefer [Logger Reuse feature](#reusing-logger-across-your-code) over inheritance unless strictly necessary, [see caveats.](#reusing-logger-across-your-code)"
584+
<center>
585+
```mermaid
586+
stateDiagram-v2
587+
direction LR
588+
Parent: Logger()
589+
Child: Logger(child=True)
590+
591+
Parent --> Child: bi-directional updates
596592
597-
> Python Logging hierarchy happens via the dot notation: `service`, `service.child`, `service.child_2`
593+
Note right of Child
594+
Both have the same service
595+
end note
598596
599-
For inheritance, Logger uses a `child=True` parameter along with `service` being the same value across Loggers.
597+
```
598+
</center>
600599

601-
For child Loggers, we introspect the name of your module where `Logger(child=True, service="name")` is called, and we name your Logger as **{service}.{filename}**.
600+
For inheritance, Logger uses `child` parameter to ensure we don't compete with its parents config. We name child Loggers following Python's convention: _`{service}`.`{filename}`_.
602601

603-
???+ danger
604-
A common issue when migrating from other Loggers is that `service` might be defined in the parent Logger (no child param), and not defined in the child Logger:
602+
Changes are bidirectional between parents and loggers. That is, appending a key in a child or parent will ensure both have them. This means, having the same `service` name is important when instantiating them.
605603

606-
=== "logging_inheritance_bad.py"
604+
=== "logging_inheritance_good.py"
607605

608606
```python hl_lines="1 9"
609-
--8<-- "examples/logger/src/logging_inheritance_bad.py"
607+
--8<-- "examples/logger/src/logging_inheritance_good.py"
610608
```
611609

612610
=== "logging_inheritance_module.py"
@@ -615,17 +613,17 @@ For child Loggers, we introspect the name of your module where `Logger(child=Tru
615613
--8<-- "examples/logger/src/logging_inheritance_module.py"
616614
```
617615

618-
In this case, Logger will register a Logger named `payment`, and a Logger named `service_undefined`. The latter isn't inheriting from the parent, and will have no handler, resulting in no message being logged to standard output.
619-
620-
???+ tip
621-
This can be fixed by either ensuring both has the `service` value as `payment`, or simply use the environment variable `POWERTOOLS_SERVICE_NAME` to ensure service value will be the same across all Loggers when not explicitly set.
616+
There are two important side effects when using child loggers:
622617

623-
Do this instead:
618+
1. **Service name mismatch**. Logging messages will be dropped as child loggers don't have logging handlers.
619+
* Solution: use `POWERTOOLS_SERVICE_NAME` env var. Alternatively, use the same service explicit value.
620+
2. **Changing state before a parent instantiate**. Using `logger.append_keys` or `logger.remove_keys` without a parent Logger will lead to `OrphanedChildLoggerError` exception.
621+
* Solution: always initialize parent Loggers first. Alternatively, move calls to `append_keys`/`remove_keys` from the child at a later stage.
624622

625-
=== "logging_inheritance_good.py"
623+
=== "logging_inheritance_bad.py"
626624

627625
```python hl_lines="1 9"
628-
--8<-- "examples/logger/src/logging_inheritance_good.py"
626+
--8<-- "examples/logger/src/logging_inheritance_bad.py"
629627
```
630628

631629
=== "logging_inheritance_module.py"

0 commit comments

Comments
 (0)