structlog-25.5.0/.git_archival.txt0000644000000000000000000000014615077624341014044 0ustar00node: $Format:%H$ node-date: $Format:%cI$ describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ structlog-25.5.0/.gitattributes0000644000000000000000000000021715077624341013463 0ustar00# Force LF line endings for text files * text=auto eol=lf # Needed for hatch-vcs / setuptools-scm-git-archive .git_archival.txt export-subst structlog-25.5.0/.pre-commit-config.yaml0000644000000000000000000000170015077624341015047 0ustar00--- ci: autoupdate_schedule: monthly repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.14.2 hooks: - id: ruff-check args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/econchick/interrogate rev: 1.7.0 hooks: - id: interrogate args: [tests] - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell args: [-L, alog, -L, abl] - repo: https://github.com/abravalheri/validate-pyproject rev: v0.24.1 hooks: - id: validate-pyproject # Optional extra validations from SchemaStore: additional_dependencies: ["validate-pyproject-schema-store[all]"] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer exclude: docs/_static - id: check-toml - id: check-yaml structlog-25.5.0/.python-version-default0000644000000000000000000000000515077624341015212 0ustar003.13 structlog-25.5.0/.readthedocs.yaml0000644000000000000000000000075515077624341014026 0ustar00--- version: 2 build: os: ubuntu-lts-latest tools: # Keep version in sync with tox.ini/docs and ci.yml/docs. python: "3.13" jobs: create_environment: # Need the tags to calculate the version (sometimes). - git fetch --tags - asdf plugin add uv - asdf install uv latest - asdf global uv latest build: html: - uvx --with tox-uv tox run -e docs-sponsors - uvx --with tox-uv tox run -e docs-build -- $READTHEDOCS_OUTPUT structlog-25.5.0/CHANGELOG.md0000644000000000000000000014520715077624341012412 0ustar00# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/) and this project adheres to [Calendar Versioning](https://calver.org/). The **first number** of the version is the year. The **second number** is incremented with each release, starting at 1 for each year. The **third number** is for emergencies when we need to start branches for older releases. You can find our backwards-compatibility policy [here](https://github.com/hynek/structlog/blob/main/.github/SECURITY.md). ## [25.5.0](https://github.com/hynek/structlog/compare/25.4.0...25.5.0) - 2025-10-27 ### Deprecated - `structlog.dev.ConsoleRenderer()`'s *pad_event* argument has been renamed to *pad_event_to* to differentiate it from the boolean *pad_level* argument. *pad_event* is now deprecated. ### Added - Added `structlog.dev.ConsoleRenderer.get_active()` that returns the currently active `structlog.dev.ConsoleRenderer()`. [#749](https://github.com/hynek/structlog/pull/749) - `structlog.dev.ConsoleRenderer()` now supports setting the `exception_formatter` attribute. You can now disable the pretty-printing of exceptions by setting it to `structlog.dev.plain_traceback`: ```python cr = structlog.dev.ConsoleRenderer.get_active() cr.exception_formatter = structlog.dev.plain_traceback ``` Same goes for *sort_keys*, *columns*, *colors*, *force_colors*, *level_styles*, *pad_event_to*, *event_key*, *timestamp_key*, and *repr_native_str*. [#749](https://github.com/hynek/structlog/pull/749) [#756](https://github.com/hynek/structlog/pull/756) [#757](https://github.com/hynek/structlog/pull/757) [#759](https://github.com/hynek/structlog/pull/759) - Added `structlog.dev.ConsoleRenderer.get_default_column_styles()` for reuse the default column styles. [#741](https://github.com/hynek/structlog/pull/741) - `structlog.testing.capture_logs()` now optionally accepts *processors* to apply before capture. [#728](https://github.com/hynek/structlog/pull/728) - `structlog.dev.RichTracebackFormatter` now exposes the upstream *code_width* parameter. Default *width* is now `None` for full terminal width. Full terminal width is now handled by Rich itself, bringing support for reflow and `COLUMN` environment variable. Passing `-1` for *width* is now deprecated and automatically replaced by `None`. [#717](https://github.com/hynek/structlog/pull/717) - Native loggers now allow the passing of a dictionary for dictionary-based interpolation `log.info("hello %(name)s!", {"name": "world"})`. [#748](https://github.com/hynek/structlog/pull/748) - On Python 3.11+, `structlog.processors.CallsiteParameterAdder` now supports `CallsiteParameter.QUAL_NAME` that adds the qualified name of the callsite, including scope and class names. This is only available for *structlog*-originated events since the standard library has no equivalent. - `structlog.stdlib.LoggerFactory` now supports the *stacklevel* parameter. [#763](https://github.com/hynek/structlog/pull/763) ### Changed - `structlog.dev.rich_traceback()` now throws a more helpful error when Rich is missing. [#735](https://github.com/hynek/structlog/pull/735) ### Fixed - `structlog.processors.MaybeTimeStamper` now respects the *key* argument when determining whether to overwrite the timestamp field. [#747](https://github.com/hynek/structlog/pull/747) - `structlog.tracebacks.extract()` no longer raises a `RecursionError` when the cause chain of an exception contains itself. [#739](https://github.com/hynek/structlog/pull/739) - Default config now respects `FORCE_COLOR` on Windows. [#758](https://github.com/hynek/structlog/pull/758) ## [25.4.0](https://github.com/hynek/structlog/compare/25.3.0...25.4.0) - 2025-06-02 ### Added - Support for Python 3.14 and Python 3.13.4. Python 3.14 has an backwards-incompatible change to `logging.Logger.isEnabledFor()` (it now always returns False if a log entry is in flight) that has been backported to 3.13.4 (expected on 2025-06-03). It mainly affects `structlog.stdlib.filter_by_level()`. [#723](https://github.com/hynek/structlog/pull/723) - `structlog.tracebacks` now handles [exception groups](https://docs.python.org/3/library/exceptions.html#exception-groups). `structlog.tracebacks.Stack` has two new fields, `is_group: bool` and `exceptions: list[Trace]`. This works similarly to what Rich v14.0.0 does. [#720](https://github.com/hynek/structlog/pull/720) ### Fixed - `structlog.processors.ExceptionPrettyPrinter` now respects the *exception_formatter* arguments instead of always using the default formatter. [#724](https://github.com/hynek/structlog/pull/724) ## [25.3.0](https://github.com/hynek/structlog/compare/25.2.0...25.3.0) - 2025-04-25 ### Fixed - `structlog.processors.TimeStamper` now again uses timestamps using UTC for custom format strings when `utc=True`. [#713](https://github.com/hynek/structlog/pull/713) ## [25.2.0](https://github.com/hynek/structlog/compare/25.1.0...25.2.0) - 2025-03-11 ### Added - `structlog.tracebacks.Stack` now includes an `exc_notes` field reflecting the notes attached to the exception. [#684](https://github.com/hynek/structlog/pull/684) ### Changed - `structlog.stdlib.BoundLogger`'s binding-related methods now also return `Self`. [#694](https://github.com/hynek/structlog/pull/694) - `structlog.processors.TimeStamper` now produces internally timezone-aware `datetime` objects. Default output hasn't changed, but you can now use `%z` in your *fmt* string. [#709](https://github.com/hynek/structlog/pull/709) ### Fixed - Expose `structlog.dev.RichTracebackFormatter` for imports. [#699](https://github.com/hynek/structlog/pull/699) - Expose `structlog.processors.LogfmtRenderer` for imports. [#701](https://github.com/hynek/structlog/pull/701) ## [25.1.0](https://github.com/hynek/structlog/compare/24.4.0...25.1.0) - 2025-01-16 ### Added - Add `structlog.stdlib.render_to_log_args_and_kwargs` processor. Same as `structlog.stdlib.render_to_log_kwargs`, but also allows to pass positional arguments to `logging`. With it, you do not need to add `structlog.stdlib.PositionalArgumentsFormatter` processor to format positional arguments from *structlog* loggers. [#668](https://github.com/hynek/structlog/pull/668) - Native loggers now have `is_enabled_for()` and `get_effective_level()` methods that mirror the behavior of the standard library's `logging.Logger.isEnabledFor()` and `logging.Logger.getEffectiveLevel()`. [#689](https://github.com/hynek/structlog/pull/689) ### Changed - `structlog.typing.BindableLogger` protocol now returns `Self` instead of `BindableLogger`. This adds a dependency on [*typing-extensions*](https://pypi.org/project/typing-extensions/) for Pythons older than 3.11. [#642](https://github.com/hynek/structlog/pull/642) [#659](https://github.com/hynek/structlog/pull/659) - `structlog.dev.ConsoleRenderer` will quote string value with special characters. [#647](https://github.com/hynek/structlog/pull/647) - `structlog.stdlib.recreate_defaults()` now also adds `structlog.stdlib.PositionalArgumentsFormatter`. In default native mode, this is done by the loggers at the edge. - `structlog.make_filtering_bound_logger()` now also accepts a string for *min_level*. ### Fixed - Fix handling calls to `{logger}.exception()` outside of exception blocks. Depending on the structlog configuration, this either resulted in an event dict key `exception: "MISSING"` or lead to an error. Now, an invalid or missing `exc_info` will just be ignored. This means, that calling `{logger}.exception()` outside of an exception block is basically the same as calling `{logger}.error()`. [#634](https://github.com/hynek/structlog/issues/634) [#680](https://github.com/hynek/structlog/pull/680) - Instantiating `structlog.dev.ConsoleRenderer` does not mutate the passed *styles* dict anymore. [#669](https://github.com/hynek/structlog/pull/669) - The native `FilteringBoundLogger.fatal()` method now maps to the critical level, as it does in the standard library. Note that the level is discouraged to use there, so we recommend to stick to `error()` or `critical()`. [#677](https://github.com/hynek/structlog/pull/677) - `structlog.tracebacks.ExceptionDictTransformer` now actually accepts `None` for `locals_max_length` and `locals_max_string`. [#675](https://github.com/hynek/structlog/pull/675) ## [24.4.0](https://github.com/hynek/structlog/compare/24.3.0...24.4.0) - 2024-07-17 ### Changed No code changes since 24.3.0 ## [24.3.0](https://github.com/hynek/structlog/compare/24.2.0...24.3.0) - 2024-07-17 ### Added - Restore feature parity between `structlog.traceback.ExceptionDictTransformer` and Rich's traceback extractor: - When displaying locals, use Rich for formatting if it is available. - When displaying locals, call `repr()` on strings, too (improves handling of `SecretStr` implementations). - Add `locals_max_length` config option - Add `locals_hide_sunder` config option - Add `locals_hide_dunder` config option - Add `suppress` config option [#627](https://github.com/hynek/structlog/pull/627) ### Changed - `structlog.testing.capture_logs()` now maps the `exception` log level to `error` (as it's elsewhere). [#628](https://github.com/hynek/structlog/pull/628) ## [24.2.0](https://github.com/hynek/structlog/compare/24.1.0...24.2.0) - 2024-05-27 ### Added - It is now possible to disable log level-padding in `structlog.dev.LogLevelColumnFormatter` and `structlog.dev.ConsoleRenderer`. [#599](https://github.com/hynek/structlog/pull/599) - The `structlog.processors.CallsiteParameterAdder` can now be pickled. [#603](https://github.com/hynek/structlog/pull/603) - `structlog.processors.CallsiteParameterAdder` now also works with `structlog.stdlib.BoundLogger`'s non-standard async methods (`ainfo()`, and so forth) [#618](https://github.com/hynek/structlog/pull/618) ### Changed - `structlog.processors.LogfmtRenderer` now escapes newlines. [#592](https://github.com/hynek/structlog/pull/592) - `structlog.processors.LogfmtRenderer` now escapes backslashes and double quotes. [#594](https://github.com/hynek/structlog/pull/594) - `structlog.processors.CallsiteParameterAdder` has been optimized to be about 2x faster. [#606](https://github.com/hynek/structlog/pull/606) ### Fixed - `structlog.stdlib.render_to_log_kwargs` now correctly passes stacklevel as a kwarg to stdlib logging. [#619](https://github.com/hynek/structlog/pull/620) ## [24.1.0](https://github.com/hynek/structlog/compare/23.3.0...24.1.0) - 2024-01-08 ### Fixed - The lazy logger proxy returned by `structlog.get_logger()` now returns its initial values when asked for context. When asked for context before binding for the first time, it returned an empty dictionary in 23.3.0. - The displayed level name when using `structlog.stdlib.BoundLogger.exception()` is `"error"` instead of `"exception"`. Fixes regression in 23.3.0. [#584](https://github.com/hynek/structlog/issues/584) - Don't ignore the `width` argument of `RichTracebackFormatter`. [#587](https://github.com/hynek/structlog/issues/587) ## [23.3.0](https://github.com/hynek/structlog/compare/23.2.0...23.3.0) - 2023-12-29 ### Added - The colorful development logger is now even more configurable! Choose freely your colors and the order of the key-value pairs! Implement your own formatters for certain keys! Implementing the output on top of the new columns API has changed the default very slightly, but shouldn't be noticeable. [#577](https://github.com/hynek/structlog/issues/577) - Async log methods (those starting with an `a`) now also support the collection of callsite information using `structlog.processors.CallsiteParameterAdder`. [#565](https://github.com/hynek/structlog/issues/565) ### Changed - `structlog.stdlib.recreate_defaults()` now also adds `structlog.stdlib.add_logger_name` to the processors. Check out the [updated screenshot](https://raw.githubusercontent.com/hynek/structlog/main/docs/_static/console_renderer.png)! ### Fixed - The return value from `get_logger()` (a `BoundLoggerLazyProxy`) now passes `isinstance`-checks against `structlog.typing.BindableLogger` on Python 3.12. [#561](https://github.com/hynek/structlog/issues/561) - `structlog.threadlocal.tmp_bind()` now also works with `BoundLoggerLazyProxy` (in other words: before anything is bound to a bound logger). - stdlib: `ProcessorFormatter` can now be told to not render the log record message using `getMessage` and just `str(record.msg)` instead. [#550](https://github.com/hynek/structlog/issues/550) - stdlib: `structlog.stdlib.BoundLogger.exception()`'s handling of`LogRecord.exc_info` is now set consistent with `logging`. [#571](https://github.com/hynek/structlog/issues/571) [#572](https://github.com/hynek/structlog/issues/572) ## [23.2.0](https://github.com/hynek/structlog/compare/23.1.0...23.2.0) - 2023-10-09 ### Removed - Support for Python 3.7. ### Added - Official support for Python 3.12. [#515](https://github.com/hynek/structlog/issues/515) - `structlog.processors.MaybeTimeStamper` that only adds a timestamp if there isn't one already. [#81](https://github.com/hynek/structlog/issues/81) - `structlog.dev.ConsoleRenderer` now supports renamed timestamp keys using the *timestamp_key* parameter. [#541](https://github.com/hynek/structlog/issues/541) - `structlog.dev.RichTracebackFormatter` that allows to configure the traceback formatting. [#542](https://github.com/hynek/structlog/issues/542) ### Fixed - `FilteringBoundLogger.exception()` and `FilteringBoundLogger.aexception()` now support positional argument formatting like the rest of the methods. [#531](https://github.com/hynek/structlog/issues/531) - `structlog.processors.format_exc_info()` and `structlog.dev.ConsoleRenderer` do not crash anymore when told to format a non-existent exception. [#533](https://github.com/hynek/structlog/issues/533) ## [23.1.0](https://github.com/hynek/structlog/compare/22.3.0...23.1.0) - 2023-04-06 ### Added - `structlog.stdlib.BoundLogger` now has, analogously to our native logger, a full set of async log methods prefixed with an `a`: `await log.ainfo("event!")` [#502](https://github.com/hynek/structlog/issues/502) - The default configuration now respects the presence of `FORCE_COLOR` (regardless of its value, unless an empty string). This disables all heuristics whether it makes sense to use colors. [#503](https://github.com/hynek/structlog/issues/503) - The default configuration now respects the presence of [`NO_COLOR`](https://no-color.org) (regardless of its value, unless an empty string). This disables all heuristics whether it makes sense to use colors and overrides `FORCE_COLOR`. [#504](https://github.com/hynek/structlog/issues/504) ### Fixed - ConsoleRenderer now reuses the `_figure_out_exc_info` to process the `exc_info` argument like `ExceptionRenderer` does. This prevents crashes if the actual Exception is passed for the *exc_info* argument instead of a tuple or `True`. [#482](https://github.com/hynek/structlog/issues/482) - `FilteringBoundLogger.aexception()` now extracts the exception info using `sys.exc_info()` before passing control to the asyncio executor (where original exception info is no longer available). [#488](https://github.com/hynek/structlog/issues/488) ## [22.3.0](https://github.com/hynek/structlog/compare/22.2.0...22.3.0) - 2022-11-24 ### Changed - String interpolation in `FilteringBoundLogger` (used by default) is now only attempted if positional arguments are passed. This prevents crashes if something different than a string is passed for the *event* argument. [#475](https://github.com/hynek/structlog/pull/475) ### Fixed - String interpolation doesn't cause crashes in filtered log call anymore. [#478](https://github.com/hynek/structlog/pull/478) ## [22.2.0](https://github.com/hynek/structlog/compare/22.1.0...22.2.0) - 2022-11-19 ### Deprecated - Accessing package metadata as attributes on the *structlog* module is deprecated (for example, `structlog.__version__`). Please use [`importlib.metadata`](https://docs.python.org/3.10/library/importlib.metadata.html) instead (for Python 3.7: the [*importlib-metadata*](https://pypi.org/project/importlib-metadata/) PyPI package). - The `structlog.types` module is now deprecated in favor of the `structlog.typing` module. It seems like the Python typing community is settling on this name. ### Added - `FilteringBoundLogger` (used by default) now allows for string interpolation using positional arguments: ```pycon >>> log.info("Hello %s! The answer is %d.", "World", 42, x=1) 2022-10-07 10:04.31 [info ] Hello World! The answer is 42. x=1 ``` [#454](https://github.com/hynek/structlog/pull/454) - `FilteringBoundLogger` now also has support for *asyncio*-based logging. Instead of a wrapper class like `structlog.stdlib.AsyncBoundLogger`, async equivalents have been added for all logging methods. So instead of `log.info("hello")` you can also write `await log.ainfo("hello")` in async functions and methods. This seems like the better approach and if it's liked by the community, `structlog.stdlib.BoundLogger` will get those methods too. [#457](https://github.com/hynek/structlog/pull/457) ### Changed - The documentation has been **heavily** overhauled. Have a look if you haven't lately! Especially the graphs in the [standard library chapter](https://www.structlog.org/en/latest/standard-library.html) have proven valuable to many. - The build backend has been switched to [*Hatch*](https://hatch.pypa.io/). ### Fixed - The timestamps in the default configuration now use the correct separator (`:`) for seconds. ## [22.1.0](https://github.com/hynek/structlog/compare/21.5.0...22.1.0) - 2022-07-20 ### Removed - Python 3.6 is not supported anymore. - Pickling is now only possible with protocol version 3 and newer. ### Deprecated - The entire `structlog.threadlocal` module is deprecated. Please use the primitives from `structlog.contextvars` instead. If you're using the modern APIs (`bind_threadlocal()` / `merge_threadlocal()`) it's enough to replace them 1:1 with their `contextvars` counterparts. The old approach around `wrap_dict()` has been discouraged for a while. Currently there are no concrete plans to remove the module, but no patches against it will be accepted from now on. [#409](https://github.com/hynek/structlog/pull/409) ### Added - `structlog.processors.StackInfoRenderer` now has an *additional_ignores* parameter that allows you to filter out your own logging layer. [#396](https://github.com/hynek/structlog/issues/396) - Added `structlog.WriteLogger`, a faster – but more low-level – alternative to `structlog.PrintLogger`. It works the way `PrintLogger` used to work in previous versions. [#403](https://github.com/hynek/structlog/pull/403) [#404](https://github.com/hynek/structlog/pull/404) - `structlog.make_filtering_bound_logger()`-returned loggers now also have a `log()` method to match the `structlog.stdlib.BoundLogger` signature closer. [#413](https://github.com/hynek/structlog/pull/413) - Added structured logging of tracebacks via the `structlog.tracebacks` module, and most notably the `structlog.tracebacks.ExceptionDictTransformer` which can be used with the new `structlog.processors.ExceptionRenderer` to render JSON tracebacks. [#407](https://github.com/hynek/structlog/pull/407) - `structlog.stdlib.recreate_defaults(log_level=logging.NOTSET)` that recreates *structlog*'s defaults on top of standard library's `logging`. It optionally also configures `logging` to log to standard out at the passed log level. [#428](https://github.com/hynek/structlog/pull/428) - `structlog.processors.EventRenamer` allows you to rename the hitherto hard-coded event dict key `event` to something else. Optionally, you can rename another key to `event` at the same time, too. So adding `EventRenamer(to="msg", replace_by="_event")` to your processor pipeline will rename the standard `event` key to `msg` and then rename the `_event` key to `event`. This allows you to use the `event` key in your own log files and to have consistent log message keys across languages. - `structlog.dev.ConsoleRenderer(event_key="event")` now allows to customize the name of the key that is used for the log message. ### Changed - `structlog.make_filtering_bound_logger()` now returns a method with the same signature for all log levels, whether they are active or not. This ensures that invalid calls to inactive log levels are caught immediately and don't explode once the log level changes. [#401](https://github.com/hynek/structlog/pull/401) - `structlog.PrintLogger` – that is used by default – now uses `print()` for printing, making it a better citizen for interactive terminal applications. [#399](https://github.com/hynek/structlog/pull/399) - `structlog.testing.capture_logs` now works for already initialized bound loggers. [#408](https://github.com/hynek/structlog/pull/412) - `structlog.processors.format_exc_info()` is no longer a function, but an instance of `structlog.processors.ExceptionRenderer`. Its behavior has not changed. [#407](https://github.com/hynek/structlog/pull/407) - The default configuration now includes the `structlog.contextvars.merge_contextvars` processor. That means you can use [`structlog.contextvars`](https://www.structlog.org/en/stable/contextvars.html) features without configuring *structlog*. ### Fixed - Overloaded the `bind`, `unbind`, `try_unbind` and `new` methods in the `FilteringBoundLogger` [Protocol](https://docs.python.org/3/library/typing.html#typing.Protocol). This makes it easier to use objects of type `FilteringBoundLogger` in a typed context. [#392](https://github.com/hynek/structlog/pull/392) - Monkeypatched `sys.stdout`s are now handled more gracefully by `ConsoleRenderer` (that's used by default). [#404](https://github.com/hynek/structlog/pull/404) - `structlog.stdlib.render_to_log_kwargs()` now correctly handles the presence of `exc_info`, `stack_info`, and `stackLevel` in the event dictionary. They are transformed into proper keyword arguments instead of putting them into the `extra` dictionary. [#424](https://github.com/hynek/structlog/issues/424), [#427](https://github.com/hynek/structlog/issues/427) ## [21.5.0](https://github.com/hynek/structlog/compare/21.4.0...21.5.0) - 2021-12-16 ### Added - Added the `structlog.processors.LogfmtRenderer` processor to render log lines using the [*logfmt*](https://brandur.org/logfmt) format. [#376](https://github.com/hynek/structlog/pull/376) - Added the `structlog.stdlib.ExtraAdder` processor that adds extra attributes of `logging.LogRecord` objects to the event dictionary. This processor can be used for adding data passed in the `extra` parameter of the `logging` module's log methods to the event dictionary. [#209](https://github.com/hynek/structlog/pull/209), [#377](https://github.com/hynek/structlog/pull/377) - Added the `structlog.processor.CallsiteParameterAdder` processor that adds parameters of the callsite that an event dictionary originated from to the event dictionary. This processor can be used to enrich events dictionaries with information such as the function name, line number and filename that an event dictionary originated from. [#380](https://github.com/hynek/structlog/pull/380) ## [21.4.0](https://github.com/hynek/structlog/compare/21.3.0...21.4.0) - 2021-11-25 ### Added - Added the `structlog.threadlocal.bound_threadlocal` and `structlog.contextvars.bound_contextvars` decorator/context managers to temporarily bind key-value pairs to a thread-local and context-local context. [#371](https://github.com/hynek/structlog/pull/371) ### Fixed - Fixed import when running in optimized mode (`PYTHONOPTIMIZE=2` or `python -OO`) . [#373](https://github.com/hynek/structlog/pull/373) ## [21.3.0](https://github.com/hynek/structlog/compare/21.2.0...21.3.0) - 2021-11-20 ### Added - `structlog.dev.ConsoleRenderer` now has `sort_keys` boolean parameter that allows to disable the sorting of keys on output. [#358](https://github.com/hynek/structlog/pull/358) ### Changed - *structlog* switched its packaging to [*flit*](https://flit.pypa.io/). Users shouldn't notice a difference, but (re-)packagers might. - `structlog.stdlib.AsyncBoundLogger` now determines the running loop when logging, not on instantiation. That has a minor performance impact, but makes it more robust when loops change (for example, `aiohttp.web.run_app()`), or you want to use `sync_bl` *before* a loop has started. ### Fixed - `structlog.processors.TimeStamper` now works well with [*FreezeGun*](https://github.com/spulec/freezegun) even when it gets applied before the loggers are configured. [#364](https://github.com/hynek/structlog/pull/364) - `structlog.stdlib.ProcessorFormatter` now has a *processors* argument that allows to define a processor chain to run over *all* log entries. Before running the chain, two additional keys are added to the event dictionary: `_record` and `_from_structlog`. With them it's possible to extract information from `logging.LogRecord`s and differentiate between *structlog* and `logging` log entries while processing them. The old *processor* (singular) parameter is now deprecated, but no plans exist to remove it. [#365](https://github.com/hynek/structlog/pull/365) ## [21.2.0](https://github.com/hynek/structlog/compare/21.1.0...21.2.0) - 2021-10-12 ### Added - `structlog.threadlocal.get_threadlocal()` and `structlog.contextvars.get_contextvars()` can now be used to get a copy of the current thread-local/context-local context that has been bound using `structlog.threadlocal.bind_threadlocal()` and `structlog.contextvars.bind_contextvars()`. [#331](https://github.com/hynek/structlog/pull/331), [#337](https://github.com/hynek/structlog/pull/337) - `structlog.threadlocal.get_merged_threadlocal(bl)` and `structlog.contextvars.get_merged_contextvars(bl)` do the same, but also merge the context from a bound logger *bl*. Same pull requests as previous change. - `structlog.contextvars.bind_contextvars()` now returns a mapping of keys to `contextvars.Token`s, allowing you to reset values using the new `structlog.contextvars.reset_contextvars()`. [#339](https://github.com/hynek/structlog/pull/339) - Exception rendering in `structlog.dev.ConsoleLogger` is now configurable using the `exception_formatter` setting. If either the [Rich](https://github.com/Textualize/rich) or the [*better-exceptions*](https://github.com/qix-/better-exceptions) package is present, *structlog* will use them for pretty-printing tracebacks. Rich takes precedence over *better-exceptions* if both are present. This only works if `format_exc_info` is **absent** in the processor chain. [#330](https://github.com/hynek/structlog/pull/330), [#349](https://github.com/hynek/structlog/pull/349) - The final processor can now return a `bytearray` (additionally to `str` and `bytes`). [#344](https://github.com/hynek/structlog/issues/344) ### Changed - To implement pretty exceptions (see Changes below), `structlog.dev.ConsoleRenderer` now formats exceptions itself. Make sure to remove `format_exc_info` from your processor chain if you configure *structlog* manually. This change is not really breaking, because the old use-case will keep working as before. However if you pass `pretty_exceptions=True` (which is the default if either `rich` or `better-exceptions` is installed), a warning will be raised and the exception will be rendered without prettification. - All use of [Colorama](https://github.com/tartley/colorama) on non-Windows systems has been excised. Thus, colors are now enabled by default in `structlog.dev.ConsoleRenderer` on non-Windows systems. You can keep using Colorama to customize colors, of course. [#345](https://github.com/hynek/structlog/pull/345) ### Fixed - *structlog* is now importable if `sys.stdout` is `None` (for example, when running using `pythonw`). [#313](https://github.com/hynek/structlog/issues/313) ## [21.1.0](https://github.com/hynek/structlog/compare/20.2.0...21.1.0) - 2021-02-18 ### Changed - `structlog.dev.ConsoleRenderer` will now look for a `logger_name` key if no `logger` key is set. [#295](https://github.com/hynek/structlog/pull/295) ### Fixed - `structlog.threadlocal.wrap_dict()` now has a correct type annotation. [#290](https://github.com/hynek/structlog/pull/290) - Fix isolation in `structlog.contextvars`. [#302](https://github.com/hynek/structlog/pull/302) - The default configuration and loggers are pickleable again. [#301](https://github.com/hynek/structlog/pull/301) ## [20.2.0](https://github.com/hynek/structlog/compare/20.1.0...20.2.0) - 2020-12-31 ### Removed - Python 2.7 and 3.5 aren't supported anymore. The package meta data should ensure that you keep getting 20.1.0 on those versions. [#244](https://github.com/hynek/structlog/pull/244) ### Deprecated - Accessing the `_context` attribute of a bound logger is now deprecated. Please use the new `structlog.get_context()`. ### Added - *structlog* has now type hints for all of its APIs! Since *structlog* is highly dynamic and configurable, this led to a few concessions like a specialized `structlog.stdlib.get_logger()` whose only difference to `structlog.get_logger()` is that it has the correct type hints. We consider them provisional for the time being – that means the backwards-compatibility does not apply to them in its full strength until we feel we got it right. Please feel free to provide feedback! [#223](https://github.com/hynek/structlog/issues/223), [#282](https://github.com/hynek/structlog/issues/282) - Added `structlog.make_filtering_logger` that can be used like `configure(wrapper_class=make_filtering_bound_logger(logging.INFO))`. It creates a highly optimized bound logger whose inactive methods only consist of a `return None`. This is now also the default logger. - As a complement, `structlog.stdlib.add_log_level()` can now additionally be imported as `structlog.processors.add_log_level` since it just adds the method name to the event dict. - Added `structlog.BytesLogger` to avoid unnecessary encoding round trips. Concretely this is useful with *orjson* which returns bytes. [#271](https://github.com/hynek/structlog/issues/271) - The final processor now also may return bytes that are passed untouched to the wrapped logger. - `structlog.get_context()` allows you to retrieve the original context of a bound logger. [#266](https://github.com/hynek/structlog/issues/266), - Added `structlog.testing.CapturingLogger` for more unit testing goodness. - Added `structlog.stdlib.AsyncBoundLogger` that executes logging calls in a thread executor and therefore doesn't block. [#245](https://github.com/hynek/structlog/pull/245) ### Changed - The default bound logger (`wrapper_class`) if you don't configure *structlog* has changed. It's mostly compatible with the old one but a few uncommon methods like `log`, `failure`, or `err` don't exist anymore. You can regain the old behavior by using `structlog.configure(wrapper_class=structlog.BoundLogger)`. Please note that due to the various interactions between settings, it's possible that you encounter even more errors. We **strongly** urge you to always configure all possible settings since the default configuration is *not* covered by our backwards-compatibility policy. - `structlog.processors.add_log_level()` is now part of the default configuration. - `structlog.stdlib.ProcessorFormatter` no longer uses exceptions for control flow, allowing `foreign_pre_chain` processors to use `sys.exc_info()` to access the real exception. ### Fixed - `structlog.PrintLogger` now supports `copy.deepcopy()`. [#268](https://github.com/hynek/structlog/issues/268) ## [20.1.0](https://github.com/hynek/structlog/compare/19.2.0...20.1.0) - 2020-01-28 ### Deprecated - This is the last version to support Python 2.7 (including PyPy) and 3.5. All following versions will only support Python 3.6 or later. ### Added - Added a new module `structlog.contextvars` that allows to have a global but context-local *structlog* context the same way as with `structlog.threadlocal` since 19.2.0. [#201](https://github.com/hynek/structlog/issues/201), [#236](https://github.com/hynek/structlog/pull/236) - Added a new module `structlog.testing` for first class testing support. The first entry is the context manager `capture_logs()` that allows to make assertions about structured log calls. [#14](https://github.com/hynek/structlog/issues/14), [#234](https://github.com/hynek/structlog/pull/234) - Added `structlog.threadlocal.unbind_threadlocal()`. [#239](https://github.com/hynek/structlog/pull/239) ### Fixed - The logger created by `structlog.get_logger()` is not detected as an abstract method anymore, when attached to an abstract base class. [#229](https://github.com/hynek/structlog/issues/229) - Colorama isn't initialized lazily on Windows anymore because it breaks rendering. [#232](https://github.com/hynek/structlog/issues/232), [#242](https://github.com/hynek/structlog/pull/242) ## [19.2.0](https://github.com/hynek/structlog/compare/19.1.0...19.2.0) - 2019-10-16 ### Removed - Python 3.4 is not supported anymore. It has been unsupported by the Python core team for a while now and its PyPI downloads are negligible. It's very unlikely that *structlog* will break under 3.4 anytime soon, but we don't test it anymore. ### Added - Full Python 3.8 support for `structlog.stdlib`. - Added more pass-through properties to `structlog.stdlib.BoundLogger`. To makes it easier to use it as a drop-in replacement for `logging.Logger`. [#198](https://github.com/hynek/structlog/issues/198) - Added new processor `structlog.dev.set_exc_info()` that will set `exc_info=True` if the method's name is `exception` and `exc_info` isn't set at all. *This is only necessary when the standard library integration is not used*. It fixes the problem that in the default configuration, `structlog.get_logger().exception("hi")` in an `except` block would not print the exception without passing `exc_info=True` to it explicitly. [#130](https://github.com/hynek/structlog/issues/130), [#173](https://github.com/hynek/structlog/issues/173), [#200](https://github.com/hynek/structlog/issues/200), [#204](https://github.com/hynek/structlog/issues/204) - Added a new thread-local API that allows binding values to a thread-local context explicitly without affecting the default behavior of `bind()`. [#222](https://github.com/hynek/structlog/issues/222), [#225](https://github.com/hynek/structlog/issues/225) - Added *pass_foreign_args* argument to `structlog.stdlib.ProcessorFormatter`. It allows to pass a foreign log record's *args* attribute to the event dictionary under the `positional_args` key. [#228](https://github.com/hynek/structlog/issues/228) ### Changed - `structlog.stdlib.ProcessorFormatter` now takes a logger object as an optional keyword argument. This makes `ProcessorFormatter` work properly with `stuctlog.stdlib.filter_by_level()`. [#219](https://github.com/hynek/structlog/issues/219) - `structlog.dev.ConsoleRenderer` now calls `str()` on the event value. [#221](https://github.com/hynek/structlog/issues/221) ### Fixed - `structlog.dev.ConsoleRenderer` now uses no colors by default, if Colorama is not available. [#215](https://github.com/hynek/structlog/issues/215) - `structlog.dev.ConsoleRenderer` now initializes Colorama lazily, to prevent accidental side-effects just by importing *structlog*. [#210](https://github.com/hynek/structlog/issues/210) - A best effort has been made to make as much of *structlog* pickleable as possible to make it friendlier with `multiprocessing` and similar libraries. Some classes can only be pickled on Python 3 or using the [dill](https://pypi.org/project/dill/) library though and that is very unlikely to change. So far, the configuration proxy, `structlog.processor.TimeStamper`, `structlog.BoundLogger`, `structlog.PrintLogger` and `structlog.dev.ConsoleRenderer` have been made pickleable. Please report if you need any another class fixed. [#126](https://github.com/hynek/structlog/issues/126) ## [19.1.0](https://github.com/hynek/structlog/compare/18.2.0...19.1.0) - 2019-02-02 ### Added - `structlog.ReturnLogger` and `structlog.PrintLogger` now have a `fatal()` log method. [#181](https://github.com/hynek/structlog/issues/181) ### Changed - As announced in 18.1.0, `pip install -e .[dev]` now installs all development dependencies. Sorry for the inconveniences this undoubtedly will cause! - *structlog* now tolerates passing through `dict`s to stdlib logging. [#187](https://github.com/hynek/structlog/issues/187), [#188](https://github.com/hynek/structlog/pull/188), [#189](https://github.com/hynek/structlog/pull/189) ### Fixed - Under certain (rather unclear) circumstances, the frame extraction could throw an `SystemError: error return without exception set`. A workaround has been added. [#174](https://github.com/hynek/structlog/issues/174) ## [18.2.0](https://github.com/hynek/structlog/compare/18.1.0...18.2.0) - 2018-09-05 ### Added - Added `structlog.stdlib.add_log_level_number()` processor that adds the level *number* to the event dictionary. Can be used to simplify log filtering. [#151](https://github.com/hynek/structlog/pull/151) - `structlog.processors.JSONRenderer` now allows for overwriting the *default* argument of its serializer. [#77](https://github.com/hynek/structlog/pull/77), [#163](https://github.com/hynek/structlog/pull/163) - Added `try_unbind()` that works like `unbind()` but doesn't raise a `KeyError` if one of the keys is missing. [#171](https://github.com/hynek/structlog/pull/171) ## [18.1.0](https://github.com/hynek/structlog/compare/17.2.0...18.1.0) - 2018-01-27 ### Deprecated - The meaning of the `structlog[dev]` installation target will change from "colorful output" to "dependencies to develop *structlog*" in 19.1.0. The main reason behind this decision is that it's impossible to have a *structlog* in your normal dependencies and additionally a `structlog[dev]` for development (`pip` will report an error). ### Added - `structlog.dev.ConsoleRenderer` now accepts a *force_colors* argument to output colored logs even if the destination is not a tty. Use this option if your logs are stored in files that are intended to be streamed to the console. - `structlog.dev.ConsoleRenderer` now accepts a *level_styles* argument for overriding the colors for individual levels, as well as to add new levels. See the docs for `ConsoleRenderer.get_default_level_styles()` for usage. [#139](https://github.com/hynek/structlog/pull/139) - Added `structlog.is_configured()` to check whether or not *structlog* has been configured. - Added `structlog.get_config()` to introspect current configuration. ### Changed - Empty strings are valid events now. [#110](https://github.com/hynek/structlog/issues/110) - `structlog.stdlib.BoundLogger.exception()` now uses the `exc_info` argument if it has been passed instead of setting it unconditionally to `True`. [#149](https://github.com/hynek/structlog/pull/149) - Default configuration now uses plain `dict`s on Python 3.6+ and PyPy since they are ordered by default. ### Fixed - Do not encapsulate Twisted failures twice with newer versions of Twisted. [#144](https://github.com/hynek/structlog/issues/144) ## [17.2.0](https://github.com/hynek/structlog/compare/17.1.0...17.2.0) - 2017-05-15 ### Added - `structlog.stdlib.ProcessorFormatter` now accepts *keep_exc_info* and *keep_stack_info* arguments to control what to do with this information on log records. Most likely you want them both to be `False` therefore it's the default. [#109](https://github.com/hynek/structlog/issues/109) ### Fixed - `structlog.stdlib.add_logger_name()` now works in `structlog.stdlib.ProcessorFormatter`'s `foreign_pre_chain`. [#112](https://github.com/hynek/structlog/issues/112) - Clear log record args in `structlog.stdlib.ProcessorFormatter` after rendering. This fix is for you if you tried to use it and got `TypeError: not all arguments converted during string formatting` exceptions. [#116](https://github.com/hynek/structlog/issues/116), [#117](https://github.com/hynek/structlog/issues/117) ## [17.1.0](https://github.com/hynek/structlog/compare/16.1.0...17.1.0) - 2017-04-24 The main features of this release are massive improvements in standard library's `logging` integration. Have a look at the updated [standard library chapter](https://www.structlog.org/en/stable/standard-library.html) on how to use them! Special thanks go to [Fabian Büchler](https://github.com/fabianbuechler), [Gilbert Gilb's](https://github.com/gilbsgilbs), [Iva Kaneva](https://github.com/if-fi), [insolite](https://github.com/insolite), and [sky-code](https://github.com/sky-code), that made them possible. ### Added - Added `structlog.stdlib.render_to_log_kwargs()`. This allows you to use `logging`-based formatters to take care of rendering your entries. [#98](https://github.com/hynek/structlog/issues/98) - Added `structlog.stdlib.ProcessorFormatter` which does the opposite: This allows you to run *structlog* processors on arbitrary `logging.LogRecords`. [#79](https://github.com/hynek/structlog/issues/79), [#105](https://github.com/hynek/structlog/issues/105) - Added *repr_native_str* to `structlog.processors.KeyValueRenderer` and `structlog.dev.ConsoleRenderer`. This allows for human-readable non-ASCII output on Python 2 (`repr()` on Python 2 behaves like `ascii()` on Python 3 in that regard). As per compatibility policy, it's on (original behavior) in `KeyValueRenderer` and off (human-friendly behavior) in `ConsoleRenderer`. [#94](https://github.com/hynek/structlog/issues/94) - Added *colors* argument to `structlog.dev.ConsoleRenderer` and made it the default renderer. [#78](https://github.com/hynek/structlog/pull/78) ### Changed - The default renderer now is `structlog.dev.ConsoleRenderer` if you don't configure *structlog*. Colors are used if available and human-friendly timestamps are prepended. This is in line with our backwards-compatibility policy that explicitly excludes default settings. - UNIX epoch timestamps from `structlog.processors.TimeStamper` are more precise now. - Positional arguments are now removed even if they are empty. [#82](https://github.com/hynek/structlog/pull/82) ## Fixed - Fixed bug with Python 3 and `structlog.stdlib.BoundLogger.log()`. Error log level was not reproducible and was logged as exception one time out of two. [#92](https://github.com/hynek/structlog/pull/92) ## [16.1.0](https://github.com/hynek/structlog/compare/16.0.0...16.1.0) - 2016-05-24 ### Removed - Python 3.3 and 2.6 aren't supported anymore. They may work by chance but any effort to keep them working has ceased. The last Python 2.6 release was on October 29, 2013 and isn't supported by the CPython core team anymore. Major Python packages like Django and Twisted dropped Python 2.6 a while ago already. Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release. ### Added - Added a `drop_missing` argument to `KeyValueRenderer`. If `key_order` is used and a key is missing a value, it's not rendered at all instead of being rendered as `None`. [#67](https://github.com/hynek/structlog/pull/67) ### Fixed - Exceptions without a `__traceback__` are now also rendered on Python 3. - Don't cache loggers in lazy proxies returned from `get_logger()`. This lead to in-place mutation of them if used before configuration which in turn lead to the problem that configuration was applied only partially to them later. [#72](https://github.com/hynek/structlog/pull/72) ## [16.0.0](https://github.com/hynek/structlog/compare/15.3.0...16.0.0) - 2016-01-28 ### Added - Added `structlog.dev.ConsoleRenderer` that renders the event dictionary aligned and with colors. - Added `structlog.processors.UnicodeDecoder` that will decode all byte string values in an event dictionary to Unicode. - Added `serializer` parameter to `structlog.processors.JSONRenderer` which allows for using different (possibly faster) JSON encoders than the standard library. ### Changed - `structlog.processors.ExceptionPrettyPrinter` and `structlog.processors.format_exc_info` now support passing of Exceptions on Python 3. - [*six*](https://six.readthedocs.io/) is now used for compatibility. ### Fixed - The context is now cleaned up when exiting `structlog.threadlocal.tmp_bind` in case of exceptions. [#64](https://github.com/hynek/structlog/issues/64) - Be more more lenient about missing `__name__`s. [#62](https://github.com/hynek/structlog/pull/62) ## [15.3.0](https://github.com/hynek/structlog/compare/15.2.0...15.3.0) - 2015-09-25 ### Added - Officially support Python 3.5. - Added `structlog.ReturnLogger.failure` and `structlog.PrintLogger.failure` as preparation for the new Twisted logging system. ### Fixed - Tolerate frames without a `__name__`, better. [#58](https://github.com/hynek/structlog/pull/58) ## [15.2.0](https://github.com/hynek/structlog/compare/15.1.0...15.2.0) - 2015-06-10 ### Added - Added option to specify target key in `structlog.processors.TimeStamper` processor. [#51](https://github.com/hynek/structlog/pull/51) ### Changed - Allow empty lists of processors. This is a valid use case since [#26](https://github.com/hynek/structlog/issues/26) has been merged. Before, supplying an empty list resulted in the defaults being used. - Better support of `logging.Logger.exception` within *structlog*. [#52](https://github.com/hynek/structlog/pull/52) ### Fixed - Prevent Twisted's `log.err` from quoting strings rendered by `structlog.twisted.JSONRenderer`. ## [15.1.0](https://github.com/hynek/structlog/compare/15.0.0...15.1.0) - 2015-02-24 ### Fixed - Tolerate frames without a `__name__` when guessing callsite names. ## [15.0.0](https://github.com/hynek/structlog/compare/0.4.2...15.0.0) - 2015-01-23 ### Added - Added `structlog.stdlib.add_log_level` and `structlog.stdlib.add_logger_name` processors. [#44](https://github.com/hynek/structlog/pull/44) - Added `structlog.stdlib.BoundLogger.log`. [#42](https://github.com/hynek/structlog/pull/42) - Added `structlog.stdlib.BoundLogger.exception`. [#22](https://github.com/hynek/structlog/pull/22) ### Changed - Pass positional arguments to stdlib wrapped loggers that use string formatting. [#19](https://github.com/hynek/structlog/pull/19) - *structlog* is now dually licensed under the [Apache License, Version 2](https://choosealicense.com/licenses/apache/) and the [MIT](https://choosealicense.com/licenses/mit/) license. Therefore it is now legal to use *structlog* with [GPLv2](https://choosealicense.com/licenses/gpl-2.0/)-licensed projects. [#28](https://github.com/hynek/structlog/pull/28) ## [0.4.2](https://github.com/hynek/structlog/compare/0.4.1...0.4.2) - 2014-07-26 ### Removed - Drop support for Python 3.2. There is no justification to add complexity for a Python version that nobody uses. If you are one of the [0.350%](https://alexgaynor.net/2014/jan/03/pypi-download-statistics/) that use Python 3.2, please stick to the 0.4 branch; critical bugs will still be fixed. ### Added - Officially support Python 3.4. - Allow final processor to return a dictionary. See the adapting chapter. [#26](https://github.com/hynek/structlog/issues/26) - Test Twisted-related code on Python 3 (with some caveats). ### Fixed - Fixed a memory leak in greenlet code that emulates thread locals. It shouldn't matter in practice unless you use multiple wrapped dicts within one program that is rather unlikely. [#8](https://github.com/hynek/structlog/pull/8) - `structlog.PrintLogger` now is thread-safe. - `from structlog import *` works now (but you still shouldn't use it). ## [0.4.1](https://github.com/hynek/structlog/compare/0.4.0...0.4.1) - 2013-12-19 ### Changed - Don't cache proxied methods in `structlog.threadlocal._ThreadLocalDictWrapper`. This doesn't affect regular users. ### Fixed - Various doc fixes. ## [0.4.0](https://github.com/hynek/structlog/compare/0.3.2...0.4.0) - 2013-11-10 ### Added - Added `structlog.processors.StackInfoRenderer` for adding stack information to log entries without involving exceptions. Also added it to default processor chain. [#6](https://github.com/hynek/structlog/pull/6) - Allow optional positional arguments for `structlog.get_logger` that are passed to logger factories. The standard library factory uses this for explicit logger naming. [#12](https://github.com/hynek/structlog/pull/12) - Add `structlog.processors.ExceptionPrettyPrinter` for development and testing when multiline log entries aren't just acceptable but even helpful. - Allow the standard library name guesser to ignore certain frame names. This is useful together with frameworks. - Add meta data (for example, function names, line numbers) extraction for wrapped stdlib loggers. [#5](https://github.com/hynek/structlog/pull/5) ## [0.3.2](https://github.com/hynek/structlog/compare/0.3.1...0.3.2) - 2013-09-27 ### Fixed - Fix stdlib's name guessing. ## [0.3.1](https://github.com/hynek/structlog/compare/0.3.0...0.3.1) - 2013-09-26 ### Fixed - Added forgotten `structlog.processors.TimeStamper` to API documentation. ## [0.3.0](https://github.com/hynek/structlog/compare/0.2.0...0.3.0) - 2013-09-23 ### Changes: - Greatly enhanced and polished the documentation and added a new theme based on Write The Docs, requests, and Flask. - Add Python Standard Library-specific BoundLogger that has an explicit API instead of intercepting unknown method calls. See `structlog.stdlib.BoundLogger`. - `structlog.ReturnLogger` now allows arbitrary positional and keyword arguments. - Add Twisted-specific BoundLogger that has an explicit API instead of intercepting unknown method calls. See `structlog.twisted.BoundLogger`. - Allow logger proxies that are returned by `structlog.get_logger` and `structlog.wrap_logger` to cache the BoundLogger they assemble according to configuration on first use. See the chapter on performance and the `cache_logger_on_first_use` argument of `structlog.configure` and `structlog.wrap_logger`. - Extract a common base class for loggers that does nothing except keeping the context state. This makes writing custom loggers much easier and more straight-forward. See `structlog.BoundLoggerBase`. ## [0.2.0](https://github.com/hynek/structlog/compare/0.1.0...0.2.0) - 2013-09-17 ### Added - Add `key_order` option to `structlog.processors.KeyValueRenderer` for more predictable log entries with any `dict` class. - Enhance Twisted support by offering JSONification of non-structlog log entries. - Allow for custom serialization in `structlog.twisted.JSONRenderer` without abusing `__repr__`. ### Changed - Promote to stable, thus henceforth a strict backwards-compatibility policy is put into effect. - `structlog.PrintLogger` now uses proper I/O routines and is thus viable not only for examples but also for production. ## [0.1.0](https://github.com/hynek/structlog/tree/0.1.0) - 2013-09-16 Initial release. structlog-25.5.0/COPYRIGHT0000644000000000000000000000062715077624341012070 0ustar00Licensed under either of - Apache License, Version 2.0 (LICENSE-APACHE or ) - or MIT license (LICENSE-MIT or ) at your option. Any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions. structlog-25.5.0/README.md0000644000000000000000000001533115077624341012052 0ustar00# *structlog*: Structured Logging for Python

structlog: Structured Logging for Python

Documentation License: MIT / Apache 2.0 DOI Supported Python versions of the current PyPI release. Downloads per month

Simple. Powerful. Fast. Pick three.

*structlog* is *the* production-ready logging solution for Python: - **Simple**: Everything is about **functions** that take and return **dictionaries** – all hidden behind **familiar APIs**. - **Powerful**: Functions and dictionaries aren’t just simple but also powerful. *structlog* leaves *you* in control. - **Fast**: *structlog* is not hamstrung by designs of yore. Its flexibility comes not at the price of performance. Thanks to its flexible design, *you* choose whether you want *structlog* to take care of the **output** of your log entries or whether you prefer to **forward** them to an existing logging system like the standard library's `logging` module. The output format is just as flexible and *structlog* comes with support for JSON, [*logfmt*](https://brandur.org/logfmt), as well as pretty console output out-of-the-box: [![Screenshot of colorful structlog output with ConsoleRenderer](https://github.com/hynek/structlog/blob/main/docs/_static/console_renderer.png?raw=true)](https://github.com/hynek/structlog/blob/main/docs/_static/console_renderer.png?raw=true) ## Sponsors *structlog* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). Especially those generously supporting us at the *The Organization* tier and higher:

Please consider joining them to help make structlog’s maintenance more sustainable!

## Introduction *structlog* has been successfully used in production at every scale since **2013**, while embracing cutting-edge technologies like *asyncio*, context variables, or type hints as they emerged. Its paradigms proved influential enough to [help design](https://twitter.com/sirupsen/status/638330548361019392) structured logging [packages across ecosystems](https://github.com/sirupsen/logrus). A short explanation on *why* structured logging is good for you, and why *structlog* is the right tool for the job can be found in the [Why chapter](https://www.structlog.org/en/stable/why.html) of our documentation. Once you feel inspired to try it out, check out our friendly [Getting Started tutorial](https://www.structlog.org/en/stable/getting-started.html). For a fully-fledged zero-to-hero tutorial, check out [*A Comprehensive Guide to Python Logging with structlog*](https://betterstack.com/community/guides/logging/structlog/). If you prefer videos over reading, check out [Markus Holtermann](https://chaos.social/@markush)'s talk *Logging Rethought 2: The Actions of Frank Taylor Jr.*:

## Credits *structlog* is written and maintained by [Hynek Schlawack](https://hynek.me/). The idea of bound loggers is inspired by previous work by [Jean-Paul Calderone](https://github.com/exarkun) and [David Reid](https://github.com/dreid). The development is kindly supported by my employer [Variomedia AG](https://www.variomedia.de/), *structlog*’s [Tidelift subscribers](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek), and all my amazing [GitHub Sponsors](https://github.com/sponsors/hynek). The logs-loving beaver logo has been contributed by [Lynn Root](https://www.roguelynn.com). ## Project Links - [**Get Help**](https://stackoverflow.com/questions/tagged/structlog) (use the *structlog* tag on Stack Overflow) - [**PyPI**](https://pypi.org/project/structlog/) - [**GitHub**](https://github.com/hynek/structlog) - [**Documentation**](https://www.structlog.org/) - [**Changelog**](https://github.com/hynek/structlog/tree/main/CHANGELOG.md) - [**Third-party Extensions**](https://github.com/hynek/structlog/wiki/Third-party-Extensions) ## *structlog* for Enterprise Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek). The maintainers of *structlog* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. structlog-25.5.0/show_off.py0000644000000000000000000000210515077624341012752 0ustar00""" Show how console logging looks like. This is used for the screenshot in the readme and . """ from dataclasses import dataclass import structlog @dataclass class SomeClass: x: int y: str structlog.stdlib.recreate_defaults() # so we have logger names log = structlog.get_logger("some_logger") log.debug("debugging is hard", a_list=[1, 2, 3]) log.info("informative!", some_key="some_value") log.warning("uh-uh!") log.error("omg", a_dict={"a": 42, "b": "foo"}) log.critical("wtf", what=SomeClass(x=1, y="z")) # Demonstrate writable properties cr = structlog.dev.ConsoleRenderer.get_active() cr.colors = False log.info("where are the colors!?", colors="gone") cr.colors = True log.info("there they are!", colors="back") log2 = structlog.get_logger("another_logger") def make_call_stack_more_impressive(): try: d = {"x": 42} print(SomeClass(d["y"], "foo")) except Exception: log2.exception("poor me") log.info("all better now!", stack_info=True) make_call_stack_more_impressive() structlog-25.5.0/tox.ini0000644000000000000000000000711715077624341012111 0ustar00[tox] min_version = 4 env_list = pre-commit, py3{8,9,10,11,12,13,14}-{tests,mypy} py3{8,13}-tests-{colorama,be,rich}, typing-{mypy,pyright,ty,pyrefly}, docs-{sponsors,build,doctests}, coverage-{combine,report} [testenv] package = wheel wheel_build_env = .pkg dependency_groups = tests: tests mypy: typing commands = tests: pytest {posargs} mypy: mypy tests/typing # Run oldest and latest under Coverage. # Keep in-sync with coverage `depends below. [testenv:py3{8,13}-tests{,-colorama,-be,-rich}] deps = coverage[toml] py313: twisted colorama: colorama rich: rich be: better-exceptions commands = coverage run -m pytest {posargs} # Split combine/report in 2 to avoid excessive "Combined data file ..." output. [testenv:coverage-combine] # Keep base_python in-sync with .python-version-default base_python = py313 # Keep in-sync with test env definition above. depends = py3{8,13}-tests{,-colorama,-be,-rich} skip_install = true deps = coverage commands = coverage combine [testenv:coverage-report] # Keep base_python in-sync with .python-version-default base_python = py313 depends = coverage-combine parallel_show_output = true skip_install = true deps = coverage commands = coverage report [testenv:docs-{build,doctests,linkcheck}] # Keep base_python in sync with ci.yml/docs and .readthedocs.yaml. base_python = py313 dependency_groups = docs commands = # N.B. doctests is not a nitpicky as build -- we need to run both in CI! build: sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html doctests: sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs {posargs:docs/_build/}html linkcheck: sphinx-build -W -b linkcheck -d {envtmpdir}/doctrees docs docs/_build/html [testenv:docs-watch] package = editable base_python = {[testenv:docs-build]base_python} dependency_groups = {[testenv:docs-build]dependency_groups} deps = watchfiles commands = watchfiles \ --ignore-paths docs/_build/ \ 'sphinx-build -W -n --jobs auto -b html -d {envtmpdir}/doctrees docs docs/_build/html' \ src \ docs [testenv:docs-sponsors] description = Ensure sponsor logos are up to date. deps = cogapp commands = cog -rP README.md docs/index.md [testenv:pre-commit] skip_install = true deps = prek commands = prek run --all-files [testenv:typing-{pyright,ty,mypy,pyrefly}] description = Type-check the package. # Keep base_python in-sync with .python-version-default base_python = py313 dependency_groups = typing deps = pyrefly: pyrefly pyright: pyright ty: ty commands = # Mypy API checks need to happen per-Python version. mypy: mypy src pyrefly: pyrefly check pyright: pyright tests/typing ty: ty check [testenv:color-force] help = A visual check that FORCE_COLOR is working. set_env = FORCE_COLOR=1 commands = python -c "import structlog; structlog.get_logger().warning('should be colorful')" [testenv:color-no] help = A visual check that NO_COLOR is working. set_env = NO_COLOR=1 commands = python -c "import structlog; structlog.get_logger().warning('should be plain')" [testenv:docset] deps = doc2dash dependency_groups = docs allowlist_externals = rm cp tar commands = rm -rf structlog.docset structlog.tgz docs/_build sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html doc2dash --index-page index.html --icon docs/_static/docset-icon.png --icon-2x docs/_static/docset-icon@2x.png --online-redirect-url https://www.structlog.org/en/latest/ docs/_build/html tar --exclude='.DS_Store' -cvzf structlog.tgz structlog.docset structlog-25.5.0/.github/CODE_OF_CONDUCT.md0000644000000000000000000001255215077624341014734 0ustar00 # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socioeconomic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations]. [homepage]: https://www.contributor-covenant.org [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations structlog-25.5.0/.github/CONTRIBUTING.md0000644000000000000000000002162615077624341014370 0ustar00# How To Contribute > [!IMPORTANT] > We do not care how you write your code, but you're taking full responsibility for it by submitting a pull request. > Do not waste our time with LLM/"AI" slop that you don't understand yourself. ## Support In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity: help your fellow developers on [Stack Overflow](https://stackoverflow.com/questions/tagged/structlog)! The official tag is `structlog` and helping out in support frees us up to improve *structlog* instead! ## Workflow First off, thank you for considering to contribute! It's people like *you* who make this project such a great tool for everyone. - No contribution is too small! Please submit as many fixes for typos and grammar bloopers as you can! - Try to limit each pull request to *one* change only (except for typos -- please group those). - Since we squash on merge, it's up to you how you handle updates to the `main` branch. Whether you prefer to rebase on `main` or merge `main` into your branch, do whatever is more comfortable for you. Just remember to [not use your own `main` branch for the pull request](https://hynek.me/articles/pull-requests-branch/). - *Always* add tests and docs for your code. This is a hard rule; patches with missing tests or documentation won't be merged. - Consider updating [`CHANGELOG.md`](../CHANGELOG.md) to reflect the changes as observed by people *using* this library. - Make sure your changes pass our [CI](https://github.com/hynek/structlog/actions). You won't get any feedback until it's green unless you ask for it. For the CI to pass, the coverage must be 100%. If you have problems to test something, open anyway and ask for advice. In some situations, we may agree to add an `# pragma: no cover`. - Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done. - Don't break [backwards-compatibility](SECURITY.md). ## Local development environment First, **fork** the repository on GitHub and **clone** it using one of the alternatives that you can copy-paste by pressing the big green button labeled `<> Code`. You can (and should) run our test suite using [*tox*](https://tox.wiki/). However, you'll probably want a more traditional environment as well. We recommend using the Python version from the `.python-version-default` file in the project's root directory, because that's the one that is used in the CI by default, too. If you're using [*direnv*](https://direnv.net), you can automate the creation of the project virtual environment with the correct Python version by adding the following `.envrc` to the project root: ```bash layout python python$(cat .python-version-default) ``` or, if you like [*uv*](https://github.com/astral-sh/uv): ```bash test -d .venv || (uv venv --python $(cat .python-version-default) && uv pip install -e . --group dev) . .venv/bin/activate ``` > [!WARNING] > - **Before** you start working on a new pull request, use the "*Sync fork*" button in GitHub's web UI to ensure your fork is up to date. > - **Always create a new branch off `main` for each new pull request.** > Yes, you can work on `main` in your fork and submit pull requests. > But this will *inevitably* lead to you not being able to synchronize your fork with upstream and having to start over. Change into the newly created directory and after activating a virtual environment, install an editable version of this project along with its tests requirements: ```console $ pip install -e . --group dev # or `uv pip install -e . --group dev` ``` Now you can run the test suite: ```console $ python -Im pytest ``` When working on the documentation, use: ```console $ tox run -e docs-watch ``` This will build the documentation, watch for changes, and rebuild it whenever you save a file. To just build the documentation and exit immediately use: ```console $ tox run -e docs-build ``` You will find the built documentation in `docs/_build/html`. To run doctests: ```console $ tox run -e docs-doctests ``` ## Code - Obey [PEP 8](https://peps.python.org/pep-0008/) and [PEP 257](https://peps.python.org/pep-0257/). We use the `"""`-on-separate-lines style for docstrings with [Napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)-style API documentation: ```python def func(x: str, y: int) -> str: """ Do something. Args: x: A very important argument. y: Another very important argument, but its description is so long that it doesn't fit on one line. So, we start the whole block on a fresh new line to keep the block together. Returns: The result of doing something. """ ``` Please note that the API docstrings are still reStructuredText. - If you add or change public APIs, tag the docstring using `.. versionadded:: 24.1.0 WHAT` or `.. versionchanged:: 24.1.0 WHAT`. We follow CalVer, so the next version will be the current with with the middle number incremented (for example, `24.1.0` -> `24.2.0`). - We use [Ruff](https://ruff.rs/) to sort our imports and format our code with a line length of 79 characters. As long as you run our full *tox* suite before committing, or install our [*pre-commit*](https://pre-commit.com/) hooks (ideally you'll do both -- see [*Local Development Environment*](#local-development-environment) above), you won't have to spend any time on formatting your code at all. If you don't, CI will catch it for you -- but that seems like a waste of your time! ## Tests - Write your asserts as `expected == actual` to line them up nicely, and leave an empty line before them: ```python x = f() assert 42 == x.some_attribute assert "foo" == x._a_private_attribute ``` - You can run the test suite runs with all (optional) dependencies against all supported Python versions -- just as it will in our CI -- by running `tox`. - Write [good test docstrings](https://jml.io/test-docstrings/). ## Documentation - Use [semantic newlines] in [reStructuredText](https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html) (`*.rst`) and [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) (`*.md`) files: ```markdown This is a sentence. This is another sentence. This is a new paragraph. ``` - If you start a new section, add two blank lines before and one blank line after the header except if two headers follow immediately after each other: ```markdown # Main Header Last line of previous section. ## Header of New Top Section ### Header of New Section First line of new section. ``` ### Changelog If your change is interesting to end-users, there needs to be an entry in our `CHANGELOG.md`, so they can learn about it. - The changelog follows the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) standard. Add the best-fitting section if it's missing for the current release. We use the following order: `Security`, `Removed`, `Deprecated`, `Added`, `Changed`, `Fixed`. - As with other docs, please use [semantic newlines] in the changelog. - Make the last line a link to your pull request. You probably have to open it first to know the number. - Leave an empty line between entries, so it doesn't look like a wall of text. - Refer to all symbols by their fully-qualified names. For example, `structlog.Foo` -- not just `Foo`. - Wrap symbols like modules, functions, or classes into backticks, so they are rendered in a `monospace font`. - Wrap arguments into asterisks so they are *italicized* like in API documentation: `Added new argument *an_argument*.` - If you mention functions or methods, add parentheses at the end of their names: `structlog.func()` or `structlog.Class.method()`. This makes the changelog a lot more readable. - Prefer simple past tense or constructions with "now". In the `Added` section, you can leave out the "Added" prefix: ```markdown ### Added - `structlog.func()` that does foo. It's pretty cool. [#1](https://github.com/hynek/structlog/pull/1) ### Fixed - `structlog.func()` now doesn't crash the Large Hadron Collider anymore. That was a nasty bug! [#2](https://github.com/hynek/structlog/pull/2) ``` ## See you on GitHub! Again, this whole file is mainly to help you to get started by codifying tribal knowledge and expectations to save you time and turnarounds. It is **not** meant to be a barrier to entry, so don't be afraid to open half-finished PRs and ask questions if something is unclear! Please note that this project is released with a Contributor [Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. Please report any harm to [Hynek Schlawack](https://hynek.me/about/) in any way you find appropriate. [semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ structlog-25.5.0/.github/FUNDING.yml0000644000000000000000000000014715077624341013747 0ustar00--- github: hynek tidelift: pypi/structlog thanks_dev: u/gh/hynek custom: https://hynek.me/say-thanks/ structlog-25.5.0/.github/PULL_REQUEST_TEMPLATE.md0000644000000000000000000000464515077624341015742 0ustar00# Summary # Pull Request Check List - [ ] Do **not** open pull requests from your `main` branch – **use a separate branch**! - There's a ton of footguns waiting if you don't heed this warning. You can still go back to your project, create a branch from your main branch, push it, and open the pull request from the new branch. - This is not a pre-requisite for your pull request to be accepted, but **you have been warned**. - [ ] Added **tests** for changed code. - The CI fails with less than 100% coverage. - [ ] **New APIs** are added to our typing tests in [`api.py`](https://github.com/hynek/structlog/blob/main/tests/typing/api.py). - [ ] Updated **documentation** for changed code. - [ ] New functions/classes have to be added to `docs/api.rst` by hand. - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded). - The next version is the second number in the current release + 1. The first number represents the current year. So if the current version on PyPI is 23.1.0, the next version is gonna be 23.2.0. If the next version is the first in the new year, it'll be 24.1.0. - [ ] Documentation in `.rst` and `.md` files is written using [**semantic newlines**](https://rhodesmill.org/brandon/2012/one-sentence-per-line/). - [ ] Changes (and possible deprecations) are documented in the [**changelog**](https://github.com/hynek/structlog/blob/main/CHANGELOG.md). - [ ] Consider granting [push permissions to the PR branch](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork), so maintainers can fix minor issues themselves without pestering you. structlog-25.5.0/.github/SECURITY.md0000644000000000000000000000163115077624341013722 0ustar00# Security Policy ## Supported versions We are following [Calendar Versioning](https://calver.org) with generous backwards-compatibility guarantees. Therefore we only support the latest version. Put simply, you shouldn't ever be afraid to upgrade as long as you're only using our public APIs. Whenever there is a need to break compatibility, it is announced in the changelog, and raises a `DeprecationWarning` for a year (if possible) before it's finally really broken. You **can't** rely on the default settings and the `structlog.dev` module, though. They may be adjusted in the future to provide a better experience when starting to use *structlog*. So please make sure to **always** properly configure your applications. ## Reporting a vulnerability To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. structlog-25.5.0/.github/dependabot.yml0000644000000000000000000000033515077624341014761 0ustar00--- version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: monthly cooldown: default-days: 4 groups: github-actions: patterns: - "*" structlog-25.5.0/.github/workflows/build-docset.yml0000644000000000000000000000147315077624341017273 0ustar00--- name: Build docset on: push: tags: ["*"] workflow_dispatch: env: PIP_DISABLE_PIP_VERSION_CHECK: 1 PIP_NO_PYTHON_VERSION_WARNING: 1 permissions: {} jobs: docset: runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 # get correct version persist-credentials: false - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: "3.12" - run: pip install tox - run: tox run -e docset - run: tar --exclude='.DS_Store' -cvzf structlog.tgz structlog.docset - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: docset path: structlog.tgz structlog-25.5.0/.github/workflows/ci.yml0000644000000000000000000001623015077624341015305 0ustar00--- name: CI on: push: branches: [main] pull_request: workflow_dispatch: env: FORCE_COLOR: "1" # Make tools pretty. PIP_DISABLE_PIP_VERSION_CHECK: "1" PIP_NO_PYTHON_VERSION_WARNING: "1" permissions: {} jobs: build-package: name: Build & verify package runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 persist-credentials: false - uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0 id: baipp outputs: # Used to define the matrix for tests below. The value is based on # packaging metadata (trove classifiers). python-versions: ${{ steps.baipp.outputs.supported_python_classifiers_json_array }} tests: name: Tests & Mypy API on ${{ matrix.python-version }} runs-on: ubuntu-latest needs: build-package strategy: fail-fast: false matrix: # Created by the build-and-inspect-python-package action above. python-version: ${{ fromJson(needs.build-package.outputs.python-versions) }} env: PYTHON: ${{ matrix.python-version }} steps: - name: Download pre-built packages uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: Packages path: dist - run: | tar xf dist/*.tar.gz --strip-components=1 rm -rf src - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version: ${{ matrix.python-version }} allow-prereleases: true - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0 - name: Run tests run: > uvx --with tox-uv tox run --installpkg dist/*.whl -f py${PYTHON//./}-tests - name: Upload coverage data uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: coverage-data-${{ matrix.python-version }} path: .coverage.* include-hidden-files: true if-no-files-found: ignore - name: Check public API with Mypy run: > uvx --with tox-uv tox run --installpkg dist/*.whl -e py${PYTHON//./}-mypy coverage: name: Ensure 100% test coverage runs-on: ubuntu-latest needs: tests if: always() steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version-file: .python-version-default - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0 - name: Download coverage data uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: pattern: coverage-data-* merge-multiple: true - name: Combine coverage and fail if it's <100%. run: | uv tool install coverage coverage combine coverage html --skip-covered --skip-empty # Report and write to summary. coverage report --format=markdown >> $GITHUB_STEP_SUMMARY # Report again and fail if under 100%. coverage report --fail-under=100 - name: Upload HTML report if check failed. uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: html-report path: htmlcov if: ${{ failure() }} typing: name: Check types using supported type checkers runs-on: ubuntu-latest needs: build-package steps: - name: Download pre-built packages uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: Packages path: dist - run: tar xf dist/*.tar.gz --strip-components=1 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version-file: .python-version-default - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0 - run: > uvx --with tox-uv tox run -f typing docs: name: Run doctests needs: build-package runs-on: ubuntu-latest steps: - name: Download pre-built packages uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: Packages path: dist - run: tar xf dist/*.tar.gz --strip-components=1 - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: # Keep in sync with tox.ini/docs & .readthedocs.yaml python-version: "3.13" - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0 - run: > uvx --with tox-uv tox run -e docs-doctests install-dev: name: Verify dev env runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version-file: .python-version-default - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0 - run: uv venv - run: uv pip install -e . --group dev - run: .venv/bin/python -Ic 'import structlog; print(structlog.__version__)' if: runner.os != 'Windows' - run: .\.venv\Scripts\python.exe -Ic 'import structlog; print(structlog.__version__)' if: runner.os == 'Windows' # Exercise the terminal initialization that we don't test otherwise. - run: uv pip install colorama if: runner.os == 'Windows' - run: .\.venv\Scripts\python.exe -Ic 'import structlog; log = structlog.get_logger(); log.info("colors"); structlog.dev.ConsoleRenderer.get_active().colors = False; log.info("bw")' if: runner.os == 'Windows' required-checks-pass: name: Ensure everything required is passing for branch protection if: always() needs: - coverage - install-dev - typing - docs runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} colors: name: Visual check for color settings using env variables needs: build-package runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 with: python-version-file: .python-version-default - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0 - run: > uvx --with=tox-uv tox run -f color structlog-25.5.0/.github/workflows/codeql-analysis.yml0000644000000000000000000000164515077624341020006 0ustar00--- name: CodeQL on: schedule: - cron: "41 3 * * 6" permissions: contents: read jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [python] steps: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 with: languages: ${{ matrix.language }} - name: Autobuild uses: github/codeql-action/autobuild@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 structlog-25.5.0/.github/workflows/pypi-package.yml0000644000000000000000000000414215077624341017263 0ustar00--- name: Build & upload PyPI package on: push: branches: [main] tags: ["*"] release: types: - published workflow_dispatch: jobs: # Always build & lint package. build-package: name: Build & verify package runs-on: ubuntu-latest permissions: attestations: write id-token: write steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 persist-credentials: false - uses: hynek/build-and-inspect-python-package@efb823f52190ad02594531168b7a2d5790e66516 # v2.14.0 with: attest-build-provenance-github: 'true' # Upload to Test PyPI on every commit on main. release-test-pypi: name: Publish in-dev package to test.pypi.org environment: release-test-pypi if: github.repository_owner == 'hynek' && github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: build-package permissions: id-token: write steps: - name: Download packages built by build-and-inspect-python-package uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: Packages path: dist - name: Upload package to Test PyPI uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: repository-url: https://test.pypi.org/legacy/ # Upload to real PyPI on GitHub Releases. release-pypi: name: Publish released package to pypi.org environment: release-pypi if: github.repository_owner == 'hynek' && github.event.action == 'published' runs-on: ubuntu-latest needs: build-package permissions: id-token: write steps: - name: Download packages built by build-and-inspect-python-package uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: name: Packages path: dist - name: Upload package to PyPI uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 structlog-25.5.0/.github/workflows/zizmor.yml0000644000000000000000000000207115077624341016242 0ustar00# https://github.com/woodruffw/zizmor name: Zizmor on: push: branches: ["main"] pull_request: branches: ["*"] permissions: contents: read jobs: zizmor: name: Zizmor latest via PyPI runs-on: ubuntu-latest permissions: security-events: write steps: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - uses: hynek/setup-cached-uv@757bedc3f972eb7227a1aa657651f15a8527c817 # v2.3.0 - name: Run zizmor 🌈 run: uvx zizmor --format sarif . > results.sarif env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload SARIF file uses: github/codeql-action/upload-sarif@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0 with: # Path to SARIF file relative to the root of the repository sarif_file: results.sarif # Optional category for the results # Used to differentiate multiple results for one commit category: zizmor structlog-25.5.0/docs/Makefile0000644000000000000000000001271315077624341013164 0ustar00# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -n -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/structlog.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/structlog.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/structlog" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/structlog" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." structlog-25.5.0/docs/api.rst0000644000000000000000000002600315077624341013024 0ustar00.. _api: API Reference ============= .. note:: The examples here use a very simplified configuration using the minimalist `structlog.processors.KeyValueRenderer` for brevity and to enable doctests. The output is going to be different (nicer!) with the default configuration. .. testsetup:: * import structlog structlog.configure( processors=[structlog.processors.KeyValueRenderer()], ) .. testcleanup:: * import structlog structlog.reset_defaults() .. module:: structlog `structlog` Package ------------------- .. autofunction:: get_logger .. autofunction:: getLogger .. autofunction:: wrap_logger .. autofunction:: configure .. autofunction:: configure_once .. autofunction:: reset_defaults .. autofunction:: is_configured .. autofunction:: get_config .. autoclass:: BoundLogger :members: new, bind, unbind .. autofunction:: make_filtering_bound_logger .. autofunction:: get_context .. autoclass:: PrintLogger :members: msg, err, debug, info, warning, error, critical, log, failure, fatal .. autoclass:: PrintLoggerFactory .. autoclass:: WriteLogger :members: msg, err, debug, info, warning, error, critical, log, failure, fatal .. autoclass:: WriteLoggerFactory .. autoclass:: BytesLogger :members: msg, err, debug, info, warning, error, critical, log, failure, fatal .. autoclass:: BytesLoggerFactory .. autoexception:: DropEvent .. autoclass:: BoundLoggerBase :members: new, bind, unbind, try_unbind, _logger, _process_event, _proxy_to_logger `structlog.dev` Module ---------------------- .. automodule:: structlog.dev .. autoclass:: ConsoleRenderer :members: get_default_level_styles, get_default_column_styles, exception_formatter, sort_keys, columns, get_active, event_key, timestamp_key, level_styles,colors, force_colors, repr_native_str, pad_level, pad_event_to .. autoclass:: ColumnStyles .. autoexception:: NoConsoleRendererConfiguredError .. autoexception:: MultipleConsoleRenderersConfiguredError .. autoclass:: Column .. autoclass:: ColumnFormatter(typing.Protocol) :members: __call__ .. autoclass:: KeyValueColumnFormatter .. autoclass:: LogLevelColumnFormatter .. autofunction:: plain_traceback .. autoclass:: RichTracebackFormatter .. autofunction:: rich_traceback .. autofunction:: better_traceback .. autofunction:: set_exc_info `structlog.testing` Module -------------------------- .. automodule:: structlog.testing .. autofunction:: capture_logs .. autoclass:: LogCapture .. autoclass:: CapturingLogger >>> from pprint import pprint >>> cl = structlog.testing.CapturingLogger() >>> cl.info("hello") >>> cl.info("hello", when="again") >>> pprint(cl.calls) [CapturedCall(method_name='info', args=('hello',), kwargs={}), CapturedCall(method_name='info', args=('hello',), kwargs={'when': 'again'})] .. autoclass:: CapturingLoggerFactory .. autoclass:: CapturedCall .. autoclass:: ReturnLogger :members: msg, err, debug, info, warning, error, critical, log, failure, fatal .. autoclass:: ReturnLoggerFactory `structlog.contextvars` Module ------------------------------ .. automodule:: structlog.contextvars .. autofunction:: bind_contextvars .. autofunction:: bound_contextvars .. autofunction:: get_contextvars .. autofunction:: get_merged_contextvars .. autofunction:: merge_contextvars .. autofunction:: clear_contextvars .. autofunction:: unbind_contextvars .. autofunction:: reset_contextvars `structlog.threadlocal` Module ------------------------------ .. automodule:: structlog.threadlocal :noindex: .. _procs: `structlog.processors` Module ----------------------------- .. automodule:: structlog.processors .. autoclass:: JSONRenderer .. doctest:: >>> from structlog.processors import JSONRenderer >>> JSONRenderer(sort_keys=True)(None, "", {"a": 42, "b": [1, 2, 3]}) '{"a": 42, "b": [1, 2, 3]}' Bound objects are attempted to be serialize using a ``__structlog__`` method. If none is defined, ``repr()`` is used: .. doctest:: >>> class C1: ... def __structlog__(self): ... return ["C1!"] ... def __repr__(self): ... return "__structlog__ took precedence" >>> class C2: ... def __repr__(self): ... return "No __structlog__, so this is used." >>> from structlog.processors import JSONRenderer >>> JSONRenderer(sort_keys=True)(None, "", {"c1": C1(), "c2": C2()}) '{"c1": ["C1!"], "c2": "No __structlog__, so this is used."}' Please note that additionally to strings, you can also return any type the standard library JSON module knows about -- like in this example a list. If you choose to pass a *default* parameter as part of *dumps_kw*, support for ``__structlog__`` is disabled. That can be useful with more elegant serialization methods like `functools.singledispatch`: `Better Python Object Serialization `_. It can also be helpful if you are using *orjson* and want to rely on it to serialize `datetime.datetime` and other objects natively. .. tip:: If you use this processor, you may also wish to add structured tracebacks for exceptions. You can do this by adding the :class:`~structlog.processors.dict_tracebacks` to your list of processors: .. doctest:: >>> structlog.configure( ... processors=[ ... structlog.processors.dict_tracebacks, ... structlog.processors.JSONRenderer(), ... ], ... ) >>> log = structlog.get_logger() >>> var = "spam" >>> try: ... 1 / 0 ... except ZeroDivisionError: ... log.exception("Cannot compute!") {"event": "Cannot compute!", "exception": [{"exc_type": "ZeroDivisionError", "exc_value": "division by zero", "exc_notes": [], "syntax_error": null, "is_cause": false, "frames": [{"filename": "", "lineno": 2, "name": "", "locals": {..., "var": "'spam'"}}], "is_group": false, "exceptions": []}]} .. autoclass:: KeyValueRenderer .. doctest:: >>> from structlog.processors import KeyValueRenderer >>> KeyValueRenderer(sort_keys=True)(None, "", {"a": 42, "b": [1, 2, 3]}) 'a=42 b=[1, 2, 3]' >>> KeyValueRenderer(key_order=["b", "a"])(None, "", ... {"a": 42, "b": [1, 2, 3]}) 'b=[1, 2, 3] a=42' .. autoclass:: LogfmtRenderer .. doctest:: >>> from structlog.processors import LogfmtRenderer >>> event_dict = {"a": 42, "b": [1, 2, 3], "flag": True} >>> LogfmtRenderer(sort_keys=True)(None, "", event_dict) 'a=42 b="[1, 2, 3]" flag' >>> LogfmtRenderer(key_order=["b", "a"], bool_as_flag=False)(None, "", event_dict) 'b="[1, 2, 3]" a=42 flag=true' .. autoclass:: EventRenamer .. autofunction:: add_log_level .. autoclass:: UnicodeDecoder .. autoclass:: UnicodeEncoder .. autoclass:: ExceptionRenderer .. autofunction:: format_exc_info .. doctest:: >>> from structlog.processors import format_exc_info >>> try: ... raise ValueError ... except ValueError: ... format_exc_info(None, "", {"exc_info": True}) # doctest: +ELLIPSIS {'exception': 'Traceback (most recent call last):... .. autofunction:: dict_tracebacks .. doctest:: >>> from structlog.processors import dict_tracebacks >>> try: ... raise ValueError("onoes") ... except ValueError: ... dict_tracebacks(None, "", {"exc_info": True}) # doctest: +ELLIPSIS {'exception': [{'exc_type': 'ValueError', 'exc_value': 'onoes', ..., 'frames': [{'filename': ... .. autoclass:: StackInfoRenderer .. autoclass:: ExceptionPrettyPrinter .. autoclass:: TimeStamper .. doctest:: >>> from structlog.processors import TimeStamper >>> TimeStamper()(None, "", {}) # doctest: +SKIP {'timestamp': 1378994017} >>> TimeStamper(fmt="iso")(None, "", {}) # doctest: +SKIP {'timestamp': '2013-09-12T13:54:26.996778Z'} >>> TimeStamper(fmt="%Y", key="year")(None, "", {}) # doctest: +SKIP {'year': '2013'} .. autoclass:: MaybeTimeStamper .. doctest:: >>> from structlog.processors import MaybeTimeStamper >>> MaybeTimeStamper()(None, "", {}) # doctest: +SKIP {'timestamp': 1690036074.494428} >>> MaybeTimeStamper()(None, "", {"timestamp": 42}) {'timestamp': 42} .. autoclass:: CallsiteParameter :members: .. autoclass:: CallsiteParameterAdder `structlog.stdlib` Module ------------------------- .. automodule:: structlog.stdlib .. autofunction:: recreate_defaults .. autofunction:: get_logger .. autoclass:: BoundLogger :members: bind, unbind, try_unbind, new, debug, info, warning, warn, error, critical, exception, log, adebug, ainfo, awarning, aerror, acritical, aexception, alog .. autoclass:: AsyncBoundLogger :members: sync_bl .. autoclass:: LoggerFactory :members: __call__ .. autofunction:: render_to_log_args_and_kwargs .. autofunction:: render_to_log_kwargs .. autofunction:: filter_by_level .. autofunction:: add_log_level .. autofunction:: add_log_level_number .. autofunction:: add_logger_name .. autoclass:: ExtraAdder .. autoclass:: PositionalArgumentsFormatter .. autoclass:: ProcessorFormatter :members: wrap_for_formatter, remove_processors_meta `structlog.tracebacks` Module ----------------------------- .. automodule:: structlog.tracebacks .. autofunction:: extract .. autoclass:: ExceptionDictTransformer .. autoclass:: Trace .. autoclass:: Stack .. autoclass:: Frame .. autoclass:: SyntaxError_ `structlog.typing` Module ------------------------- .. automodule:: structlog.typing .. autoclass:: BindableLogger Additionally to the methods listed below, bound loggers **must** have a ``__init__`` method with the following signature: .. method:: __init__(self, wrapped_logger: WrappedLogger, processors: Iterable[Processor], context: Context) -> None :noindex: Unfortunately it's impossible to define initializers using :pep:`544` Protocols. They currently also have to carry a `Context` as a ``_context`` attribute. .. note:: Currently Sphinx has no support for Protocols, so please click ``[source]`` for this entry to see the full definition. .. autoclass:: FilteringBoundLogger .. note:: Currently Sphinx has no support for Protocols, so please click ``[source]`` for this entry to see the full definition. .. autoclass:: ExceptionTransformer .. note:: Currently Sphinx has no support for Protocols, so please click ``[source]`` for this entry to see the full definition. .. autodata:: EventDict .. autodata:: WrappedLogger .. autodata:: Processor .. autodata:: Context .. autodata:: ExcInfo .. autodata:: ExceptionRenderer `structlog.twisted` Module -------------------------- .. automodule:: structlog.twisted .. autoclass:: BoundLogger :members: bind, unbind, new, msg, err .. autoclass:: LoggerFactory :members: __call__ .. autoclass:: EventAdapter .. autoclass:: JSONRenderer .. autofunction:: plainJSONStdOutLogger .. autofunction:: JSONLogObserverWrapper .. autoclass:: PlainFileLogObserver structlog-25.5.0/docs/bound-loggers.md0000644000000000000000000002100215077624341014604 0ustar00# Bound Loggers The centerpiece of *structlog* that you will interact with most is called a *bound logger*. It's what you get back from {func}`structlog.get_logger()` and it's called a *bound logger* because you can *bind* key-value pairs to it. As far as *structlog* is concerned, it consists of three parts: ```{image} _static/BoundLogger.svg ``` 1. A *context dictionary* that you can *bind* key-value pairs to. This dictionary is *merged* into each log entry that is logged from *this logger specifically*. You can inspect a context of a *bound logger* by calling {func}`structlog.get_context()` on it. 2. A list of {doc}`processors ` that are called on every log entry. Each processor receives the return value of its predecessor passed as an argument. This list is usually set using {doc}`configuration`. 3. And finally a *logger* that it's wrapping. This wrapped logger is responsible for the *output* of the log entry that has been returned by the last processor. This *can* be standard library's {class}`logging.Logger` like in the image above, but absolutely doesn't have to: By default it's *structlog*'s {class}`~structlog.PrintLogger`. This wrapped logger also is usually set using {doc}`configuration`. :::{important} Bound loggers themselves do *not* do any I/O themselves. All they do is manage the *context* and proxy log calls to a *wrapped logger*. ::: ## Context To manipulate the context dictionary, a *bound logger* can: - Recreate itself with (optional) *additional* context data: {func}`~structlog.BoundLoggerBase.bind` and {func}`~structlog.BoundLoggerBase.new`. - Recreate itself with *less* context data: {func}`~structlog.BoundLoggerBase.unbind` and {func}`~structlog.BoundLoggerBase.try_unbind`. In any case, the original bound logger or its context are never mutated. They always return a *copy* of the bound logger with a *new* context that reflects your changes. This part of the API is defined in the {class}`typing.Protocol` called {class}`structlog.typing.BindableLogger`. The protocol is marked {func}`typing.runtime_checkable` which means that you can check an object for being a *bound logger* using `isinstance(obj, structlog.typing.BindableLogger)`. ## Output Finally, a *bound logger* also **indirectly** exposes the logging methods of the *wrapped logger*. By default, that's a {class}`~structlog.typing.FilteringBoundLogger` that is wrapping a {class}`~structlog.PrintLogger`. They both share the set of log methods that's present in the standard library: `debug()`, `info()`, `warning()`, `error()`, and `critical()`. Whenever you call one of those methods on the *bound logger*, it will: 1. Make a copy of its context -- now it becomes the *event dictionary*, 2. Add the keyword arguments of the method call to the event dict. 3. Add a new key `event` with the value of the first positional argument of the method call to the event dict. 4. Run the processors successively on the event dict. Each processor receives the result of its predecessor. 5. Finally, it takes the result of the final processor and calls the method with the same name – that got called on the *bound logger* – on the wrapped logger. For flexibility, the final processor can return either a string[^str] that is passed directly as a positional parameter, or a tuple `(args, kwargs)` that are passed as `wrapped_logger.log_method(*args, **kwargs)`. [^str]: {any}`str`, {any}`bytes`, or {any}`bytearray` to be exact. ### Step-by-Step example Assuming you've left the default configuration and have: ```python import structlog logger = structlog.get_logger() log = logger.bind(foo="bar") ``` Now, `log` is a *bound logger* of type {class}`~structlog.typing.FilteringBoundLogger` (but in the default config there's no filtering). `log`'s context is `{"foo": "bar"}` and its wrapped logger is a {class}`structlog.PrintLogger`. If you call `log.info("Hello, %s!", "world", number=42)` now, the following happens: 1. `"world"` gets interpolated into `"Hello, %s!"`, making the event "Hello, world!"[^interpolation]. 2. The *bound logger*'s context gets copied and the key-value pairs from the `info` call are added to it. It becomes an *event dict* and is `{"foo": "bar", "number": 42}` now. 3. The event from step 1 is added too. The *event dict* is `{"foo": "bar", "number": 42, "event": "Hello, world!"}` now. 4. The *event dict* is fed into the [processor chain](processors.md). In this case the processors add a timestamp and the log level name to the *event dict*. Before it hits the last processor, the *event dict* looks something like `{"foo": "bar", "number": 42, "event": "Hello, world!", "level": "info", "timestamp": "2022-10-13 16:29:27"}`. The last processor is {class}`structlog.dev.ConsoleRenderer` and renders the *event dict* into a colorful string[^json]. 5. Finally, the *wrapped logger*'s (a {class}`~structlog.PrintLogger`) `info()` method is called with that string. [^json]: Until this very step, the *event dict* was a dictionary. By replacing the last processor, you decide on the **format** of your logs. For example, if you wanted JSON logs, you just have to replace the last processor with {class}`structlog.processors.JSONRenderer`. [^interpolation]: String interpolation only takes place if you pass positional arguments. If the first and only argument is a mapping, it will be used for dict-based interpolation. (filtering)= ## Filtering by log levels Filtering based on log levels can be done in a processor very easily[^stdlib], however that means unnecessary performance overhead through function calls. We care a lot about performance and that's why *structlog*'s default *bound logger* class implements level-filtering as close to the users as possible: in the *bound logger*'s logging methods *before* even creating an *event dict* and starting the processor chain. {func}`structlog.make_filtering_bound_logger` allows you to create a *bound logger* whose log methods with a log level beneath the configured one consist of a plain `return None`. Here's an example: ```pycon >>> import structlog >>> logger = structlog.get_logger() >>> logger.debug("hi!") 2022-10-15 11:39:03 [debug ] hi! >>> import logging >>> structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(logging.INFO)) >>> logger.debug("hi!") # no output! ``` In this example, we first log out using the default logger that doesn't filter at all. Then we change the configuration to filtering at the info level and try again: no log output! Let's have a look at the `debug` method: ```pycon >>> import inspect >>> print(inspect.getsource(logger.debug)) def _nop(self: Any, event: str, **kw: Any) -> Any: return None ``` This is as effective as it gets and usually as flexible as the vast majority of users need. :::{important} *structlog* uses the constants from {mod}`logging`, but does **not** share any code. Passing `20` instead of `logging.INFO` would have worked too. ::: [^stdlib]: And it's in fact supported for standard library logging with the {func}`structlog.stdlib.filter_by_level` processor. ## Wrapping loggers manually In practice, you won't be instantiating bound loggers yourself. You will configure *structlog* as explained in the {doc}`next chapter ` and then just call {func}`structlog.get_logger`. However, in some rare cases you may not want to do that. For example because you don't control how you get the logger that you would like to wrap (famous example: Celery). For that times there is the {func}`structlog.wrap_logger` function that can be used to wrap a logger -- optionally without any global state (in other words, configuration): (proc)= ```{doctest} >>> import structlog >>> class CustomPrintLogger: ... def msg(self, message): ... print(message) >>> def proc(logger, method_name, event_dict): ... print("I got called with", event_dict) ... return repr(event_dict) >>> log = structlog.wrap_logger( ... CustomPrintLogger(), ... wrapper_class=structlog.BoundLogger, ... processors=[proc], ... ) >>> log2 = log.bind(x=42) >>> log == log2 False >>> log.msg("hello world") I got called with {'event': 'hello world'} {'event': 'hello world'} >>> log2.msg("hello world") I got called with {'x': 42, 'event': 'hello world'} {'x': 42, 'event': 'hello world'} >>> log3 = log2.unbind("x") >>> log == log3 True >>> log3.msg("nothing bound anymore", foo="but you can structure the event too") I got called with {'foo': 'but you can structure the event too', 'event': 'nothing bound anymore'} {'foo': 'but you can structure the event too', 'event': 'nothing bound anymore'} ``` structlog-25.5.0/docs/conf.py0000644000000000000000000001055715077624341013027 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import os from importlib import metadata # Set canonical URL from the Read the Docs Domain html_baseurl = os.environ.get("READTHEDOCS_CANONICAL_URL", "") # Tell Jinja2 templates the build is running on Read the Docs if os.environ.get("READTHEDOCS", "") == "True": html_context = {"READTHEDOCS": True} # We want an image in the README and include the README in the docs. suppress_warnings = ["image.nonlocal_uri"] # -- General configuration ---------------------------------------------------- extensions = [ "myst_parser", "notfound.extension", "sphinx.ext.autodoc", "sphinx.ext.autodoc.typehints", "sphinx.ext.napoleon", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.viewcode", "sphinxcontrib.mermaid", "sphinxext.opengraph", "sphinx_copybutton", ] myst_enable_extensions = [ "colon_fence", "smartquotes", "deflist", ] mermaid_init_js = "mermaid.initialize({startOnLoad:true,theme:'neutral'});" ogp_image = "_static/structlog_logo.png" # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = [".rst", ".md"] # The master toctree document. master_doc = "index" # General information about the project. project = "structlog" author = "Hynek Schlawack" copyright = f"2013, {author}" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # The full version, including alpha/beta/rc tags. release = metadata.version("structlog") # The short X.Y version. version = release.rsplit(".", 1)[0] if "dev" in release: release = version = "UNRELEASED" exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. default_role = "any" nitpick_ignore = [ ("py:class", "Context"), ("py:class", "EventDict"), ("py:class", "ILogObserver"), ("py:class", "PlainFileObserver"), ("py:class", "Processor"), ("py:class", "Styles"), ("py:class", "structlog.dev._Styles"), ("py:class", "WrappedLogger"), ("py:class", "structlog.threadlocal.TLLogger"), ("py:class", "structlog.typing.EventDict"), ("py:class", "ModuleType"), ] # If true, '()' will be appended to :func: etc. cross-reference text. add_function_parentheses = True # Move type hints into the description block, instead of the func definition. autodoc_typehints = "description" autodoc_typehints_description_target = "documented" # -- Options for HTML output -------------------------------------------------- html_theme = "sphinx_book_theme" html_theme_options = {} html_logo = "_static/structlog_logo.svg" html_static_path = ["_static"] html_css_files = ["custom.css"] htmlhelp_basename = "structlogdoc" latex_documents = [ ("index", "structlog.tex", "structlog Documentation", "Author", "manual") ] # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [("index", "structlog", "structlog Documentation", ["Author"], 1)] # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( "index", "structlog", "structlog Documentation", "Author", "structlog", "One line description of project.", "Miscellaneous", ) ] # -- Options for Epub output -------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # GitHub has rate limits linkcheck_ignore = [ r"https://github.com/.*/(issues|pull|compare)/\d+", r"https://twitter.com/.*", ] # Twisted's trac tends to be slow linkcheck_timeout = 300 intersphinx_mapping = { "python": ("https://docs.python.org/3", None), "rich": ("https://rich.readthedocs.io/en/stable/", None), } structlog-25.5.0/docs/configuration.md0000644000000000000000000001154315077624341014715 0ustar00# Configuration The focus of *structlog* has always been to be flexible to a fault. The goal is that a user can use it with *any* logger of their own that is wrapped by *structlog*. That's the reason why there's an overwhelming amount of knobs to tweak, but – ideally – once you find your configuration, you don't touch it ever again and, more importantly: don't see any of it in your application code. --- Let's start at the end and introduce the ultimate convenience function that relies purely on configuration: {func}`structlog.get_logger`. The goal is to reduce your per-file application logging boilerplate to: ``` import structlog logger = structlog.get_logger() ``` To that end, you'll have to call {func}`structlog.configure` on app initialization. You can call {func}`structlog.configure` repeatedly and only set one or more settings -- the rest will not be affected. If necessary, you can always reset your global configuration back to default values using {func}`structlog.reset_defaults`. That can be handy in tests. At any time, you can check whether and how *structlog* is configured using {func}`structlog.is_configured` and {func}`structlog.get_config`}: ```pycon >>> structlog.is_configured() False >>> structlog.configure(logger_factory=structlog.stdlib.LoggerFactory()) >>> structlog.is_configured() True >>> cfg = structlog.get_config() >>> cfg["logger_factory"] ``` :::{important} Since you'll call {func}`structlog.get_logger` in module scope, it runs at import time *before* you had a chance to configure *structlog*. Therefore it returns a **lazy proxy** that returns a correctly configured *bound logger* on its first call to one of the context-managing methods like `bind()`. Thus, you must never call `new()` or `bind()` in module or class scope because , you will receive a logger configured with *structlog*'s default values. Use {func}`~structlog.get_logger`'s `initial_values` to achieve pre-populated contexts. To enable you to log with the module-global logger, it will create a temporary *bound logger* **on each call**. Therefore if you have nothing to bind but intend to do lots of log calls in a function, it makes sense performance-wise to create a local logger by calling `bind()` or `new()` without any parameters. See also {doc}`performance`. ::: ## What to configure You can find the details in the API documentation of {func}`structlog.configure`, but let's introduce the most important ones at a high level first. ### Wrapper classes You've met {doc}`bound-loggers` in the last chapter. They're the objects returned by {func}`~structlog.get_logger` and allow to bind key-value pairs into their private context. You can configure their type using the `wrapper_class` keyword. Whenever you bind or unbind data to a *bound logger*, this class is instantiated with the new context and returned. ### Logger factories We've already talked about wrapped loggers responsible for the output, but we haven't explained where they come from until now. Unlike with *bound loggers*, you often need more flexibility when instantiating them. Therefore you don't configure a class; you configure a *factory* using the `logger_factory` keyword. It's a callable that returns the logger that gets wrapped and returned. In the simplest case, it's a function that returns a logger -- or just a class. But you can also pass in an instance of a class with a `__call__` method for more complicated setups. The arguments you pass to `structlog.get_logger()` will be passed to the logger factory. For example, if you use `structlog.get_logger("a name")` and configure *structlog* to use the standard library {class}`~structlog.stdlib.LoggerFactory`, which has support for positional parameters, the returned logger will have the name `"a name"`. For the common cases of standard library logging and Twisted logging, *structlog* comes with two factories built right in: - {class}`structlog.stdlib.LoggerFactory` - {class}`structlog.twisted.LoggerFactory` So all it takes to use standard library {mod}`logging` for output is: ``` >>> from structlog import get_logger, configure >>> from structlog.stdlib import LoggerFactory >>> configure(logger_factory=LoggerFactory()) >>> log = get_logger() >>> log.critical("this is too easy!") event='this is too easy!' ``` By using *structlog*'s {class}`structlog.stdlib.LoggerFactory`, it is also ensured that variables like function names and line numbers are expanded correctly in your log format. See {doc}`standard-library` for more details. Calling {func}`structlog.get_logger` without configuration gives you a perfectly useful {class}`structlog.PrintLogger`. We don't believe silent loggers are a sensible default. ### Processors You will meet {doc}`processors` in the next chapter. They are configured using the `processors` keyword that takes an {class}`~collections.abc.Iterable` of callables that act as processors. structlog-25.5.0/docs/console-output.md0000644000000000000000000001624215077624341015047 0ustar00# Console Output To make development a more pleasurable experience, *structlog* comes with the {mod}`structlog.dev` module. The highlight is {class}`structlog.dev.ConsoleRenderer` that offers nicely aligned and colorful[^win] console output. [^win]: Requires the [Colorama package](https://pypi.org/project/colorama/) on Windows. If either of the [Rich](https://rich.readthedocs.io/) or [*better-exceptions*](https://github.com/Qix-/better-exceptions) packages is installed, it will also pretty-print exceptions with helpful contextual data. Rich takes precedence over *better-exceptions*, but you can configure it by passing {func}`structlog.dev.plain_traceback` or {func}`structlog.dev.better_traceback` for the `exception_formatter` parameter of {class}`~structlog.dev.ConsoleRenderer`. The following output is rendered using Rich: ```{figure} _static/console_renderer.png :alt: Screenshot of colorful console output by ConsoleRenderer. Colorful console output by ConsoleRenderer. ``` You can find the code for the output above [in the repo](https://github.com/hynek/structlog/blob/main/show_off.py). To use it, just add it as a renderer to your processor chain. It will recognize logger names, log levels, time stamps, stack infos, and `exc_info` as produced by *structlog*'s processors and render them in special ways. :::{warning} For pretty exceptions to work, {func}`~structlog.processors.format_exc_info` must be **absent** from the processors chain. ::: *structlog*'s default configuration already uses {class}`~structlog.dev.ConsoleRenderer`, therefore if you want nice colorful output on the console, you don't have to do anything except installing Rich or *better-exceptions* (and Colorama on Windows). If you want to use it along with standard library logging, there's the {func}`structlog.stdlib.recreate_defaults` helper. :::{seealso} {doc}`exceptions` for more information on how to configure exception rendering. For the console and beyond. ::: (columns-config)= ## Console output configuration Since {class}`~structlog.dev.ConsoleRenderer` is mainly a development helper, it is less strict about immutability than the rest of *structlog* for better ergonomics. Notably, the currently active instance can be obtained by calling {meth}`ConsoleRenderer.get_active() ` and it offers properties to configure its behavior after instantiation. :::{important} Roughly speaking, there are two ways to configure console output. 1. Using our defaults and tweak some settings. 2. Explicit columns configuration. You should pick one and not mix the two. For example, if you pass *columns* to {class}`~structlog.dev.ConsoleRenderer`, all other output-related parameters are stored but otherwise ignored. But if you set one of the non-*columns* properties, the columns will be reconfigured accordingly and *your* columns settings will be lost. ::: ### Defaults plus tweaking The easier way where you're mostly using our defaults and just tweak a few things here and there by passing arguments or setting properties (as of 25.5.0). For example, you can easily change the colors of the the log levels by passing the *level_styles*[^styles] parameter or switch *colors* on and off completely. [^styles]: When the API talks about "styles", it means ANSI control strings. You can find them, for example, in [Colorama](https://github.com/tartley/colorama). You can find the relevant arguments and properties in the {class}`~structlog.dev.ConsoleRenderer` documentation. ### Explicit columns configuration The more flexible way is to configure *everything* by explicitly defining the columns: Colors, order, and how values are stringified. This is the way *structlog* represents the output configuration internally, too. For that {class}`~structlog.dev.ConsoleRenderer` accepts the *columns* parameter that takes a list of {class}`~structlog.dev.Column`s. Once you pass the *columns* parameter, all other output-related parameters are ignored. It allows you to assign a formatter to each key and a default formatter for the rest (by passing an empty key name). The order of the column definitions is the order in which the columns are rendered; the rest is -- depending on the *sort_keys* argument to {class}`~structlog.dev.ConsoleRenderer` -- either sorted alphabetically or in the order of the keys in the event dictionary. You can use a column definition to drop a key-value pair from the output by returning an empty string from the formatter. It's best demonstrated by an example: ```python import structlog import colorama cr = structlog.dev.ConsoleRenderer( columns=[ # Render the timestamp without the key name in yellow. structlog.dev.Column( "timestamp", structlog.dev.KeyValueColumnFormatter( key_style=None, value_style=colorama.Fore.YELLOW, reset_style=colorama.Style.RESET_ALL, value_repr=str, ), ), # Render the event without the key name in bright magenta. structlog.dev.Column( "event", structlog.dev.KeyValueColumnFormatter( key_style=None, value_style=colorama.Style.BRIGHT + colorama.Fore.MAGENTA, reset_style=colorama.Style.RESET_ALL, value_repr=str, ), ), # Default formatter for all keys not explicitly mentioned. The key is # cyan, the value is green. structlog.dev.Column( "", structlog.dev.KeyValueColumnFormatter( key_style=colorama.Fore.CYAN, value_style=colorama.Fore.GREEN, reset_style=colorama.Style.RESET_ALL, value_repr=str, ), ), ] ) structlog.configure(processors=structlog.get_config()["processors"][:-1]+[cr]) ``` You can also access and configure the columns of the active console renderer: ```python cr = structlog.dev.ConsoleRenderer.get_active() cr.columns = [ ... ] ``` ## Standard environment variables *structlog*'s default configuration uses colors if standard out is a TTY (that is, an interactive session) and is able to do so (that is, Colorama is installed on Windows). It's possible to override this behavior by setting two standard environment variables to any value except an empty string: - `FORCE_COLOR` *activates* colors, regardless of where output is going. - [`NO_COLOR`](https://no-color.org) *disables* colors, regardless of where the output is going and regardless the value of `FORCE_COLOR`. Please note that `NO_COLOR` disables _all_ styling, including bold and italics. ## Disabling exception pretty-printing If you prefer the default terse Exception rendering, but still want Rich installed, you can disable the auto-enabled pretty-printing by configuring your {class}`~structlog.dev.ConsoleRenderer` to use {class}`structlog.dev.plain_traceback`. You can either instantiate {class}`structlog.dev.ConsoleRenderer()` yourself and pass `exception_formatter=structlog.dev.plain_traceback`, or set the `exception_formatter` attribute of the active console renderer to it: ```python cr = structlog.dev.ConsoleRenderer.get_active() cr.exception_formatter = structlog.dev.plain_traceback ``` structlog-25.5.0/docs/contextvars.md0000644000000000000000000001516615077624341014433 0ustar00(contextvars)= # Context Variables ```{testsetup} import structlog ``` ```{testcleanup} import structlog structlog.reset_defaults() ``` The {mod}`contextvars` module in the Python standard library allows having a global *structlog* context that is local to the current execution context. The execution context can be thread-local if using threads, stored in the {mod}`asyncio` event loop, or [*greenlet*](https://greenlet.readthedocs.io/) respectively. For example, you may want to bind certain values like a request ID or the peer's IP address at the beginning of a web request and have them logged out along with the local contexts you build within our views. For that *structlog* provides the {mod}`structlog.contextvars` module with a set of functions to bind variables to a context-local context. This context is safe to be used both in threaded as well as asynchronous code. :::{warning} Since the storage mechanics of your context variables is different for each concurrency method, they are _isolated_ from each other. This can be a problem in hybrid applications like those based on [Starlette](https://www.starlette.io) (this [includes FastAPI](https://github.com/fastapi/fastapi/discussions/5999)) where context variables set in a synchronous context don't appear in logs from an async context and vice versa. ::: The general flow is: - Use {func}`structlog.configure` with {func}`structlog.contextvars.merge_contextvars` as your first processor (part of default configuration). - Call {func}`structlog.contextvars.clear_contextvars` at the beginning of your request handler (or whenever you want to reset the context-local context). - Call {func}`structlog.contextvars.bind_contextvars` and {func}`structlog.contextvars.unbind_contextvars` instead of your bound logger's `bind()` and `unbind()` when you want to bind and unbind key-value pairs to the context-local context. You can also use the {func}`structlog.contextvars.bound_contextvars` context manager / decorator. - Use *structlog* as normal. Loggers act as they always do, but the {func}`structlog.contextvars.merge_contextvars` processor ensures that any context-local binds get included in all of your log messages. - If you want to access the context-local storage, you use {func}`structlog.contextvars.get_contextvars` and {func}`structlog.contextvars.get_merged_contextvars`. We're sorry the word *context* means three different things in this itemization depending on ... context. ```{doctest} >>> from structlog.contextvars import ( ... bind_contextvars, ... bound_contextvars, ... clear_contextvars, ... merge_contextvars, ... unbind_contextvars, ... ) >>> from structlog import configure >>> configure( ... processors=[ ... merge_contextvars, ... structlog.processors.KeyValueRenderer(key_order=["event", "a"]), ... ] ... ) >>> log = structlog.get_logger() >>> # At the top of your request handler (or, ideally, some general >>> # middleware), clear the contextvars-local context and bind some common >>> # values: >>> clear_contextvars() >>> bind_contextvars(a=1, b=2) {'a': at ...>, 'b': at ...>} >>> # Then use loggers as per normal >>> # (perhaps by using structlog.get_logger() to create them). >>> log.info("hello") event='hello' a=1 b=2 >>> # Use unbind_contextvars to remove a variable from the context. >>> unbind_contextvars("b") >>> log.info("world") event='world' a=1 >>> # You can also bind key-value pairs temporarily. >>> with bound_contextvars(b=2): ... log.info("hi") event='hi' a=1 b=2 >>> # Now it's gone again. >>> log.info("hi") event='hi' a=1 >>> # And when we clear the contextvars state again, it goes away. >>> # a=None is printed due to the key_order argument passed to >>> # KeyValueRenderer, but it is NOT present anymore. >>> clear_contextvars() >>> log.info("hi there") event='hi there' a=None ``` ## Support for `contextvars.Token` If, for example, your request handler calls a helper function that needs to temporarily override some contextvars before restoring them back to their original values, you can use the {class}`~contextvars.Token`s returned by {func}`~structlog.contextvars.bind_contextvars` along with {func}`~structlog.contextvars.reset_contextvars` to accomplish this (much like how {meth}`contextvars.ContextVar.reset` works): ```python def foo(): bind_contextvars(a=1) _helper() log.info("a is restored!") # a=1 def _helper(): tokens = bind_contextvars(a=2) log.info("a is overridden") # a=2 reset_contextvars(**tokens) ``` (flask-example)= ## Example: Flask and thread-local data Let's assume you want to bind a unique request ID, the URL path, and the peer's IP to every log entry by storing it in thread-local storage that is managed by context variables: ```python import logging import sys import uuid import flask from .some_module import some_function import structlog logger = structlog.get_logger() app = flask.Flask(__name__) @app.route("/login", methods=["POST", "GET"]) def some_route(): # You would put this into some kind of middleware or processor so it's set # automatically for all requests in all views. structlog.contextvars.clear_contextvars() structlog.contextvars.bind_contextvars( view=flask.request.path, request_id=str(uuid.uuid4()), peer=flask.request.access_route[0], ) # End of belongs-to-middleware. log = logger.bind() # do something # ... log.info("user logged in", user="test-user") # ... some_function() # ... return "logged in!" if __name__ == "__main__": logging.basicConfig( format="%(message)s", stream=sys.stdout, level=logging.INFO ) structlog.configure( processors=[ structlog.contextvars.merge_contextvars, # <--!!! structlog.processors.KeyValueRenderer( key_order=["event", "view", "peer"] ), ], logger_factory=structlog.stdlib.LoggerFactory(), ) app.run() ``` `some_module.py`: ```python from structlog import get_logger logger = get_logger() def some_function(): # ... logger.error("user did something", something="shot_in_foot") # ... ``` This would result among other the following lines to be printed: ```text event='user logged in' view='/login' peer='127.0.0.1' user='test-user' request_id='e08ddf0d-23a5-47ce-b20e-73ab8877d736' event='user did something' view='/login' peer='127.0.0.1' something='shot_in_foot' request_id='e08ddf0d-23a5-47ce-b20e-73ab8877d736' ``` As you can see, `view`, `peer`, and `request_id` are present in **both** log entries. structlog-25.5.0/docs/exceptions.md0000644000000000000000000000436715077624341014235 0ustar00# Exceptions While you should use a proper crash reporter like [Sentry](https://sentry.io) in production, *structlog* has helpers for formatting exceptions for humans and machines. All *structlog*'s exception features center around passing an `exc_info` key-value pair in the event dict. There are three possible behaviors depending on its value: 1. If the value is a tuple, render it as if it was returned by {func}`sys.exc_info`. 2. If the value is an Exception, render it. 3. If the value is true but no tuple, call {func}`sys.exc_info` and render that. If there is no `exc_info` key or false, the event dict is not touched. This behavior is analog to the one of the stdlib's logging. ## Transformations *structlog* comes with {class}`structlog.processors.ExceptionRenderer` that deduces and removes the `exc_info` key as outlined above, calls a user-supplied function with the synthesized `exc_info`, and stores its return value in the `exception` key. The most common use-cases are already covered by the following processors: {func}`structlog.processors.format_exc_info` : Formats it to a flat string like the standard library would on the console. {obj}`structlog.processors.dict_tracebacks` : Uses {class}`structlog.tracebacks.ExceptionDictTransformer` to give you a structured and JSON-serializable `exception` key. ## Console rendering Our {doc}`console-output`'s {class}`structlog.dev.ConsoleRenderer` takes an *exception_formatter* argument that allows for customizing the output of exceptions. {func}`structlog.dev.plain_traceback` : Is the default if neither [Rich] nor [*better-exceptions*] are installed. As the name suggests, it renders a plain traceback. {func}`structlog.dev.better_traceback` : Uses [*better-exceptions*] to render a colorful traceback. : It's the default if *better-exceptions* is installed and Rich is not. {class}`structlog.dev.RichTracebackFormatter` : Uses [Rich] to render a colorful traceback. It's a class because it allows for customizing the output by passing arguments to Rich. : It's the default if Rich is installed. :::{seealso} {doc}`console-output` for more information on *structlog*'s console features. ::: [*better-exceptions*]: https://github.com/qix-/better-exceptions [Rich]: https://github.com/Textualize/rich structlog-25.5.0/docs/frameworks.md0000644000000000000000000001063415077624341014226 0ustar00# Frameworks To have consistent log output, it makes sense to configure *structlog* *before* any logging is done. The best place to perform your configuration varies with applications and frameworks. If you use standard library's {mod}`logging`, it makes sense to configure them next to each other. ## Celery [Celery](https://docs.celeryq.dev/)'s multi-process architecture leads unavoidably to race conditions that show up as interleaved logs. It ships standard library-based helpers in the form of [`celery.utils.log.get_task_logger()`](https://docs.celeryq.dev/en/stable/userguide/tasks.html#logging) that you should use inside of tasks to prevent that problem. The most straight-forward way to integrate that with *structlog* is using {doc}`standard-library` and wrapping that logger using {func}`structlog.wrap_logger`: ```python from celery.utils.log import get_task_logger logger = structlog.wrap_logger(get_task_logger(__name__)) ``` If you want to automatically bind task metadata to your {doc}`contextvars`, you can use [Celery's signals](https://docs.celeryq.dev/en/stable/userguide/signals.html): ```python from celery import signals @signals.task_prerun.connect def on_task_prerun(sender, task_id, task, args, kwargs, **_): structlog.contextvars.bind_contextvars(task_id=task_id, task_name=task.name) ``` See [this issue](https://github.com/hynek/structlog/issues/287) for more details. ## Django [*django-structlog*](https://pypi.org/project/django-structlog/) is a popular and well-maintained package that does all the heavy lifting. ## Flask See Flask's [Logging docs](https://flask.palletsprojects.com/en/latest/logging/). Generally speaking: configure *structlog* *before* instantiating `flask.Flask`. Here's a [signal handler](https://flask.palletsprojects.com/en/latest/signals/) that binds various request details into [*context variables*](contextvars.md): ```python def bind_request_details(sender: Flask, **extras: dict[str, Any]) -> None: structlog.contextvars.clear_contextvars() structlog.contextvars.bind_contextvars( request_id=request.headers.get("X-Unique-ID", "NONE"), peer=peer, ) if current_user.is_authenticated: structlog.contextvars.bind_contextvars( user_id=current_user.get_id(), ) ``` You add it to an existing `app` like this: ```python from flask import request_started request_started.connect(bind_request_details, app) ``` ## Litestar [Litestar](https://docs.litestar.dev/) comes with *structlog* support [out of the box](https://docs.litestar.dev/latest/usage/logging.html). ## OpenTelemetry The [Python OpenTelemetry SDK](https://opentelemetry.io/docs/languages/python/) offers an easy API to get the current span, so you can enrich your logs with a straight-forward processor: ```python from opentelemetry import trace def add_open_telemetry_spans(_, __, event_dict): span = trace.get_current_span() if not span.is_recording(): event_dict["span"] = None return event_dict ctx = span.get_span_context() parent = getattr(span, "parent", None) event_dict["span"] = { "span_id": format(ctx.span_id, "016x"), "trace_id": format(ctx.trace_id, "032x"), "parent_span_id": None if not parent else format(parent.span_id, "016x"), } return event_dict ``` ## Pyramid Configure it in the [application constructor](https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/startup.html#the-startup-process). Here's an example for a Pyramid [*tween*](https://docs.pylonsproject.org/projects/pyramid/en/latest/glossary.html#term-tween) that stores various request-specific data into [*context variables*](contextvars.md): ```python @dataclass class StructLogTween: handler: Callable[[Request], Response] registry: Registry def __call__(self, request: Request) -> Response: structlog.contextvars.clear_contextvars() structlog.contextvars.bind_contextvars( peer=request.client_addr, request_id=request.headers.get("X-Unique-ID", "NONE"), user_agent=request.environ.get("HTTP_USER_AGENT", "UNKNOWN"), user=request.authenticated_userid, ) return self.handler(request) ``` ## Twisted The [plugin definition](https://docs.twisted.org/en/stable/core/howto/plugin.html) is the best place. If your app is not a plugin, put it into your [tac file](https://docs.twisted.org/en/stable/core/howto/application.html). structlog-25.5.0/docs/getting-started.md0000644000000000000000000002461715077624341015161 0ustar00# Getting Started (install)= ## Installation You can install *structlog* from [PyPI](https://pypi.org/project/structlog/) using *pip*: ```console $ python -m pip install structlog ``` If you want pretty exceptions in development (you know you do!), additionally install either [Rich] or [*better-exceptions*]. Try both to find out which one you like better -- the screenshot in the README and docs homepage is rendered by Rich. On **Windows**, you also have to install [Colorama](https://pypi.org/project/colorama/) if you want colorful output beside exceptions. ## Your first log entry A lot of effort went into making *structlog* accessible without reading pages of documentation. As a result, the simplest possible usage looks like this: ```{doctest} >>> import structlog >>> log = structlog.get_logger() >>> log.info("hello, %s!", "world", key="value!", more_than_strings=[1, 2, 3]) # doctest: +SKIP 2022-10-07 10:41:29 [info ] hello, world! key=value! more_than_strings=[1, 2, 3] ``` Here, *structlog* takes advantage of its default settings: - Output is sent to **[standard out](https://en.wikipedia.org/wiki/Standard_out#Standard_output_.28stdout.29)** instead of doing nothing. - It **imitates** standard library {mod}`logging`'s **log level names** for familiarity. By default, no level-based filtering is done, but it comes with a **very fast [filtering machinery](filtering)**. - Like in `logging`, positional arguments are [**interpolated into the message string using %**](https://docs.python.org/3/library/stdtypes.html#old-string-formatting). That might look dated, but it's *much* faster than using {any}`str.format` and allows *structlog* to be used as drop-in replacement for {mod}`logging`. If you *know* that the log entry is *always* gonna be logged out, just use [f-strings](https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals) which are the fastest. *structlog* supports both positional and dict-based interpolation, therefore `log.info("hello %(name)s!", {"name": "world"})` generates the same output as above. - All keywords are formatted using {class}`structlog.dev.ConsoleRenderer`. That in turn uses {func}`repr` to serialize **any value to a string**. - It's rendered in nice **{doc}`colors `**. - If you have [Rich] or [*better-exceptions*] installed, **exceptions** will be rendered in **colors** and with additional **helpful information**. Please note that even in most complex logging setups the example would still look just like that thanks to {doc}`configuration`. Using the defaults, as above, is equivalent to: ```python import logging import structlog structlog.configure( processors=[ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.StackInfoRenderer(), structlog.dev.set_exc_info, structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False), structlog.dev.ConsoleRenderer() ], wrapper_class=structlog.make_filtering_bound_logger(logging.NOTSET), context_class=dict, logger_factory=structlog.PrintLoggerFactory(), cache_logger_on_first_use=False ) log = structlog.get_logger() ``` :::{note} - {func}`structlog.stdlib.recreate_defaults()` allows you to switch *structlog* to using standard library's `logging` module for output for better interoperability with just one function call. - {func}`~structlog.make_filtering_bound_logger()` (re-)uses {any}`logging`'s log levels, but doesn't use `logging` at all. The exposed API is {class}`~structlog.typing.FilteringBoundLogger`. - For brevity and to enable doctests, all further examples in *structlog*'s documentation use the more simplistic {class}`~structlog.processors.KeyValueRenderer()` without timestamps. ::: There you go, structured logging! However, this alone wouldn't warrant its own package. After all, there's even a [recipe] on structured logging for the standard library. So let's go a step further. (building-ctx)= ## Building a context Imagine a hypothetical web application that wants to log out all relevant data with just the APIs that we've introduced so far: ```python def view(request): user_agent = request.get("HTTP_USER_AGENT", "UNKNOWN") peer_ip = request.client_addr if something: log.info("something", user_agent=user_agent, peer_ip=peer_ip) return "something" elif something_else: log.info("something_else", user_agent=user_agent, peer_ip=peer_ip) return "something_else" else: log.info("else", user_agent=user_agent, peer_ip=peer_ip) return "else" ``` The calls themselves are nice and straight to the point, however you're repeating yourself all over the place. It's easy to forget to add a key-value pair in one of the incantations. At this point, you'll be tempted to write a closure like: ```python def log_closure(event): log.info(event, user_agent=user_agent, peer_ip=peer_ip) ``` inside of the view. Problem solved? Not quite. What if the parameters are introduced step by step? And do you really want to have a logging closure in each of your views? Let's have a look at a better approach: ```python def view(request): log = log.bind( user_agent=request.get("HTTP_USER_AGENT", "UNKNOWN"), peer_ip=request.client_addr, ) if foo := request.get("foo"): log = log.bind(foo=foo) if something: log.info("something") return "something" elif something_else: log.info("something_else") return "something_else" else: log.info("else") return "else" ``` Suddenly your logger becomes your closure! --- To *structlog*, a log entry is just a dictionary called *event dict\[ionary\]*: - You can pre-build a part of the dictionary step by step. These pre-saved values are called the *context*. - As soon as an *event* happens -- which are the `kwargs` of the log call -- it is merged together with the *context* to an *event dict* and logged out. - Each logger with its context is *immutable*. You manipulate the context by creating new loggers using `bind()` and `unbind()`. The last point is very clean and easy to reason about, but sometimes it's useful to store _some_ data globally. In our example above the peer IP comes to mind. There's no point in extracting it in every view! For that, *structlog* gives you thread-local context storage based on the {mod}`contextvars` module: ```pycon >>> structlog.contextvars.bind_contextvars(peer_ip="1.2.3.4") >>> structlog.get_logger().info("something") 2022-10-10 10:18:05 [info ] something peer_ip=1.2.3.4 ``` See {doc}`contextvars` for more information and a more complete example. ## Manipulating log entries in flight Now that your log events are dictionaries, it's also much easier to manipulate them than if they were plain strings. To facilitate that, *structlog* has the concept of {doc}`processor chains `. A processor is a function that receives the event dictionary along with two other arguments and returns a new event dictionary that may or may not differ from the one it got passed. The next processor in the chain receives that returned dictionary instead of the original one. Let's assume you wanted to add a timestamp to every event dict. The processor would look like this: ```{doctest} >>> import datetime >>> def timestamper(_, __, event_dict): ... event_dict["time"] = datetime.datetime.now().isoformat() ... return event_dict ``` Plain Python, plain dictionaries. Now you have to tell *structlog* about your processor by {doc}`configuring ` it: ```{doctest} >>> structlog.configure(processors=[timestamper, structlog.processors.KeyValueRenderer()]) >>> structlog.get_logger().info("hi") # doctest: +SKIP event='hi' time='2018-01-21T09:37:36.976816' ``` ## Rendering Finally you want to have control over the actual format of your log entries. As you may have noticed in the previous section, renderers are just processors too. The type of the return value that is required from the renderer depends on the input that the *logger* that is wrapped by *structlog* needs. While usually it's a string or bytes, there's no rule saying it _has_ to be a string! So assuming you want to follow [best practices](logging-best-practices.md) and render your event dictionary to JSON that is picked up by a log aggregation system like ELK or Graylog, *structlog* comes with batteries included -- you just have to tell it to use its {class}`~structlog.processors.JSONRenderer`: ```{doctest} >>> structlog.configure(processors=[structlog.processors.JSONRenderer()]) >>> structlog.get_logger().info("hi") {"event": "hi"} ``` ## *structlog* and standard library's `logging` While *structlog*'s loggers are very fast and sufficient for the majority of our users, you're not bound to them. Instead, it's been designed from day one to wrap your *existing* loggers and **add** *structure* and *incremental context building* to them. The most prominent example of such an "existing logger" is certainly the logging module in the standard library. To make this common case as simple as possible, *structlog* comes with [some tools](standard-library.md) to help you. As noted before, the fastest way to transform *structlog* into a `logging`-friendly package is calling {func}`structlog.stdlib.recreate_defaults()`. ## asyncio The default *bound logger* that you get back from {func}`structlog.get_logger()` and standard library's {class}`structlog.stdlib.BoundLogger` don't have just the familiar log methods like `debug()` or `info()`, but also their async cousins, that simply prefix the name with an a: ```pycon >>> import asyncio >>> logger = structlog.get_logger() >>> async def f(): ... await logger.ainfo("async hi!") ... >>> logger.info("Loop isn't running yet, but we can log!") 2023-04-06 07:25:48 [info ] Loop isn't running yet, but we can log! >>> asyncio.run(f()) 2023-04-06 07:26:08 [info ] async hi! ``` You can use the sync and async logging methods interchangeably within the same application. ## Liked what you saw? Now you're all set for the rest of the user's guide and can start reading about [bound loggers](bound-loggers.md) -- the heart of *structlog*. ```{include} ../README.md :start-after: :end-before: ``` [*better-exceptions*]: https://github.com/qix-/better-exceptions [recipe]: https://docs.python.org/3/howto/logging-cookbook.html#implementing-structured-logging [Rich]: https://github.com/Textualize/rich structlog-25.5.0/docs/glossary.md0000644000000000000000000000512115077624341013704 0ustar00# Glossary Please feel free to [file an issue](https://github.com/hynek/structlog/issues) if you think some important concept is missing here. :::{glossary} Event Dictionary Often abbreviated as *event dict*. It's a dictionary that contains all the information that is logged, with the `event` key having the special role of being the name of the event. It's the result of the values bound to the {term}`bound logger`'s context and the key-value pairs passed to the logging method. It is then passed through the {term}`processor` chain that can add, modify, and even remove key-value pairs. Bound Logger An instance of a {class}`structlog.typing.BindableLogger` that is returned by either {func}`structlog.get_logger` or the bind/unbind/new methods on it. As the name suggests, it's possible to bind key-value pairs to it -- this data is called the {term}`context` of the logger. Its methods are the user's logging API and depend on the type of the bound logger. The two most common implementations are {class}`structlog.BoundLogger` and {class}`structlog.stdlib.BoundLogger`. Bound loggers are **immutable**. The context can only be modified by creating a new bound logger using its `bind()`and `unbind()` methods. :::{seealso} {doc}`bound-loggers` ::: Context A dictionary of key-value pairs belonging to a {term}`bound logger`. When a log entry is logged out, the context is the base for the event dictionary with the keyword arguments of the logging method call merged in. Bound loggers are **immutable**, so it's not possible to modify a context directly. But you can create a new bound logger with a different context using its `bind()` and `unbind()` methods. Native Loggers Loggers created using {func}`structlog.make_filtering_bound_logger` which includes the default configuration. These loggers are very fast and do **not** use the standard library. Wrapped Logger The logger that is wrapped by *structlog* and that is responsible for the actual output. By default it's a {class}`structlog.PrintLogger` for native logging. Another popular choice is {class}`logging.Logger` for standard library logging. :::{seealso} {doc}`standard-library` ::: Processor A callable that is called on every log entry. It receives the return value of its predecessor as an argument and returns a new event dictionary. This allows for composable transformations of the event dictionary. The result of the final processor is passed to the {term}`wrapped logger`. :::{seealso} {doc}`processors` ::: ::: structlog-25.5.0/docs/index.md0000644000000000000000000000660715077624341013162 0ustar00# structlog *Simple. Powerful. Fast. Pick three.* Release **{sub-ref}`release`** ([What's new?](https://github.com/hynek/structlog/blob/main/CHANGELOG.md)) --- ```{include} ../README.md :start-after: :end-before: ``` ```{include} ../README.md :start-after: :end-before: ``` If you’d like more information on why structured logging in general – and *structlog* in particular – are good ideas, we’ve prepared a [summary](why.md) just for you. Otherwise, let’s dive right in! ```{toctree} :hidden: true why ``` ## Basics The first chapters teach you all you need to use *structlog* productively. They build gently on each other, so ideally, read them in order. If anything seems confusing, don't hesitate to have a look at our {doc}`glossary`! ```{toctree} :maxdepth: 2 :caption: Basics getting-started bound-loggers configuration processors contextvars exceptions ``` ## Development affordances *structlog*'s focus is on production systems, but it comes with **pretty console logging** and handy in-development helpers both for your **comfort** and your code's **quality**. ```{toctree} :maxdepth: 2 :caption: Development Affordances console-output testing typing ``` (integration)= ## Integration with existing systems *structlog* is both zero-config as well as highly configurable. You can use it on its own or integrate with existing systems. Dedicated support for the standard library and Twisted is shipped out-of-the-box. ```{toctree} :maxdepth: 2 :caption: Integrations frameworks standard-library twisted ``` ## *structlog* in practice The following chapters deal with considerations of using *structlog* in the real world. ```{toctree} :maxdepth: 2 :caption: In Practice recipes logging-best-practices performance ``` ## Reference ```{toctree} :maxdepth: 2 :caption: Reference api glossary genindex modindex ``` ## Deprecated features ```{toctree} :maxdepth: 1 :caption: Deprecated Features thread-local ``` ```{toctree} :hidden: :caption: Meta license PyPI GitHub Changelog Contributing Security Policy Funding ``` structlog-25.5.0/docs/license.md0000644000000000000000000000201615077624341013463 0ustar00# License and Hall of Fame *structlog* is licensed both under the [Apache License, Version 2](https://choosealicense.com/licenses/apache/) and the [MIT license](https://choosealicense.com/licenses/mit/). Any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. --- The reason for that is to be both protected against patent claims by own contributors and still allow the usage within GPLv2 software. For more legal details, see [this issue](https://github.com/pyca/cryptography/issues/1209) on the bug tracker of PyCA's *cryptography* project. The full license texts can be also found in the source code repository: - [Apache License 2.0](https://github.com/hynek/structlog/blob/main/LICENSE-APACHE) - [MIT](https://github.com/hynek/structlog/blob/main/LICENSE-MIT) ## Credits ```{include} ../README.md :parser: myst_parser.sphinx_ :start-after: "## Credits" :end-before: ``` structlog-25.5.0/docs/logging-best-practices.md0000644000000000000000000001147515077624341016406 0ustar00# Logging Best Practices Logging is not a new concept and is in no way unique to Python. Logfiles have existed for decades, and there's little reason to reinvent the wheel in our little world. Therefore, let's rely on proven tools as much as possible and do only the bare minimum inside Python applications[^unix]. A simple but powerful approach is to log to unbuffered [standard out](https://en.wikipedia.org/wiki/Standard_out#Standard_output_.28stdout.29 ) and let other tools take care of the rest. That can be your terminal window while developing; it can be [*systemd*](https://en.wikipedia.org/wiki/Systemd) redirecting your log entries to [*syslogd*](https://en.wikipedia.org/wiki/Syslogd) and rotating them using [*logrotate*](https://github.com/logrotate/logrotate); or it can be your [cluster manager](https://kubernetes.io/docs/concepts/cluster-administration/logging/) forwarding them to an obscenely expensive log aggregator service. It doesn't matter where or how your application runs -- it just works, and the reason why the popular [*Twelve-Factor App* methodology](https://12factor.net/logs) suggests just that. [^unix]: This is obviously a privileged UNIX-centric view but even Windows has tools and means for log management although we won't be able to discuss them here. ## Canonical log lines Generally speaking, having as few log entries per request as possible is a good thing. The less noise, the more insights. *structlog*'s ability to {ref}`bind data to loggers incrementally ` -- plus {doc}`loggers that are local to the current execution context ` -- can help you to minimize the output to a *single log entry*. At Stripe, this concept is called [Canonical Log Lines](https://brandur.org/canonical-log-lines). ## Pretty printing vs. structured output Colorful and pretty printed log messages are nice during development when you locally run your code. However, in production you should emit structured output (like JSON) which is a lot easier to parse by log aggregators. Since you already log in a structured way, writing JSON output with *structlog* comes naturally. You can even generate structured exception tracebacks. This makes analyzing errors easier, since log aggregators can render JSON much better than multiline strings with a lot escaped quotation marks. Here is a simple example of how you can have pretty logs during development and JSON output when your app is running in a production context: ```{doctest} >>> import sys >>> import structlog >>> >>> shared_processors = [ ... # Processors that have nothing to do with output, ... # e.g., add timestamps or log level names. ... ] >>> if sys.stderr.isatty(): ... # Pretty printing when we run in a terminal session. ... # Automatically prints pretty tracebacks when "rich" is installed ... processors = shared_processors + [ ... structlog.dev.ConsoleRenderer(), ... ] ... else: ... # Print JSON when we run, e.g., in a Docker container. ... # Also print structured tracebacks. ... processors = shared_processors + [ ... structlog.processors.dict_tracebacks, ... structlog.processors.JSONRenderer(), ... ] >>> structlog.configure(processors) ``` ## Centralized logging Nowadays you usually don't want your log files in compressed archives distributed over dozens -- if not thousands -- of servers or cluster nodes. You want them in a single location. Parsed, indexed, and easy to search. ### ELK The ELK stack ([**E**lasticsearch][elasticsearch], [**L**ogstash][logstash], [**K**ibana][kibana]) from Elastic is a great way to store, parse, and search your logs. The way it works is that you have local log shippers like [Filebeat] that parse your log files and forward the log entries to your [Logstash] server. Logstash parses the log entries and stores them in [Elasticsearch]. Finally, you can view and search them in [Kibana]. If your log entries consist of a JSON dictionary, this is fairly easy and efficient. All you have to do is to tell [Logstash] either that your log entries are prepended with a timestamp from {class}`~structlog.processors.TimeStamper` or the name of your timestamp field. ### Graylog [Graylog](https://graylog.org/) goes one step further. It not only supports everything those above do (and then some); you can also directly log JSON entries towards it -- optionally even through an AMQP server (like [RabbitMQ](https://www.rabbitmq.com/)) for better reliability. Additionally, [Graylog's Extended Log Format](https://go2docs.graylog.org/current/getting_in_log_data/gelf.html) (GELF) allows for structured data which makes it an obvious choice to use together with *structlog*. [elasticsearch]: https://www.elastic.co/elasticsearch [filebeat]: https://github.com/elastic/beats/tree/main/filebeat [kibana]: https://www.elastic.co/kibana [logstash]: https://www.elastic.co/logstash structlog-25.5.0/docs/make.bat0000644000000000000000000001175615077624341013137 0ustar00@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\structlog.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\structlog.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end structlog-25.5.0/docs/performance.md0000644000000000000000000001055615077624341014352 0ustar00# Performance Here are a few hints how to get the best performance out of *structlog* in production: - Use *structlog*'s native *BoundLogger* (created using {func}`structlog.make_filtering_bound_logger`) if you want to use level-based filtering. `return None` is hard to beat. - Avoid (frequently) calling log methods on loggers you get back from {func}`structlog.get_logger` or {func}`structlog.wrap_logger`. Since those functions are usually called in module scope and thus before you are able to configure them, they return a proxy object that assembles the correct logger on demand. Create a local logger if you expect to log frequently without binding: ```python logger = structlog.get_logger() def f(): log = logger.bind() for i in range(1000000000): log.info("iterated", i=i) ``` Since global scope lookups are expensive in Python, it's generally a good idea to copy frequently-used symbols into local scope. - Set the *cache_logger_on_first_use* option to `True` so the aforementioned on-demand loggers will be assembled only once and cached for future uses: ```python configure(cache_logger_on_first_use=True) ``` This has two drawbacks: 1. Later calls of {func}`~structlog.configure` don't have any effect on already cached loggers -- that shouldn't matter outside of {doc}`testing ` though. 2. The resulting bound logger is not pickleable. Therefore, you can't set this option if you, for example, plan on passing loggers around using {mod}`multiprocessing`. - Avoid sending your log entries through the standard library if you can: its dynamic nature and flexibility make it a major bottleneck. Instead use {class}`structlog.WriteLoggerFactory` or -- if your serializer returns bytes (for example, [*orjson*] or [*msgspec*]) -- {class}`structlog.BytesLoggerFactory`. You can still configure `logging` for packages that you don't control, but avoid it for your *own* log entries. - Configure {class}`~structlog.processors.JSONRenderer` to use a faster JSON serializer than the standard library. Possible alternatives are among others are [*orjson*], [*msgspec*], or [RapidJSON](https://pypi.org/project/python-rapidjson/). - Be conscious about whether and how you use *structlog*'s *asyncio* support. While it's true that moving log processing into separate threads prevents your application from hanging, it also comes with a performance cost. Decide judiciously whether or not you're willing to pay that price. If your processor chain has a good and predictable performance without external dependencies (as it should), it might not be worth it. ## Example Here's an example for a production-ready *structlog* configuration that's as fast as it gets: ```python import logging import orjson import structlog structlog.configure( cache_logger_on_first_use=True, wrapper_class=structlog.make_filtering_bound_logger(logging.INFO), processors=[ structlog.contextvars.merge_contextvars, structlog.processors.add_log_level, structlog.processors.format_exc_info, structlog.processors.TimeStamper(fmt="iso", utc=True), structlog.processors.JSONRenderer(serializer=orjson.dumps), ], logger_factory=structlog.BytesLoggerFactory(), ) ``` It has the following properties: - Caches all loggers on first use. - Filters all log entries below the `info` log level **very** efficiently. The `debug` method literally consists of `return None`. - Supports {doc}`contextvars` (thread-local contexts outside of *asyncio*). - Adds the log level name. - Renders exceptions into the `exception` key. - Adds an [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) timestamp under the `timestamp` key in the UTC timezone. - Renders the log entries as JSON using [*orjson*] which is faster than *plain* logging in {mod}`logging`. - Uses {class}`structlog.BytesLoggerFactory` because *orjson* returns bytes. That saves encoding ping-pong. Therefore a log entry might look like this: ```json {"event":"hello","level":"info","timestamp":"2023-11-02T08:03:38.298565Z"} ``` --- If you need standard library support for external projects, you can either just use a JSON formatter like [*python-json-logger*](https://pypi.org/project/python-json-logger/), or pipe them through *structlog* as documented in {doc}`standard-library`. [*orjson*]: https://github.com/ijl/orjson [*msgspec*]: https://jcristharif.com/msgspec/ structlog-25.5.0/docs/processors.md0000644000000000000000000001362715077624341014255 0ustar00# Processors The true power of *structlog* lies in its *combinable log processors*. A log processor is a regular callable or in other words: A function or an instance of a class with a `__call__()` method. (chains)= ## Chains The *processor chain* is a list of processors. Each processors receives three positional arguments: **logger** : Your wrapped logger object that is called with the final processor return value. For example {class}`logging.Logger` or {class}`structlog.PrintLogger` (default). **method_name** : The name of the wrapped method. If you called `log.warning("foo")`, it will be `"warning"`. **event_dict** : Current context together with the current event. If the context was `{"a": 42}` and the event is `"foo"`, the initial `event_dict` will be `{"a":42, "event": "foo"}`. The return value of each processor is passed on to the next one as `event_dict` until finally the return value of the last processor gets passed into the wrapped logging method. :::{note} *structlog* only looks at the return value of the **last** processor. That means that as long as you control the next processor in the chain (the processor that will get your return value passed as an argument), you can return whatever you want. Returning a modified event dictionary from your processors is just a convention to make processors composable. ::: ### Examples If you set up your logger like: ```python structlog.configure(processors=[f1, f2, f3]) log = structlog.get_logger().bind(x=42) ``` and call `log.info("some_event", y=23)`, it results in the following call chain: ```python wrapped_logger.info( f3(wrapped_logger, "info", f2(wrapped_logger, "info", f1(wrapped_logger, "info", {"event": "some_event", "x": 42, "y": 23}) ) ) ) ``` In this case, `f3` has to make sure it returns something `wrapped_logger.info` can handle (see {ref}`adapting`). For the example with `PrintLogger` above, this means `f3` must return a string. The simplest modification a processor can make is adding new values to the `event_dict`. Parsing human-readable timestamps is tedious, not so [UNIX timestamps](https://en.wikipedia.org/wiki/UNIX_time) -- let's add one to each log entry: ```python import calendar import time def timestamper(logger, log_method, event_dict): event_dict["timestamp"] = calendar.timegm(time.gmtime()) return event_dict ``` :::{important} You're explicitly allowed to modify the `event_dict` parameter, because a copy has been created before calling the first processor. ::: Please note that *structlog* comes with such a processor built in: {class}`~structlog.processors.TimeStamper`. ## Filtering If a processor raises {class}`structlog.DropEvent`, the event is silently dropped. Therefore, the following processor drops every entry: ```python from structlog import DropEvent def dropper(logger, method_name, event_dict): raise DropEvent ``` But we can do better than that! (cond-drop)= How about dropping only log entries that are marked as coming from a certain peer (for example, monitoring)? ```python class ConditionalDropper: def __init__(self, peer_to_ignore): self._peer_to_ignore = peer_to_ignore def __call__(self, logger, method_name, event_dict): """ >>> cd = ConditionalDropper("127.0.0.1") >>> cd(None, "", {"event": "foo", "peer": "10.0.0.1"}) {'peer': '10.0.0.1', 'event': 'foo'} >>> cd(None, "", {"event": "foo", "peer": "127.0.0.1"}) Traceback (most recent call last): ... DropEvent """ if event_dict.get("peer") == self._peer_to_ignore: raise DropEvent return event_dict ``` Since it's so common to filter by the log level, *structlog* comes with {func}`structlog.make_filtering_bound_logger` that filters log entries before they even enter the processor chain. It does **not** use the standard library, but it does use its names and order of log levels. (adapting)= ## Adapting and rendering An important role is played by the *last* processor because its duty is to adapt the `event_dict` into something the logging methods of the *wrapped logger* understand. With that, it's also the *only* processor that needs to know anything about the underlying system. It can return one of three types: - An Unicode string ({any}`str`), a bytes string ({any}`bytes`), or a {any}`bytearray` that is passed as the first (and only) positional argument to the underlying logger. - A tuple of `(args, kwargs)` that are passed as `log_method(*args, **kwargs)`. - A dictionary which is passed as `log_method(**kwargs)`. Therefore `return "hello world"` is a shortcut for `return (("hello world",), {})` (the example in {ref}`chains` assumes this shortcut has been taken). This should give you enough power to use *structlog* with any logging system while writing agnostic processors that operate on dictionaries. :::{versionchanged} 14.0.0 Allow final processor to return a {any}`dict`. ::: :::{versionchanged} 20.2.0 Allow final processor to return a {any}`bytes`. ::: :::{versionchanged} 21.2.0 Allow final processor to return a {any}`bytearray`. ::: ### Examples The probably most useful formatter for string based loggers is {class}`structlog.processors.JSONRenderer`. Advanced log aggregation and analysis tools like [*Logstash*](https://www.elastic.co/logstash) offer features like telling them "this is JSON, deal with it" instead of fiddling with regular expressions. For a list of shipped processors, check out the {ref}`API documentation `. ## Third-Party packages *structlog* was specifically designed to be as composable and reusable as possible, so whatever you're missing: chances are, you can solve it with a processor! Since processors are self-contained callables, it's easy to write your own and to share them in separate packages. We collect those packages in our [GitHub Wiki](https://github.com/hynek/structlog/wiki/Third-Party-Extensions) and encourage you to add your package too! structlog-25.5.0/docs/recipes.md0000644000000000000000000001623315077624341013501 0ustar00# Recipes Because *structlog* is entirely based on dictionaries and callables, the sky is the limit with what you can achieve. That can be daunting in the beginning, so here are a few examples of tasks that have come up repeatedly. Please note that recipes related to integration with frameworks have an [own chapter](frameworks.md). (rename-event)= ## Renaming the `event` key The name of the event is hard-coded in *structlog* to `event`. But that doesn't mean it has to be called that in your logs. With the {class}`structlog.processors.EventRenamer` processor, you can, for instance, rename the log message to `msg` and use `event` for something custom, that you bind to `_event` in your code: ```pycon >>> from structlog.processors import EventRenamer >>> event_dict = {"event": "something happened", "_event": "our event!"} >>> EventRenamer("msg", "_event")(None, "", event_dict) {'msg': 'something happened', 'event': 'our event!'} ``` (finer-filtering)= ## Fine-grained log-level filtering *structlog*'s native log levels as provided by {func}`structlog.make_filtering_bound_logger` only know **one** log level – the one that is passed to `make_filtering_bound_logger()`. Sometimes, that can be a bit too coarse, though. You can achieve finer control by adding the {class}`~structlog.processors.CallsiteParameterAdder` processor and writing a simple processor that acts on the call site data added. Let's assume you have the following code: ```python logger = structlog.get_logger() def f(): logger.info("f called") def g(): logger.info("g called") f() g() ``` And you don't want to see log entries from function `f`. You add {class}`~structlog.processors.CallsiteParameterAdder` to the processor chain and then look at the `func_name` field in the *event dict*: ```python def filter_f(_, __, event_dict): if event_dict.get("func_name") == "f": raise structlog.DropEvent return event_dict structlog.configure( processors=[ structlog.processors.CallsiteParameterAdder( [structlog.processors.CallsiteParameter.FUNC_NAME] ), filter_f, # <-- your processor! structlog.processors.KeyValueRenderer(), ] ) ``` Running this gives you: ``` event='g called' func_name='g' ``` {class}`~structlog.processors.CallsiteParameterAdder` is *very* powerful in what info it can add, so your possibilities are limitless. Pick the data you're interested in from the {class}`structlog.processors.CallsiteParameter` {class}`~enum.Enum`. (custom-wrappers)= ## Custom wrappers ```{testsetup} import structlog structlog.configure( processors=[structlog.processors.KeyValueRenderer()], ) ``` ```{testcleanup} import structlog structlog.reset_defaults() ``` The type of the *bound loggers* that are returned by {func}`structlog.get_logger()` is called the *wrapper class*, because it wraps the original logger that takes care of the output. This wrapper class is [configurable](configuration.md). Originally, *structlog* used a generic wrapper class {class}`structlog.BoundLogger` by default. That class still ships with *structlog* and can wrap *any* logger class by intercepting unknown method names and proxying them to the wrapped logger. Nowadays, the default is a {class}`structlog.typing.FilteringBoundLogger` that imitates standard library’s log levels with the possibility of efficiently filtering at a certain level (inactive log methods are a plain `return None` each). If you’re integrating with {mod}`logging` or Twisted, you may want to use one of their specific *bound loggers* ({class}`structlog.stdlib.BoundLogger` and {class}`structlog.twisted.BoundLogger`, respectively). --- On top of that all, you can also write your own wrapper classes. To make it easy for you, *structlog* comes with the class {class}`structlog.BoundLoggerBase` which takes care of all data binding duties so you just add your log methods if you choose to sub-class it. (wrapper-class-example)= ### Example It’s easiest to demonstrate with an example: ```{doctest} >>> from structlog import BoundLoggerBase, PrintLogger, wrap_logger >>> class SemanticLogger(BoundLoggerBase): ... def info(self, event, **kw): ... if not "status" in kw: ... return self._proxy_to_logger("info", event, status="ok", **kw) ... else: ... return self._proxy_to_logger("info", event, **kw) ... ... def user_error(self, event, **kw): ... self.info(event, status="user_error", **kw) >>> log = wrap_logger(PrintLogger(), wrapper_class=SemanticLogger) >>> log = log.bind(user="fprefect") >>> log.user_error("user.forgot_towel") user='fprefect' status='user_error' event='user.forgot_towel' ``` You can observe the following: - The wrapped logger can be found in the instance variable {attr}`structlog.BoundLoggerBase._logger`. - The helper method {meth}`structlog.BoundLoggerBase._proxy_to_logger` that is a [DRY] convenience function that runs the processor chain, handles possible {class}`structlog.DropEvent`s and calls a named function on `_logger`. - You can run the chain by hand through using {meth}`structlog.BoundLoggerBase._process_event` . These two methods and one attribute are all you need to write own *bound loggers*. [dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself ## Passing context to worker threads Thread-local context data based on [context variables](contextvars.md) is -- as the name says -- local to the thread that binds it. When using threads to process work in parallel, you have to pass the thread-local context **into** the worker threads. One way is to retrieve the context vars and pass them along to the worker threads. Then, Inside of the worker, re-bind them using `bind_contextvars`. The following example uses [*pathos*](https://pypi.org/project/pathos/) to create a `ThreadPool`. The context variables are retrieved and passed as the first argument to the partial function. The pool invokes the partial function, once for each element of `workers`. Inside of `do_some_work`, the context vars are bound and a message about the great work being performed is logged -- including the `request_id` key / value pair. ``` from functools import partial import structlog from structlog.contextvars import bind_contextvars from pathos.threading import ThreadPool logger = structlog.get_logger(__name__) def do_some_work(ctx, this_worker): bind_contextvars(**ctx) logger.info("WorkerDidSomeWork", worker=this_worker) def structlog_with_threadpool(f): ctx = structlog.contextvars.get_contextvars() func = partial(f, ctx) workers = ["1", "2", "3"] with ThreadPool() as pool: return list(pool.map(func, workers)) def manager(request_id: str): bind_contextvars(request_id=request_id) logger.info("StartingWorkers") structlog_with_threadpool(do_some_work) ``` See the [issue 425](https://github.com/hynek/structlog/issues/425) for a more complete example. ## Switching console output to standard error When using structlog without standard library integration and want the log output to go to standard error (*stderr*) instead of standard out (*stdout*), you can switch with a single line of configuration: ```python structlog.configure(logger_factory=structlog.PrintLoggerFactory(sys.stderr)) ``` structlog-25.5.0/docs/standard-library.md0000644000000000000000000005471515077624341015320 0ustar00# Standard Library Logging Ideally, *structlog* should be able to be used as a drop-in replacement for standard library's {mod}`logging` by wrapping it. In other words, you should be able to replace your call to {func}`logging.getLogger` by a call to {func}`structlog.get_logger` and things should keep working as before (if *structlog* is configured right, see {ref}`stdlib-config` below). If you run into incompatibilities, it is a *bug* so please take the time to [report it](https://github.com/hynek/structlog/issues)! If you're a heavy `logging` user, your [help](https://github.com/hynek/structlog/issues?q=is%3Aopen+is%3Aissue+label%3Astdlib) to ensure a better compatibility would be highly appreciated! :::{important} The quickest way to get started with *structlog* and `logging` is {func}`structlog.stdlib.recreate_defaults()`. It will recreate the default configuration on top of `logging` and optionally configure `logging` for you. ::: ## Just enough `logging` If you want to use *structlog* with `logging`, you should have at least a fleeting understanding on how the standard library operates because *structlog* will *not* do any magic things in the background for you. Most importantly you have to *configure* the `logging` system *additionally* to configuring *structlog*. Usually it is enough to use: ``` import logging import sys logging.basicConfig( format="%(message)s", stream=sys.stdout, level=logging.INFO, ) ``` This will send all log messages with the [log level](https://docs.python.org/3/library/logging.html#logging-levels) `logging.INFO` and above (that means that, for example, `logging.debug` calls are ignored) to standard out without any special formatting by the standard library. If you require more complex behavior, please refer to the standard library's {mod}`logging` documentation. ## Concrete bound logger *structlog* ships a stdlib-specific [*bound logger*](bound-loggers.md) that mirrors the log methods of standard library's {any}`logging.Logger` with correct type hints. If you want to take advantage of said type hints, you have to either annotate the logger coming from {func}`structlog.get_logger`, or use {func}`structlog.stdlib.get_logger()` that has the appropriate type hints. Please note though, that it will neither configure nor verify your configuration. It will call `structlog.get_logger()` just like if you would've called it -- the only difference are the type hints. See also {doc}`typing`. ### `asyncio` In {mod}`asyncio` applications, you may not want your whole application to block while the processor chain is formatting your log entries. For that use case *structlog* comes with a set of non-standard methods that will do all processing in a thread pool executor. They have the same names as the regular methods, except they are prefixed by an `a`. So instead of `logger.info("event!")` you write `await logger.ainfo("event!)`. No extra configuration is necessary and you can mix-and-match both types of methods within the same application. This means an increased computational cost per log entry, but your application will not block because of logging. ```{versionadded} 23.1.0 ``` --- *structlog* also comes with {class}`structlog.stdlib.AsyncBoundLogger` that blankly makes all logging methods asynchronous (in other words, you have to use `await log.info()` instead of just `log.info()`). To use it, {doc}`configure ` *structlog* to use `AsyncBoundLogger` as `wrapper_class`. ```{versionadded} 20.2.0 ``` ```{deprecated} 23.1.0 ``` ## Processors *structlog* comes with a few standard library-specific processors: {func}`~structlog.stdlib.render_to_log_args_and_kwargs`: : Renders the event dictionary into positional and keyword arguments for `logging.Logger` logging methods. This is useful if you want to render your log entries entirely within `logging`. {func}`~structlog.stdlib.render_to_log_kwargs`: : Same as above, but does not support passing positional arguments from *structlog* loggers to `logging.Logger` logging methods as positional arguments. *structlog* positional arguments are still passed to `logging` under `positional_args` key of `extra` keyword argument. {func}`~structlog.stdlib.filter_by_level`: : Checks the log entry's log level against the configuration of standard library's logging. Log entries below the threshold get silently dropped. Put it at the beginning of your processing chain to avoid expensive operations from happening in the first place. {func}`~structlog.stdlib.add_logger_name`: : Adds the name of the logger to the event dictionary under the key `logger`. {func}`~structlog.stdlib.add_log_level`: : Adds the log level to the event dictionary under the key `level`. {func}`~structlog.stdlib.add_log_level_number`: : Adds the log level number to the event dictionary under the key `level_number`. Log level numbers map to the log level names. The Python stdlib uses them for filtering logic. This adds the same numbers so users can leverage similar filtering. Compare: ``` level in ("warning", "error", "critical") level_number >= 30 ``` The mapping of names to numbers is in `structlog.stdlib._NAME_TO_LEVEL`. {class}`~structlog.stdlib.ExtraAdder`: : Add extra attributes of `logging.LogRecord` objects to the event dictionary. This processor can be used for adding data passed in the `extra` parameter of the `logging` module's log methods to the event dictionary. {func}`~structlog.stdlib.PositionalArgumentsFormatter`: : This processes and formats positional arguments (if any) passed to log methods in the same way the `logging` module would do, for example, `logger.info("Hello, %s", name)`. *structlog* also comes with {class}`~structlog.stdlib.ProcessorFormatter` which is a `logging.Formatter` that enables you to format non-*structlog* log entries using *structlog* renderers *and* multiplex *structlog*’s output with different renderers (see [below](processor-formatter) for an example). (stdlib-config)= ## Suggested configurations :::{note} We do appreciate that fully integrating *structlog* with standard library's `logging` is fiddly when done for the first time. This is the price of flexibility and unfortunately -- given the different needs of our users -- we can't make it any simpler without compromising someone's use-cases. However, once it is set up, you can rely on not having to ever touch it again. ::: Depending *where* you'd like to do your formatting, you can take one of four approaches: ### Don't integrate The most straight-forward option is to configure standard library `logging` close enough to what *structlog* is logging and leaving it at that. Since these are usually log entries from third parties that don't take advantage of *structlog*'s features, this is surprisingly often a perfectly adequate approach. For instance, if you log JSON in production, configure `logging` to use [*python-json-logger*] to make it print JSON too, and then tweak the configuration to match their outputs. You can also use {class}`~structlog.stdlib.ProcessorFormatter` as a formatter for `logging` to get the same output for both *structlog* and `logging` log entries -- see [below](processor-formatter) for an example. :::{important} If you want to use same file (for example, `sys.stdout` or `sys.stderr`) for both *structlog* and `logging.StreamHandler` output, you must use {class}`~structlog.WriteLogger` instead of {class}`~structlog.PrintLogger`. This is because {class}`~structlog.PrintLogger` uses `print(log, file=file, flush=True)` to write log, and `print` writes the `log` message and a newline ("\n") to the stream separately. This can cause interleaving of log entries from *structlog* and `logging` loggers. {class}`~structlog.WriteLogger` writes log entries atomically to the file (for example, `file.write(log+"\n")`). ::: ### Rendering within *structlog* This is the simplest approach where *structlog* does all the heavy lifting and passes a fully-formatted string to `logging`. Chances are, this is all you need. ```{mermaid} :align: center flowchart TD User structlog stdlib[Standard Library e.g. logging.StreamHandler] User --> |"structlog.get_logger().info('foo')"| structlog User --> |"logging.getLogger().info('foo')"| stdlib structlog --> |"logging.getLogger().info(#quot;{'event': 'foo'}#quot;)"| stdlib ==> Output Output ``` A basic configuration to output structured logs in JSON format looks like this: ```python import structlog structlog.configure( processors=[ # If log level is too low, abort pipeline and throw away log entry. structlog.stdlib.filter_by_level, # Add the name of the logger to event dict. structlog.stdlib.add_logger_name, # Add log level to event dict. structlog.stdlib.add_log_level, # Perform %-style formatting. structlog.stdlib.PositionalArgumentsFormatter(), # Add a timestamp in ISO 8601 format. structlog.processors.TimeStamper(fmt="iso"), # If the "stack_info" key in the event dict is true, remove it and # render the current stack trace in the "stack" key. structlog.processors.StackInfoRenderer(), # If the "exc_info" key in the event dict is either true or a # sys.exc_info() tuple, remove "exc_info" and render the exception # with traceback into the "exception" key. structlog.processors.format_exc_info, # If some value is in bytes, decode it to a Unicode str. structlog.processors.UnicodeDecoder(), # Add callsite parameters. structlog.processors.CallsiteParameterAdder( { structlog.processors.CallsiteParameter.FILENAME, structlog.processors.CallsiteParameter.FUNC_NAME, structlog.processors.CallsiteParameter.LINENO, } ), # Render the final event dict as JSON. structlog.processors.JSONRenderer() ], # `wrapper_class` is the bound logger that you get back from # get_logger(). This one imitates the API of `logging.Logger`. wrapper_class=structlog.stdlib.BoundLogger, # `logger_factory` is used to create wrapped loggers that are used for # OUTPUT. This one returns a `logging.Logger`. The final value (a JSON # string) from the final processor (`JSONRenderer`) will be passed to # the method of the same name as that you've called on the bound logger. logger_factory=structlog.stdlib.LoggerFactory(), # Effectively freeze configuration after creating the first bound # logger. cache_logger_on_first_use=True, ) ``` To make your program behave like a proper [12 Factor App](https://12factor.net/logs) that outputs only JSON to `stdout`, configure the `logging` module like this: ``` import logging import sys logging.basicConfig( format="%(message)s", stream=sys.stdout, level=logging.INFO, ) ``` In this case *only* your own logs are formatted as JSON: ```pycon >>> structlog.get_logger("test").warning("hello") {"event": "hello", "logger": "test", "level": "warning", "timestamp": "2017-03-06T07:39:09.518720Z"} >>> logging.getLogger("test").warning("hello") hello ``` ### Rendering using `logging`-based formatters You can choose to use *structlog* only for building the event dictionary and leave all formatting -- additionally to the output -- to the standard library. ```{mermaid} :align: center flowchart TD User structlog stdlib[Standard Library e.g. logging.StreamHandler] User --> |"structlog.get_logger().info('foo', bar=42)"| structlog User --> |"logging.getLogger().info('foo')"| stdlib structlog --> |"logging.getLogger().info('foo', extra={"bar": 42})"| stdlib ==> Output Output ``` ```python import structlog structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.UnicodeDecoder(), # Transform event dict into `logging.Logger` method arguments. # "event" becomes "msg" and the rest is passed as a dict in # "extra". IMPORTANT: This means that the standard library MUST # render "extra" for the context to appear in log entries! See # warning below. structlog.stdlib.render_to_log_kwargs, ], logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) ``` Now you have the event dict available within each log record. If you want *all* your log entries (meaning: also those not from your application / *structlog*) to be formatted as JSON, you can use the [*python-json-logger*] library: ```python import logging import sys from pythonjsonlogger import jsonlogger handler = logging.StreamHandler(sys.stdout) handler.setFormatter(jsonlogger.JsonFormatter()) root_logger = logging.getLogger() root_logger.addHandler(handler) ``` Now both *structlog* and `logging` will emit JSON logs: ```pycon >>> structlog.get_logger("test").warning("hello") {"message": "hello", "logger": "test", "level": "warning"} >>> logging.getLogger("test").warning("hello") {"message": "hello"} ``` :::{warning} With this approach, it's the standard library `logging` formatter's duty to do something useful with the event dict. In the above example that's `jsonlogger.JsonFormatter`. Keep this in mind if you only get the event name without any context, and exceptions are ostensibly swallowed. ::: (processor-formatter)= ### Rendering using *structlog*-based formatters within `logging` Finally, the most ambitious approach. Here, you use *structlog*'s {class}`~structlog.stdlib.ProcessorFormatter` as a {any}`logging.Formatter` for both `logging` as well as *structlog* log entries. Consequently, the output is the duty of the standard library too. ```{mermaid} :align: center flowchart TD User structlog structlog2[structlog] stdlib["Standard Library"] User --> |"structlog.get_logger().info(#quot;foo#quot;, bar=42)"| structlog User --> |"logging.getLogger().info(#quot;foo#quot;)"| stdlib structlog --> |"logging.getLogger().info(event_dict, {#quot;extra#quot;: {#quot;_logger#quot;: logger, #quot;_name#quot;: name})"| stdlib stdlib --> |"structlog.stdlib.ProcessorFormatter.format(logging.Record)"| structlog2 structlog2 --> |"Returns a string that is passed into logging handlers. This flow is controlled by the logging configuration."| stdlib2 stdlib2[Standard Library e.g. logging.StreamHandler] ==> Output ``` {class}`~structlog.stdlib.ProcessorFormatter` has two parts to its API: 1. On the *structlog* side, the {doc}`processor chain ` must be configured to end with {func}`structlog.stdlib.ProcessorFormatter.wrap_for_formatter` as the renderer. It converts the processed event dictionary into something that `ProcessorFormatter` understands. 2. On the `logging` side, you must configure `ProcessorFormatter` as your formatter of choice. `logging` then calls `ProcessorFormatter`'s `format()` method. For that, `ProcessorFormatter` wraps a processor chain that is responsible for rendering your log entries to strings. :::{warning} When using {class}`~structlog.stdlib.ProcessorFormatter`, you **must not** use {func}`~structlog.stdlib.render_to_log_kwargs` or {func}`~structlog.stdlib.render_to_log_args_and_kwargs` in your processor chain. You _**must**_ use {func}`structlog.stdlib.ProcessorFormatter.wrap_for_formatter()` -- otherwise you _**will**_ get puzzling errors from the standard library. ::: Thus, the simplest possible configuration looks like the following: ```python import logging import structlog structlog.configure( processors=[ # Prepare event dict for `ProcessorFormatter`. structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), ) formatter = structlog.stdlib.ProcessorFormatter( processors=[structlog.dev.ConsoleRenderer()], ) handler = logging.StreamHandler() # Use OUR `ProcessorFormatter` to format all `logging` entries. handler.setFormatter(formatter) root_logger = logging.getLogger() root_logger.addHandler(handler) root_logger.setLevel(logging.INFO) ``` which will allow both of these to work in other modules: ```pycon >>> import logging >>> import structlog >>> logging.getLogger("stdlog").info("woo") woo _from_structlog=False _record= >>> structlog.get_logger("structlog").info("amazing", events="oh yes") amazing _from_structlog=True _record= events=oh yes ``` Of course, you probably want timestamps and log levels in your output. The `ProcessorFormatter` has a `foreign_pre_chain` argument which is responsible for adding properties to events from the standard library -- in other words, those that do not originate from a *structlog* logger -- and which should in general match the `processors` argument to {func}`structlog.configure` so you get a consistent output. `_from_structlog` and `_record` allow your processors to determine whether the log entry is coming from *structlog*, and to extract information from `logging.LogRecord`s and add them to the event dictionary. However, you probably don't want to have them in your log files, thus we've added the `ProcessorFormatter.remove_processors_meta` processor to do so conveniently. For example, to add timestamps, log levels, and traceback handling to your logs without `_from_structlog` and `_record` noise you should do: ```python timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S") shared_processors = [ structlog.stdlib.add_log_level, timestamper, ] structlog.configure( processors=shared_processors + [ structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), cache_logger_on_first_use=True, ) formatter = structlog.stdlib.ProcessorFormatter( # These run ONLY on `logging` entries that do NOT originate within # structlog. foreign_pre_chain=shared_processors, # These run on ALL entries after the pre_chain is done. processors=[ # Remove _record & _from_structlog. structlog.stdlib.ProcessorFormatter.remove_processors_meta, structlog.dev.ConsoleRenderer(), ], ) ``` which (given the same `logging.*` calls as in the previous example) will result in: ```pycon >>> logging.getLogger("stdlog").info("woo") 2021-11-15 11:41:47 [info ] woo >>> structlog.get_logger("structlog").info("amazing", events="oh yes") 2021-11-15 11:41:47 [info ] amazing events=oh yes ``` This allows you to set up some sophisticated logging configurations. For example, to use the standard library's `logging.config.dictConfig` to log colored logs to the console and plain logs to a file you could do: ```python import logging.config import structlog timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S") pre_chain = [ # Add the log level and a timestamp to the event_dict if the log entry # is not from structlog. structlog.stdlib.add_log_level, # Add extra attributes of LogRecord objects to the event dictionary # so that values passed in the extra parameter of log methods pass # through to log output. structlog.stdlib.ExtraAdder(), timestamper, ] def extract_from_record(_, __, event_dict): """ Extract thread and process names and add them to the event dict. """ record = event_dict["_record"] event_dict["thread_name"] = record.threadName event_dict["process_name"] = record.processName return event_dict logging.config.dictConfig( { "version": 1, "disable_existing_loggers": False, "formatters": { "plain": { "()": structlog.stdlib.ProcessorFormatter, "processors": [ structlog.stdlib.ProcessorFormatter.remove_processors_meta, structlog.dev.ConsoleRenderer(colors=False), ], "foreign_pre_chain": pre_chain, }, "colored": { "()": structlog.stdlib.ProcessorFormatter, "processors": [ extract_from_record, structlog.stdlib.ProcessorFormatter.remove_processors_meta, structlog.dev.ConsoleRenderer(colors=True), ], "foreign_pre_chain": pre_chain, }, }, "handlers": { "default": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "colored", }, "file": { "level": "DEBUG", "class": "logging.handlers.WatchedFileHandler", "filename": "test.log", "formatter": "plain", }, }, "loggers": { "": { "handlers": ["default", "file"], "level": "DEBUG", "propagate": True, }, }, } ) structlog.configure( processors=[ structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), timestamper, structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) ``` This defines two formatters: one plain and one colored. Both are run for each log entry. Log entries that do not originate from *structlog*, are additionally pre-processed using a cached `timestamper` and {func}`~structlog.stdlib.add_log_level`. Additionally, for both `logging` and *structlog* -- but only for the colorful logger -- we also extract some data from {class}`logging.LogRecord`: ```pycon >>> logging.getLogger().warning("bar") 2021-11-15 13:26:52 [warning ] bar process_name=MainProcess thread_name=MainThread >>> structlog.get_logger("structlog").warning("foo", x=42) 2021-11-15 13:26:52 [warning ] foo process_name=MainProcess thread_name=MainThread x=42 >>> pathlib.Path("test.log").read_text() 2021-11-15 13:26:52 [warning ] bar 2021-11-15 13:26:52 [warning ] foo x=42 ``` (Sadly, you have to imagine the colors in the first two outputs.) If you leave `foreign_pre_chain` as `None`, formatting will be left to `logging`. Meaning: you can define a `format` for {class}`~structlog.stdlib.ProcessorFormatter` too! [*python-json-logger*]: https://github.com/nhairs/python-json-logger structlog-25.5.0/docs/testing.md0000644000000000000000000000547515077624341013532 0ustar00# Testing *structlog* comes with tools for testing the logging behavior of your application. If you need functionality similar to {meth}`unittest.TestCase.assertLogs`, or you want to capture all logs for some other reason, you can use the {func}`structlog.testing.capture_logs` context manager: ```{doctest} >>> from structlog import get_logger >>> from structlog.testing import capture_logs >>> with capture_logs() as cap_logs: ... get_logger().bind(x="y").info("hello") >>> cap_logs [{'x': 'y', 'event': 'hello', 'log_level': 'info'}] ``` Note that inside the context manager all configured processors are disabled. If you want to capture logs after certain processors have been applied, they can be passed to the context manager. For example, to capture {doc}`contextvars`: ```{doctest} >>> from structlog import contextvars, get_logger >>> from structlog.testing import capture_logs >>> with capture_logs(processors=[contextvars.merge_contextvars]) as cap_logs: ... _ = contextvars.bind_contextvars(x="y") ... get_logger().info("hello") >>> cap_logs [{'event': 'hello', 'x': 'y', 'log_level': 'info'}] ``` :::{note} `capture_logs()` relies on changing the configuration. If you have *cache_logger_on_first_use* enabled for {doc}`performance `, any cached loggers will not be affected, so it’s recommended you do not enable it during tests. ::: You can build your own helpers using {class}`structlog.testing.LogCapture`. For example a [*pytest*](https://docs.pytest.org/) fixture to capture log output could look like this: ``` @pytest.fixture(name="log_output") def fixture_log_output(): return LogCapture() @pytest.fixture(autouse=True) def fixture_configure_structlog(log_output): structlog.configure( processors=[log_output] ) def test_my_stuff(log_output): do_something() assert log_output.entries == [...] ``` --- You can also use {class}`structlog.testing.CapturingLogger` (directly, or via {class}`~structlog.testing.CapturingLoggerFactory` that always returns the same logger) that is more low-level and great for unit tests: ```{doctest} >>> import structlog >>> cf = structlog.testing.CapturingLoggerFactory() >>> structlog.configure(logger_factory=cf, processors=[structlog.processors.JSONRenderer()]) >>> log = get_logger() >>> log.info("test!") >>> cf.logger.calls [CapturedCall(method_name='info', args=('{"event": "test!"}',), kwargs={})] ``` ```{testcleanup} import structlog structlog.reset_defaults() ``` --- Additionally *structlog* also ships with a logger that just returns whatever it gets passed into it: {class}`structlog.testing.ReturnLogger`. ```{doctest} >>> from structlog import ReturnLogger >>> ReturnLogger().info(42) == 42 True >>> obj = ["hi"] >>> ReturnLogger().info(obj) is obj True >>> ReturnLogger().info("hello", when="again") (('hello',), {'when': 'again'}) ``` structlog-25.5.0/docs/thread-local.md0000644000000000000000000001534615077624341014412 0ustar00# Legacy Thread-local Context :::{attention} The `structlog.threadlocal` module is deprecated as of *structlog* 22.1.0 in favor of {doc}`contextvars`. The standard library {mod}`contextvars` module provides a more feature-rich superset of the thread-local APIs and works with thread-local data, async code, and greenlets. The plan was to remove this module after 2023, however people are reporting [odd crashes with {doc}`contextvars`, that we can't reproduce](https://github.com/hynek/structlog/issues/591). Until we find a solution, this module will **not** be removed, since it's a viable workaround. ::: ```{testsetup} * import structlog structlog.configure( processors=[structlog.processors.KeyValueRenderer()], ) ``` ```{testcleanup} * import structlog structlog.reset_defaults() ``` ## The `merge_threadlocal` processor *structlog* provides a simple set of functions that allow explicitly binding certain fields to a global (thread-local) context and merge them later using a processor into the event dict. The general flow of using these functions is: - Use {func}`structlog.configure` with {func}`structlog.threadlocal.merge_threadlocal` as your first processor. - Call {func}`structlog.threadlocal.clear_threadlocal` at the beginning of your request handler (or whenever you want to reset the thread-local context). - Call {func}`structlog.threadlocal.bind_threadlocal` as an alternative to your bound logger's `bind()` when you want to bind a particular variable to the thread-local context. - Use *structlog* as normal. Loggers act as they always do, but the {func}`structlog.threadlocal.merge_threadlocal` processor ensures that any thread-local binds get included in all of your log messages. - If you want to access the thread-local storage, you use {func}`structlog.threadlocal.get_threadlocal` and {func}`structlog.threadlocal.get_merged_threadlocal`. These functions map 1:1 to the {doc}`contextvars` APIs, so please use those instead: - {func}`structlog.contextvars.merge_contextvars` - {func}`structlog.contextvars.clear_contextvars` - {func}`structlog.contextvars.bind_contextvars` - {func}`structlog.contextvars.get_contextvars` - {func}`structlog.contextvars.get_merged_contextvars` ## Thread-local contexts *structlog* also provides thread-local context storage in a form that you may already know from Flask and that makes the *entire context* global to your thread or greenlet. This makes its behavior more difficult to reason about which is why we generally recommend to use the {func}`~structlog.contextvars.merge_contextvars` route. Therefore, there are currently no plans to re-implement this behavior on top of context variables. ### Wrapped dicts In order to make your context thread-local, *structlog* ships with a function that can wrap any dict-like class to make it usable for thread-local storage: {func}`structlog.threadlocal.wrap_dict`. Within one thread, every instance of the returned class will have a *common* instance of the wrapped dict-like class: ```{doctest} >>> from structlog.threadlocal import wrap_dict >>> WrappedDictClass = wrap_dict(dict) >>> d1 = WrappedDictClass({"a": 1}) >>> d2 = WrappedDictClass({"b": 2}) >>> d3 = WrappedDictClass() >>> d3["c"] = 3 >>> d1 is d3 False >>> d1 == d2 == d3 == WrappedDictClass() True >>> d3 # doctest: +ELLIPSIS ``` To enable thread-local context use the generated class as the context class: ```python configure(context_class=WrappedDictClass) ``` :::{note} Creation of a new `BoundLogger` initializes the logger's context as `context_class(initial_values)`, and then adds any values passed via `.bind()`. As all instances of a wrapped dict-like class share the same data, in the case above, the new logger's context will contain all previously bound values in addition to the new ones. ::: `structlog.threadlocal.wrap_dict` returns always a completely *new* wrapped class: ```{doctest} >>> from structlog.threadlocal import wrap_dict >>> WrappedDictClass = wrap_dict(dict) >>> AnotherWrappedDictClass = wrap_dict(dict) >>> WrappedDictClass() != AnotherWrappedDictClass() True >>> WrappedDictClass.__name__ # doctest: +SKIP WrappedDict-41e8382d-bee5-430e-ad7d-133c844695cc >>> AnotherWrappedDictClass.__name__ # doctest: +SKIP WrappedDict-e0fc330e-e5eb-42ee-bcec-ffd7bd09ad09 ``` In order to be able to bind values temporarily to a logger, `structlog.threadlocal` comes with a [context manager](https://docs.python.org/2/library/stdtypes.html#context-manager-types): {func}`structlog.threadlocal.tmp_bind`: ```{testsetup} ctx from structlog import PrintLogger, wrap_logger from structlog.threadlocal import tmp_bind, wrap_dict WrappedDictClass = wrap_dict(dict) log = wrap_logger(PrintLogger(), context_class=WrappedDictClass) ``` ```{doctest} ctx >>> log.bind(x=42) # doctest: +ELLIPSIS , ...)> >>> log.msg("event!") x=42 event='event!' >>> with tmp_bind(log, x=23, y="foo") as tmp_log: ... tmp_log.msg("another event!") x=23 y='foo' event='another event!' >>> log.msg("one last event!") x=42 event='one last event!' ``` The state before the `with` statement is saved and restored once it's left. If you want to detach a logger from thread-local data, there's {func}`structlog.threadlocal.as_immutable`. #### Downsides & caveats The convenience of having a thread-local context comes at a price though: :::{warning} - If you can't rule out that your application reuses threads, you *must* remember to **initialize your thread-local context** at the start of each request using {func}`~structlog.BoundLogger.new` (instead of {func}`~structlog.BoundLogger.bind`). Otherwise you may start a new request with the context still filled with data from the request before. - **Don't** stop assigning the results of your `bind()`s and `new()`s! **Do**: ``` log = log.new(y=23) log = log.bind(x=42) ``` **Don't**: ``` log.new(y=23) log.bind(x=42) ``` Although the state is saved in a global data structure, you still need the global wrapped logger produce a real bound logger. Otherwise each log call will result in an instantiation of a temporary BoundLogger. See `configuration` for more details. - It [doesn't play well](https://github.com/hynek/structlog/issues/296) with `os.fork` and thus `multiprocessing` (unless configured to use the `spawn` start method). ::: ## API ```{eval-rst} .. module:: structlog.threadlocal .. autofunction:: bind_threadlocal .. autofunction:: unbind_threadlocal .. autofunction:: bound_threadlocal .. autofunction:: get_threadlocal .. autofunction:: get_merged_threadlocal .. autofunction:: merge_threadlocal .. autofunction:: clear_threadlocal .. autofunction:: wrap_dict .. autofunction:: tmp_bind(logger, **tmp_values) .. autofunction:: as_immutable ``` structlog-25.5.0/docs/twisted.md0000644000000000000000000001032215077624341013523 0ustar00# Twisted :::{warning} Since `sys.exc_clear` has been dropped in Python 3, there is currently no way to avoid multiple tracebacks in your log files if using *structlog* together with Twisted on Python 3. ::: :::{note} *structlog* currently only supports the legacy -- but still perfectly working -- Twisted logging system found in `twisted.python.log`. ::: ## Concrete bound logger To make *structlog*'s behavior less magical, it ships with a Twisted-specific wrapper class that has an explicit API instead of improvising: `structlog.twisted.BoundLogger`. It behaves exactly like the generic `structlog.BoundLogger` except: - it's slightly faster due to less overhead, - has an explicit API ({func}`~structlog.twisted.BoundLogger.msg` and {func}`~structlog.twisted.BoundLogger.err`), - hence causing less cryptic error messages if you get method names wrong. In order to avoid that *structlog* disturbs your CamelCase harmony, it comes with an alias for `structlog.get_logger` called `structlog.getLogger`. ## Processors *structlog* comes with two Twisted-specific processors: {func}`structlog.twisted.EventAdapter` : This is useful if you have an existing Twisted application and just want to wrap your loggers for now. It takes care of transforming your event dictionary into something [twisted.python.log.err](https://docs.twisted.org/en/stable/api/twisted.python.log.html#err) can digest. For example: ```python def onError(fail): failure = fail.trap(MoonExploded) log.err(failure, _why="event-that-happened") ``` will still work as expected. Needs to be put at the end of the processing chain. It formats the event using a renderer that needs to be passed into the constructor: ```python configure(processors=[EventAdapter(KeyValueRenderer()]) ``` The drawback of this approach is that Twisted will format your exceptions as multi-line log entries which is painful to parse. Therefore *structlog* comes with: {func}`structlog.twisted.JSONRenderer` : Goes a step further and circumvents Twisted logger's Exception / Failure handling and renders it itself as JSON strings. That gives you regular and simple-to-parse single-line JSON log entries no matter what happens. ## Bending foreign logging to your will *structlog* comes with a wrapper for Twisted's log observers to ensure the rest of your logs are in JSON too: `structlog.twisted.JSONLogObserverWrapper`. What it does is determining whether a log entry has been formatted by `structlog.twisted.JSONRenderer` and if not, converts the log entry to JSON with `event` being the log message and putting Twisted's `system` into a second key. So for example: ``` 2013-09-15 22:02:18+0200 [-] Log opened. ``` becomes: ``` 2013-09-15 22:02:18+0200 [-] {"event": "Log opened.", "system": "-"} ``` There is obviously some redundancy here. Also, I'm presuming that if you write out JSON logs, you're going to let something else parse them which makes the human-readable date entries more trouble than they're worth. To get a clean log without timestamps and additional system fields (`[-]`), *structlog* comes with `structlog.twisted.PlainFileLogObserver` that writes only the plain message to a file and `structlog.twisted.plainJSONStdOutLogger` that composes it with the aforementioned `structlog.twisted.JSONLogObserverWrapper` and gives you a pure JSON log without any timestamps or other noise straight to [standard out]: ```console $ twistd -n --logger structlog.twisted.plainJSONStdOutLogger web {"event": "Log opened.", "system": "-"} {"event": "twistd 13.1.0 (python 2.7.3) starting up.", "system": "-"} {"event": "reactor class: twisted...EPollReactor.", "system": "-"} {"event": "Site starting on 8080", "system": "-"} {"event": "Starting factory ", ...} ... ``` ## Suggested configuration ```python import structlog structlog.configure( processors=[ structlog.processors.StackInfoRenderer(), structlog.twisted.JSONRenderer() ], context_class=dict, logger_factory=structlog.twisted.LoggerFactory(), wrapper_class=structlog.twisted.BoundLogger, cache_logger_on_first_use=True, ) ``` See also {doc}`logging-best-practices`. [standard out]: https://en.wikipedia.org/wiki/Standard_out#Standard_output_.28stdout.29 structlog-25.5.0/docs/typing.md0000644000000000000000000000355515077624341013364 0ustar00# Type Hints Static type hints -- together with a type checker like [Mypy](https://mypy.readthedocs.io/en/stable/) -- are an excellent way to make your code more robust, self-documenting, and maintainable in the long run. And as of 20.2.0, *structlog* comes with type hints for all of its APIs. Since *structlog* is highly configurable and tries to give a clean façade to its users, adding types without breaking compatibility -- while remaining useful! -- was a formidable task. --- The main problem is that `structlog.get_logger()` returns whatever you've configured the *bound logger* to be. The only commonality are the binding methods like `bind()` and we've extracted them into the {class}`structlog.typing.BindableLogger` {class}`~typing.Protocol`. But using that as a return type is worse than useless, because you'd have to use {func}`typing.cast` on every logger returned by `structlog.get_logger()`, if you wanted to actually call any logging methods. The second problem is that said `bind()` and its cousins are inherited from a common base class (a [big](https://www.youtube.com/watch?v=3MNVP9-hglc) [mistake](https://python-patterns.guide/gang-of-four/composition-over-inheritance/) in hindsight) and can't know what concrete class subclasses them and therefore what type they are returning. The chosen solution is adding {func}`structlog.stdlib.get_logger()` that just calls `structlog.get_logger()` but has the correct type hints and adding `structlog.stdlib.BoundLogger.bind` et al that also only delegate to the base class. `structlog.get_logger()` is typed as returning {any}`typing.Any` so you can use your own type annotation and stick to the old APIs, if that's what you prefer: ``` import structlog logger: structlog.stdlib.BoundLogger = structlog.get_logger() logger.info("hi") # <- ok logger.msg("hi") # <- Mypy: 'error: "BoundLogger" has no attribute "msg"' ``` structlog-25.5.0/docs/why.md0000644000000000000000000001122715077624341012654 0ustar00# Why … ## … structured logging? > I believe the widespread use of format strings in logging is based on two presumptions: > > - The first level consumer of a log message is a human. > - The programmer knows what information is needed to debug an issue. > > I believe these presumptions are **no longer correct** in server side software. > > —[Paul Querna](https://paul.querna.org/articles/2011/12/26/log-for-machines-in-json/) Structured logging means that you don't write hard-to-parse and hard-to-keep-consistent prose in your log entries. Instead, you log *events* that happen in a *context* of key-value pairs. :::{tip} More general advice about production-grade logging can be found in the later chapter on {doc}`logging-best-practices`. ::: ## … structlog? ### Easier logging You can stop writing prose and start thinking in terms of an event that happens in the context of key-value pairs: ```pycon >>> from structlog import get_logger >>> log = get_logger() >>> log.info("key_value_logging", out_of_the_box=True, effort=0) 2020-11-18 09:17:09 [info ] key_value_logging effort=0 out_of_the_box=True ``` Each log entry is a meaningful dictionary instead of an opaque string now! That said, *structlog* is not taking anything away from you. You can still use string interpolation using positional arguments: ```pycon >>> log.info("Hello, %s!", "world") 2022-10-10 07:19:25 [info ] Hello, world! ``` Dictionary-based interpolation works too: ```pycon >>> log.info("Hello, %(name)s!", {"name": "world"}) 2025-09-11 13:00:42 [info ] Hello, world! ``` ### Data binding Since log entries are dictionaries, you can start binding and re-binding key-value pairs to your loggers to ensure they are present in every following logging call: ```pycon >>> log = log.bind(user="anonymous", some_key=23) >>> log = log.bind(user="hynek", another_key=42) >>> log.info("user.logged_in", happy=True) 2020-11-18 09:18:28 [info ] user.logged_in another_key=42 happy=True some_key=23 user=hynek ``` You can also bind key-value pairs to {doc}`context variables ` that look global, but are local to your thread or *asyncio* context -- which usually means your web request. ### Powerful pipelines Each log entry goes through a [processor pipeline](processors.md) that is just a chain of functions that receive a dictionary and return a new dictionary that gets fed into the next function. That allows for simple but powerful data manipulation: ```python def timestamper(logger, log_method, event_dict): """Add a timestamp to each log entry.""" event_dict["timestamp"] = time.time() return event_dict ``` There are [plenty of processors](structlog.processors) for most common tasks coming with *structlog*: - Collectors of [call stack information](structlog.processors.StackInfoRenderer) ("How did this log entry happen?"), - …and [exceptions](structlog.processors.format_exc_info) ("What happened‽"). - Flexible [timestamping](structlog.processors.TimeStamper). ### Formatting *structlog* is completely flexible about *how* the resulting log entry is emitted. Since each log entry is a dictionary, it can be formatted to **any** format: - A colorful key-value format for [local development](console-output.md), - [JSON](structlog.processors.JSONRenderer) or [*logfmt*](structlog.processors.LogfmtRenderer) for easy parsing, - or some standard format you have parsers for like *nginx* or Apache *httpd*. Internally, formatters are processors whose return value (usually a string) is passed into loggers that are responsible for the output of your message. *structlog* comes with multiple useful formatters out-of-the-box. ### Output *structlog* is also flexible with the final output of your log entries: - A **built-in** lightweight printer like in the examples above. Easy to use and fast. - Use the [**standard library**](standard-library.md)'s or [**Twisted**](twisted.md)'s logging modules for compatibility. In this case *structlog* works like a wrapper that formats a string and passes them off into existing systems that won't know that *structlog* even exists. Or the other way round: *structlog* comes with a `logging` formatter that allows for processing third party log records. - Don't format it to a string at all! *structlog* passes you a dictionary and you can do with it whatever you want. Reported use cases are sending them out via network or saving them to a database. ### Highly testable *structlog* is thoroughly tested and we see it as our duty to help you to achieve the same in *your* applications. That's why it ships with a [test helpers](testing.md) to introspect your application's logging behavior with little-to-no boilerplate. structlog-25.5.0/docs/_static/BoundLogger.svg0000644000000000000000000015476015077624341016113 0ustar00structlog-25.5.0/docs/_static/Justfile0000644000000000000000000000054315077624341014660 0ustar00rebuild-logos: && compress-logos magick structlog_logo.svg structlog_logo.png magick structlog_logo.svg -resize 220 structlog_logo_small.png magick structlog_logo.svg -resize 16x16 docset-icon.png magick structlog_logo.svg -resize 32x32 docset-icon@2x.png compress-logos: svgo *.svg oxipng --opt max --strip safe --zopfli *.png structlog-25.5.0/docs/_static/console_renderer.png0000644000000000000000000126317615077624341017224 0ustar00PNG  IHDR63X+iTXtXML:com.adobe.xmp *J- iCCPICC ProfileHPSY{!%D:w@J-tb vdqׂ( *`Qlb (b!7wfߜw{7@6BXLLGOH,:ŤMۇlǵ55_@8'f"| p(x#L "<4Ωס&O2u"':x#ITD'!wff-1M3Y*xr/HBNy25̑AJG"^93B,J>DɃc+e%NTŞb?T#6)@E@Ǝb4 j%YuS$,s$53b4>[7ق92BsXD _7n2ٻ+KVg~9)WL(2?Zba"/ RĥQ2圞8tNH( rR B92RXY 5MFg"Ogv6tG{G+6q7!ڕZpĦTt+dO& "_}` ,-pA4H L Kr"l Apxz@?x0 A"CH 2L!kb@PEB P 9Z AP9T B92 =zA-F$ fL3P8^‹\85>_=+xPJ(ebXpT"*%ADJQ5Tu ՃB}Fc4mDc\bJt9z?݁Ec]5cR1K0R^LҰX7l06]݀݉mĞvc#8N g…88www׏WD/OoU)Nr{m~(QhN"FӉkec;%%%#%wJJeJ.)*}&H,|tL&}ɉdy#|IlV)RPnVZbTYRrTʐ*ALQ]Zz\EA-\-SmZe8u3uzn}Ř¢p)k){((T,՜ʦS]a u gX4zh(M6юҾЛ~FÌ3>jhj5 55h~ѢkhehmjzֶҞD{!WPC]XJ7Rwnk#zzAzbzi% (3/t&]H/wЇ u Ն]FF1FyFFO va&MMLiM;M?ř3k1{ai657lAXlQcqkɰ̰iy rJn [Z wZw`lmD656lILlz^;]]]&3gn9󻽋~#u<6V\ Nd@UNNoλP\fsiw*qmpt3qKrtǠ2"1~Opy3ó,YY{fyyqzI?{p|j||0-~~&, YWz@L@y@ eAg1[\v-{8-dEHG()4*Ѿ;jjkt68Xtzy8#Gڏ263=VDi*ls[ZzZZoolk:awbIÓ4Nm:M<zLCR/jt>펹]B/\x|'%K'/{\>~qk.ך~u˵7ںgus-[oo_3NݘͿsw7>ZҧOk~ǵTgQq^.ks/Nx9e+ѡ?|m؟^#y3v;w;oy!OZf|e`tWײoھ~<96&H8 pJ o@Ny=AO @#Y!l5*FѾvrR~xw7,8Ԧ&OC@IDATx|F{ƀ TR/]rI9+R/\.{Sl mܰq f-u[6}>kF)_{)8m #D"@ D"@ D"@ D0 D"@ D"@ D"@ D@ @@D"@ D"@ D"@ D" D"@ D"@ D"@ D- J"@ D"@ D"@ D; w["@ D"@ D"@ D-KmRԴa VXV]/)4VVVл KPP'SxE_L$VUÅXXYBC~֎`aiu5fA-QGP"@ D"@ D"@ *0  _Ypįf]2[;3~DqN;uTV5Z-  c^ e$ Bts{ Bk7jjh#T"@ D"@ D"@:L" xNO@5>:W^Zml_! ct+<s2t. IpzvqTU06 A:m "@ D"@ D"@PIĤ\H̽1^>b7wnDl~0w`ck )WX@&BUWTŠ?mԨmRy"@ D"@ D"@ -%`;>'ut!pzw'%}xx'ٜǚ _x}|J?/!ZFC$z(a%D '6N`ai uL,+( pvnȾl遰A==S#4lWGX/KE@7&]ʖ?+wNJrd5{DAqv.r惽 _/0n1nԗ`ΐ$==R%cv-WP~-&PoKoVpG:*mAFЕ*K`+1,*Oʉz-{h5UlT2BFcwR3ĊMB6m D"@ D"@ D0,% L_Pа0x8.;+|W< (۱uڶQv jBxԉ#q]ylb[r5zȂcPaBX[{Nxa.Ek;@Ѫǔ^D}[z^>0H.:;ra2bHXʇpHEQi=*; ع]y?W>L̴qgp,. V0ptlay)Q =-qt,B',m1®EQU7fuEQ:ĆHx>䃍9NV ξeؽ8fq\+"PE-;.W03g Ab`wsog* nxXSY<~̄$(/(&.EM['q恗~d](TxyEs̛n4P̾ذ()ƌEh)rԲB落қ}aG::ysܤ'';Agk5.͗5>nɎNp_ͮ?^]GS D"@ D"@ D/&k`KR", mCZnš?p[Q]Q?hL@gX8ϱ2=P]DC"qMcbZH|GB1{3uDŽD΍,!Eݰo7 %Y\h? ?*ѻ0w>\gmm ؟y.N>mБ (Ɵ=CbT K Xzg6$Bb;P@D}6G^Zg{`eg&Q+khS(2^=LRQO."%e2B>/Õ 0)/(*afpo4.!b(ˬd  Ἂĝ!fP.(f'<K˂r)UƌE@a W. {x bu(TkT_6ʼR2 yW ++bWos cy:B 3]7gV,"@ D"@ D"@l.n/k3Ei;kt*'$~ pB!!Pȋ;qBfZ#x!^v ]|Q|i=***|iJ$mMz ~_ (aF\Nqڰ'U'z! qDQ[R0 RCasCQa>zZ.C,'mv-| )glWpDwi2PZ]'+ ?-L@,di0v~^<_o>#Zv{>[1cQf\{{wi1]YďWiq{(f0{C`LF D"@ D"@ ퟀjB\5!7Uf1ѩ5 - Gk(;sywU߼Wkp0cgK XpupB}6KŘYNT8&T3ٲ4 L׳X@p2|w(Ot葆) &xfXԇkTf*v|D\Q! g+ GeCrDH2"@ D"@ D"@hL. +T„ɗey)i@,..^z(,J B,$玭a U,ZPUz0<&]m/kzYHTIΓcç*Ymmݹ[b"xHN1 \WPX@6.Hφ?m\ÙN} H?{xY؄^'tx,_fP=OKFt"" cb<)YXYWx\sjY( Gb MX?>[cQfXIywy$t8LQ,FD ;(|;#D"@ D"@ DG&gtt]MVVñ!Pu &z0Q]5k62/;6C}ľ;s$$np͵:W`CymuP(z*M@l!C@(y/~\Ѱ5!uZwhkԂZEѬ("' L>Ć=aPqJ5aqsR\X595bA[v g=ŗ{_Άb^WIn:&8ďL=fq=D ;5|"@ D"@ D"@@'`2nC D&5[\PZpafx gIJ%ϰ5γT<llm}S&2ʪ8u&89`1bX P@_?WLչi>k1(fvxsu5"wd#TuE%g/uzXNCǴQMnSNנI>QADd4bxx=iBŝ<,,Ț0_cgo/@mM qOt&WJ]hq^N`p(#(8 ^xùpqq_@&j@Bg.J{WgGBݜ[8+|}71CD!C5F=|Pl ϟ*P^P) y`4a zv&lq>+Kz\^˙ ﬉ފplZ(*ajEΘYE#EY9rB-wiWћte ||?c]2"@ D"@ D"@8jcN6rڇ*6`UE+KP;}!1/R+sPDFY%j$qIv<%k7٬NSU?O66zvmB"ϻ ;lP<֑21<7$2>iz@-1Eӡ߼PBHVWz5o*qaFB(%l;' ^?ZVP,,?Cu CxϙP~)3vX9_QT»= %^/G/wA(°%ss/8,TA["@ D"@ D"@ Lqfמ2<5հEY^kW_|(UUV‘C{~ni'T?(bxby >z.ԱSɻ߄sgN9ꞌaKʽeipKG(WהC1ѻK#vlO]Tlys8< +5v+CQ7)ݰ[Tq_*>ݸ,@q9E,'(fPc,*wͫ0k=[݃EϬ Iyb&w,zoyT"@ D"@ D"@N=o6Yxd죷yC::9s dy7s~!V08jd^{/CAAo`C1l( (@EyysjQYl 0T3q,ІXmQtQp}Aour^zuLAq ǟQ5t2?}݅"aB{qV..l4<֑BL1O/fKsy䵸BvʒRKvs+үwusND"@ D"@ D"$9C|z'zjCGBlQl lE$3/!˵(2i,F`= = ٺM0};5<*H D"@ D"@ # sI=knR<"L#4O|c?na^(BT&u؉~ʵSuD"@ D"@ D"`H@lU_,Ɵ=%S 3]݇D xc;Ku5L-"@ D"@ D"@@  c{K]ee>%@s _{ 7@U l)?/P}7逯 :LLj D"@ D"@ DL t6~MXRo?TDA "@ D"@ D"@ DHSppSN/01v}Thy vīJc"D"@ D"@ DN ; D"@ D"@ D"@PSSz D"@ D"@ D"@@;$@b;he"@ D"@ D"@ D"`*$ ,K D"@ D"@ D"@!E."@ D"@ D"@ DS4UT/ DG $4Xaɣpb1$&0x( TlfUPR\x2 D"`.^\\ =^ٯ00`= ^Tś[UQ v]RlVwR7+'S nD  Grp#t.ppN>4 F,~6db?cz(h-iz]cW;w3 EOϾO!M(rQP1lh7K"$0hgo`ck ;wj(**‚M}Nl먫r(e˛jma8Z}VSJ9ij֭SU cT^ɳ\ԪC . VV) Ӧ4 ֦EMܾ}rj\[i}IWjS% z_OGJeMWSS&k"JE##a샃y}YŪ 60gA/#QvΩw0G0ek6ACG¼R]@[*W_"QI{y17+3+{{^7 mN@{6P;Oo^o$ cߴ4zhVw364#;zoބcٸҪei5ѓ?zv֮.%^`1{>?WĄJ]TRjɖENp(&]J-`}Ft1'bg`׶-P?Rœ9l^Yvf|gsmꚜP㳯ޤ͘3yt;gTWUw?ghC!5KQm D"`k"Cw:׾*ڱ r @_Y^{qp^3}!6BUM/[܏>]u7a}-5O0k3:OdyWXw@[\z\O޽F23m:%7  PU@D{=83FEU<𘭝vb3&._8a1bx o+++yl悻'L> J`Z~3/Js2:b 5:uxA52mj;M-gXtۈC?B? G[^n.C 8t`!wUiccKC7}5msg_.%3EЇlr׎#"n΍n; h0H/nb"@ Da.Z vnQlv@[(Hφߞ[֍aρaϕ5w'')U<=m [`h)'ۢyڴ`0 x_ʻy7 =21 _,ĮSD# }Ée!#i=WD\WeazZ*lްnHCa{F( lPBQܺqw [ C0 6pd0Pm`X9nX:b Ͼt 0ZkL{t1|ԔdYmm`յt[6BEE;|)! dmW(}me7aGϾ`EVo!\pD" )Wg;1A<īNc&DaPF%Ciuhb.4?ou]HO&ȡ^sEÕ[{g'녅KZ@dw/%sw/ =#˕)EyD?2ӯÑzq=珟,91?{wn" Y>尬`}ׂļkWEv|C[{Csv#+6>U:OkےƇPXC{8 07Nf6l쭡 !?8~ #6. &C֪~iDβypOFTO% D"@̈A#%=c6^6^ImXwB\rï' 7YԿb= |[5$.\#mOcN@UC"fp DXP]1($rp5&9  l8W܄?lYKjk:S;g!>~SiSicmC}xռj0ut~Baݐm%COS'ϵ15B#bu,R_3,ar={v@qSݺ[2>ؗ>Blma`/ {=XY[@첯®obWǐɱc׸h{;j(-L8 Hz+VT9!0`a)ݘY۾8gwCS;y8GUXqrRK`uh8/WQR _$g˜ aÇ^bh1Ȩ:c._RʟiRyȱl-q NlzkJGCtއ=v-}l?grsء}.J2c"cXBAa0i<8!G` Sc_7k9L,?~x?e7Q2{c!>bcaa ZrLG|ԙa,Ś?Tsg?0۷xz%jP2¡9 n>`ek;[B]m5W.ipa<,r@X&9%,l=h%T͌pVH9/ :"Mug%åc`oox,/6σz$Y'El򓣋~(}+!? A5|v#;K_*烛b\2SXb'ֆ^6zoKlj0m3f,ҶB#CQ}LVp>AZkW6mmSR*g47{%Ըbt"3Q.KcT =LHt`/?&*־?WNapwװ4.5Ɲu"߀V(0* >b=dxj CvuM#Q L#N?7^,c{n= 11 XoC,2+P8u&|\+(ɘAA]MK'Bث]׿0yX 4$ Ft*˻' zyko/?&蚷C,헟D5@QI:ݾğ;ݠO3+kE13+Sc,B{lm{H֟DBxҫ@.2s\<3ߍE|s`B`x]3a. LOϺŀx JDT}rqظ7BHvBWLz)OE/~(ȠA1 p}zɶlg^Ao)5 Kk6|C  m_-eK+"۰y:a +{.nU@߉z0N>nfq>vҞp‘},\X}a5!O:y3Qv =?;#ϦJ(Ʈuu]P+¹=+`3?R/& s^3{DLD +T%n.(t_dp D6Q=7W7]LQha@/_.y8xW_2"@̗@(i~̗*hE蝈 9ZbBc0jmm Be]axcaJ_Ŵ1jEư-2ҥLV)>nȄF x=׮Ik|}mM9Φ|Gz|oo{4{g[8=RWSV܋ #z=!j#z)K>}yIxXW{ 2.fC!лPe*cF9&?Um֋C\EV|M{zmRe'<y=[(Dີא͘PO9f۽M2vCOr+D0pL'xxXB/DAϮ` byyBⶒyɠ篮xXea~'Dc9E=}pEDЋ-jdt|ŷ#yQ_Y)[]@s{"=+R*,>h䂗 Iv/k 3`:jREN> _f?(ըץL{LO6TN x#؉G{lejSBÎ@ݣg_CpĿ \W̄'y#zUޱ4Ppå%yD,? P0 /ס~"N=!k<d"zFa=?iG'z0v2# I¥* (G\rn{ߙ'^O^8op?7qP^SɈ+ˊX^C<) ϱ c\8fU7t* ޟdNqAՑBv6K,P++j&֕܀<9t8z5$=#v,r20‡߮f/ 3_9zD4|vڕVv6gqԟ`/NqOq2k&XY9V.Suwx~E! a6. r Rڵ2'# Y?@½;+0C0Y;l\?O8nL7PH|H_1=z_!mLS35q*z&1 P25k΅hњ" ݵMSg{B\qMiX'ُʿp¼{H._msi~Lda? a?7PA|MCtIq@| .Ԇ=AOƸτ?TD $wn%xpŝ_޽[,3@8@3abXC(aDwo" a W?kZz~zѫj?⊈TDI^z$j|H$ w@~'HIЊȁ}'txlfD^b#ڹ}0Ԥopu9yF\{/~o\;϶q²?J1э [j׬{:vt)#- ?ߚ5G0ɽ gC-BwHw. $ 50uh ﲉw/zwF1D]P۷_=c-؄^_7Le;դfU_V&w/lsIl-~u룔I֯W}8o_8wUŭ=6jtd" :0πdaV x /*jQa (ms!̊yJ.EO9ї<'ܙSMGLz,7XhftC 0}<^~h2Q-(|@${a8ֳOBYYg} ጲ<ɶ73g+Y?^*ApL,o |=ٚJyye8f4E0C:(P]ʺ+(^:}&YZǙ‘ *$q_),T<S^9k:`)@IDAT=NvE& OQlD%b0dW._qÆ, 9J#z!iw։ȏګX= D5PlL\Rɲ2l[[[1 sM{l}1p>542o;(3f֋F`3{lؤw ' < bW/,+Gcmq"@:&|`_t3]M3{ g^]`A.^KX]e1_w!FA++{.ܫO !VuP%I9{ʷEXZ+P:^ëgv; jԡ;5KKoM_)s[s)v=#ъĢa- & ."t_G_v ?ߎ,-v󀂒"xRAN,1 w {h~Ćw.AлK 0Ǡ޿u9 8S^T1QEZ_6cq=-4 ,?jɂ( 7|?V%SjDѲ$5Zx„rAV~XJ'_I2&Ӷk #v1LW#ت#̋GºQ@/W4̠^wc??շ?[h_oOA%M-ngߞ="w _OrA!!f) Zk\)+}USo [PjӟfBHxIXSUW3*XT%C1ׄgUߓ4kEI%[B R7=!T#2\;ِab-y9?Q+lcs\[UPDO4JŠyM͓ +rjԡ۞\+?]\R1?>Wt Y/4\_<|{h./4a^Lm, [ i`#bS!ߘڕ?69ZaL_\"@7S[q*Ÿ,ZUE d&, S;X>ac(fVWrC1C(/϶x> Q:E̫?V[-z*UjlAx [W^HZr]Kx8Y\iU0u]WJei+[|Lpp'~:G g:'`ҋK Ev$uh`4O&~ʻ %奰䝗jff^< ^p]foN "@ZOhu<W77.㷀N t(ZJ?7Aq' QkZҏqٙ{NraID\˱)"WMN5FƂ.΃g''~0dH gNk1$&cki &ğmb/}H:GoT5o4@Jv#;O@oD }h& \Yj}ecVYd[A4sAUbf}L+ J[$ԐnKkX*QQbno(`J b94µP EAy …4%;`e L: mL> d5sKs!ǐ8h% ]cXCF}= ?%l?l Vb_|CFIm"篥Alj.ȄJ01p Û_g> 7~CJ YXk'5%J\NbVlMX|NLX_Q8-$r/÷ kj0_o!kud_;ohXf?!vhϺZ(+dTKX(V`qGOWǡVROàE!w :!ckS uُ\PTM y1 yHL1AnB(ӥ;|baPDC0jb瞇?~ӤpV>:BGջvl?ZlZڏaxS4& Z2R"-M-0AFri) #fڨC_nXR|{Iy/ t!^,\ q^} ݊Vj{H?I% lvyn fmgֶ2@'#a&lfZ0T:M%X~ 6lZClcs@ON2HjB=;Ɖ"Ƙ>F{AKBi',]ɶ0rqK6nI<-m iPJ(Sӭ8ɑk<5ުQk^!ja|aPU*;90|89T< À( BuQ_ `]YY}g)oRι džG/ ܙӁF<\ D# G$b1g>͈y(A+]\qq/e1 10T4li&Ӷ{ ,z?RO3f(n]LT۳O?ڍm-|,8b+Iby{0c>xpƥZBUAW;gCuKCZBFOQ*tTά[PaQEzNg+LfAJ;ȩNz _tߗ((zb1,lF,)-S /Cd(ֆjFT=Q !)gpsԠPinQXq={MvVXDd8rDռ;^ NMZCdY779v!ɑԇ{ Ϝ> j!ێ4 w_3?_}Q V-QDDCo|um}Aw`;zM>,lpwP 5?T UC@;# ~S_ޒn%Yp!h.^21}WDG:D˗Z ^ZXP "5`28{hJ s߅CfUiÎKFK'M~O'-FH<,UnpÿoiϵsqGNg-u1/%_ڑ. (_-KW69u|ɻڌ&MSo7E4 ^Z8tf2ٳGY ײMQO^%8S%D@] ~iFo?}ֳ!_gn˽mX~`1Capv`0ELi,iF`j.9q\?6~"?vx+)62TLQ}Ͻ,,%fzO7 B#=&+%$Q:ޞCso[OKƢgCw!Yv+ZcN ad X6P7dvo 2Ʉ!K y;rp*hIPt" wrxu\b]uc%CeHj+|Ǝ{.[Bq*{|aݻ8~|ekzA~ЎHKM;ْ}H=543’,/;pA֩vRPp< DؼaUa`>p>Nl =Ԕl!!4Fl&Y]rI?'x_/CV37wtG:e# "8e&/?`9> z?'e%?O1 5?L-v@<Ğv6 rR{O!KdD1Aӟ+P^6bG#,+lq_1s0 YZV&zNRR'8v~{Wa9/_b~s!8d,5HEglc`X;ݩxf9F>6qx !~[]V!$KBRSqpoZ(I|fm::FVXD<}FT7å=z_!0ߨj,VP#YvDwa~пflRza'D Xa0ivmgP@tb7)yI|DRIZ2gNlV4z3`?PG/̓#v`j.`+sgT3Ԙ)Zs,7.|F4Կ/yq\ .Q0jDкs65>YI Aw+^'| Ȗ6#8L(]ü8vZTWnh]Q5B t/0τr5 #!Vt,E^ \O.D:y8o]1?Tpo0Cq!&pME# t(i/'%C5cV,R,WgYUqJ=G>̈́μ#Nka=}ll&@߯ك 硐PC1g~xeNÍh!Lmm,$>Epv )W:8zb#TIVPHi/=-Cy~RJWBUl\Pny0k"IhXX}٠0+ ;j M5ZXP^^&G\?0B"ždzb'8wm!1|&;9BH+ U*JhW*ֶ1Cg@PpO(.硛Oxh'~'? aOC:pXEĊ"ߙE`,6=R8&luIo WBEI>X RLZܨjn伿`f?++{zo25L D_]e7l,cjԁuudқ}pHvPG?LS7x KkZV%{Vfai(*6@;W8f­J=5'xAV>X0C3IB8F®X^I݊2.wL"kYx'&s&i T1oW~A~eE&DTƲ/hXp'@X^òa@=smVS mL@{'bG=47 UmZ/+Rb:'ğ{a82 /TUU؃/`hǏmVcL?>_?SQm!+#k[5۠Sj3E֙s!l[n PL9D0oZ|->2¾9o죇QSg> :=ro` RE^F!Y{#Øa L+dmww bGzFSp $zU2H!,DI?ݧ ~9 s_L9)M͇az k=]4qr6 Böw{=4X8[~_Sg{aLj(%3ϼ(?p}C?<\0w?b|ERq'W>&tˠWw?͆ R 0sBn=0m&hfDm+=ըÎx3(q=J57O4M@Oï?H@~ ,v0c[Q]iLdf|Y-WsG5Du">wއ]0 `R4䖯aY}! RVlr]OrQD?W<&lxrWs(X]cމ)(2*MlWΑM3`7`-Y.^믧Qs.:jcgUl+.y>(*GO7Ӟ5=f;=Ѥ`_SPn̦/ev~ `tm J`d٩'ïϾ)K>EAh:7JsJ_O@-qa(HsI6w'F͇Z,ɑM 5WR]OZBeu*[]ŋj6k|` _֒Eq/߸R3$Dm~jN ƵuA_m츨P~\Ù7O gi*;};sZT o~T]P^s`̠2̌9@h̓AJv`NHK&DqL|BػzԶ͠Ҋi; ba"EޤKFgh~hX~ʊaKTW7e]A@}p`)(-(9(4̃+Ooole+<" ߫?M^*x 'c?gCˀX +_ iBմmG#/A7(>M=Aa{ 6pX%(4~*WXQ|spg->e҃xP_{9nkbYxхܓOw= 摆/^/FzQFGˮef OJʵ+ѻ5*t%× prk/?ě&&$5@YH<10?S\;9/# v|7E-1ʩz`R fb,6KVSYwfOPtuw~MʵuL_ô⸥Vx#v~ȾvNVt] 8&l}PW϶ws.wW>xߒ-hUNŜ3)/|km3В(&zwaLD񰪴O`Э~{b3y#4=\5k;zwN@uH jn,[=O<ulR/( k@WE@t 9fჷJϦo;bS5gzͅIʕԞa2Ѫ.| ֎qg {ca!i[۴eޙٚ#` ~ 1\jZyo JKaͨDO;݁ TXW#1fI[U\T,g"z36,,;u2dCF}/'?(SmDMiemLHϱpw1a U$-ŃP$?~Gwθzjg犊 72z36,XԄ0F˘B5.k˛S1}oj?ЛaPּ6rb uTfe,n2F@x2p ^6oa;0!E5I[hS{DO{ml=1'bƵۧJ ٽ$<^RgOٻ8d]rNVΖs9c#Gx7 N2(G5*Hi_WJ-Xei/w 6n$'.M-!=lXG:z+&>#]DҧPk=V0b,FM1xFִ84D Il"ٔtтJcsT QW4|}U]Vz߮Q9)~,"Qt>VdXqm:=}B7^\X?XMM~Yޫ܌d/gX1A+!}j*DCU-LaJعmX$0(X0K6Ý4CiQ]emu?RoO]G]f!P{mHJ-PܢR ?[bbZZԄ';k\e H=]O{#YhCn"QbK/#!I|*йSD%_ Lv-Q|ik`ުEb&؉ mGkB_Rħ:.CLN0@ `beF#@hC*PBrDwGKLtr.r^rz 7-39aY&*q _""woߌ ':Oz&mHm!F{ IK|llV^E?duyF=KWO_hۯ**'޽Yոŭ7{~rfd_~5F1d֐a]+~+CҲ>KCo> '@Oź2=у{ᅧRzjU2/ܖ ĦՕV%E%mѹ"Hm17܅ʥ U&w,Gʥ8@lӮ|*| Zn/%&%mȍq!Fhvͦ\VKCR?%L 4 Y:{C`t[ "r9-%hCj 7<zg1Wnabjv\4lw#}0yl!KUaKLܻjC00 g2#`[b?TufݣI4B[i H^2G\k{ :vn _?ClHu᷆٠EFQFCKA2#{? pO uOprǹz5v.e&á_+DZ6Ìp_ '-' ^43]gc zԕ6@dd++54鷡VZ2Ї`@na=2#!0xvO.Fqxnض`?d%3akg”Ry"K~ibZ. i,u#0#! v<Eia-D^;r1&Z.1#0#`-@#0M!@#CwDe]%6#+hn[ҒbɆ‚֢ : i,70#0RvPVZ yPRo>ZaQ\`F` `Q F`F`F`F`F`F`F и 0#0#0#0#0#0#05G Ěc-0#0#0#0#0#0#0 &T@F`F`F`F`F`F`#b1F`F`F`F`F`F`} `@dT ;Pu Ϩq&#P="ƪvnRR-LF`F`l׶`=lՙaF`E ŗ[gF@>sj[y9fQ~6Dn0Nm [3=w. N``e%et% >|5jPmauB GDCVme`m5~~0 ЮC WWwvcTBlQ =ͬZ `F0-kU@L!nJ#` j[篽[-Kς{[3xx.TǛۏP-LF5^6'gghܸ1CVV&dfd w¾QFmAA~>rs&zvvUCT/j޽cJSVVV w׿j2E# k~MD֥ qV-0/ooppp HO×̵"S©{xZh"S5t59oԨ14k4$ 7#WRRkW-ʥ8}S;1]a܋Cⴄ:!/hl]+$Uaبnlv s~Ḯ:!yy!"%M߇Ci}JnjeL hp`ɽ$Ef D &lߺ4ޚ =gqL 6iG$~UĝfQdtT͎Y3`06o!hW6wS'*[ :u7wg驩p`.8q찲izG?N]ssc'Nj/p)5y5TјfŰF0~D^;gXkiVm`Б뫪#==un\Euj٧ 荟$f"̭ܜ -}EIТ(0).*|YFaaД@$3W7݊U)ZQ!K'AQQ!;]Hh(M|aah|4Qc&@^^.FzRB+=\ъҖ,z} FuVPWSLz5a-Z!a=u54$4\E[ZJ dC Z9犓t 2 nZέWK'M/ U8m!M;G\O nCqA F! ж]'yh'Õ@7~rܙ5F`F^ pv pqӵz0#XDkz==q&<3q^01{[7{\Eĩ3U#T DGG'٧4!:[Re#`ĩ U%Ѓ:P":7Y)be ZܹsW'-r,;,"Rt,J>,)X$/hi.݄ ]|[VmkWh-;Qr;b`F`F`F`Д@`DXXib J.B (QL C鸫H% ~$3#N)ZABRV_a3YJUKK0#aOUuaևf}H:h름oYzxz&S1~CUs+W0=*\%nMTژEs٩kwhױ8VHy=MMħΟ}++;9-;I0~ТW4yRdbޙ`C@q Һo $d:B,wN︨G;ix+h?8Ni^F>ܺ6s{. 8&y3!V.d9]g}V._2H} tRR-=Aמ}6Q)#+Caj"&=x#.…%YpE'O2cъ}MB L&OB€3tyW0t7e=D߸~o/ ]xCZj2:~~UR\ s8 %T[;y3XoYG4tjA44tpH8<_})(&Z6cYJBVfI:pHhݮNcv:"On,09s[P'+:9RTR iYp Xz8}Bݴi 횶&^>416<{~ܴ*ޑK=4r4C++wmwWIJ. N^>S7ܰZg|^9|ミ#+7/4k)I "0\W}H>6o%K o_YO)[6fD A^.b >m@Du=HWtKH;8x ~_>lZʨg0#DL~{|o"<|!Ul~xG9gw UB-v >AQ Ƕo_ wDop 3^N/pprVQ~t>[C}hAR'zV,Qԓۦر}AJ嶶 X<|`s_ rMʓ~!Hv]K-Oe{j85oCy!r[8N yS DzYygKBJĢ AQހ[n:~ğFq\Z6},~]#"2GOVR 5G'Kh0`[_`ۏzÔ G9OJuh "&=|Tl)Ʋ!?#[Yj=;¸vpId,D"~Wc4p/TE볟IG5E"z(r|a0usↆ/Jqt\@69L:sՈ۾+ObmøHmqxG%Wޅ/HXD~⁤hoQNc拐V sֵ/x{[H^$Dt.H qKLGמ4Z*խM4^+7o$ [C` U祦8a$FF^\檺$YdHՑ6%DaD/JKK R?Q1tczb/:ʭiUh1e}n,Z*/%7-$=<=ŋk|6C5l_}sC ~[dˢC`>$O'A/rPX DZ/ 6"WYJ*ݠp}Jg==fBnzqTMhFcO`D,yC<#H͐SʔB\:m#PYG-MnP"Oo0BR&͆ ߈< :C$$<8@iUwPk5t֎֓ztF|zUN|<$+<[77YEbs>q n&R80#X^6"K[VV"<"%!+FBuR1y|A@*+tVdĶ"䡃5縧"Ψ~#`ߪ/ڢF!] 0 t. Nר_ ,lQtWUC"oĠOW:|-ڨy)VsJc#DJ'Y ;;Gp־ zf]nj>2Ҝ< K `~Ht@IDATg. + Ģ])# F OƺM ^v c%".x9R~HVzCPhH*7A%OaYI qs yulֿt0\gO-͗V"c+ Wգ&} K S"}\ܤS#I_kJ1猞 Q-K X@ FYp]pN6|B#WRSty.n)=Ngn-s$kf,j!"VKTU@ZE'-$$7֌}(DBZ~'3 +4l *^\FF7EKl{-VꧦcQn9%nmqe]عm37B< sZP@./_3ؼ[ُSod7-S]\Ud/tUbQL|eء I;\z(4}\u_@ܖ)q.l7\'ݕ.fp>,yb¨N!̉(}/zMz9zUkD۽-AtIWdҫwAz_o\)=TEb&r)NOrE.ݯ/>y9} Y?>3r'lOJ7mzSq%+$*YB7_~V; tMyyjoaԙ0z2) `gy|O<اqb_z$0 YF=$_35}}3~Y?=p(X'~0܇6rT,~n+V]>QWIMe*;F`jYJBE/:'>n,^z"YƠPI] ߁s%Y05zO#w̵Ewj"dw6P u!Z88!6 ݋TܟJV^@+ɺ,Ta,RեfdRq3tL {_&_B#C6 U.(73#V' sHQUiIa-r B Pw.=&5/o[ =%3r/KG^zM9 QoH Fu!<ې>r91\|7ke9Gv/[ >s'*dGD=7e61p[űtx~tO _X(\(3rK2ةyt"kӦUdAq \s@!5&$Bx>٣c\vD:4>"=I'4}[YjYh$G F\ K ʼr0mYnߢ@\jQ'L/ԚRX"ae$ VmO}eGKtժnmc[>oC%hVmEjru[[9Y" "ZMW"97jUG\4nٸ֪5|ys?yK?E<#öE|'Iu>܇^:H--I ?Z#@ G>!^zsnO "|Jk4:H.7gw]kÞ%G! WX@R/ơq8/r=N4\:z;⅜^;ׯ]^7v# =)+>Gޥ넒@<~ % *Y|k⪵DEzo є$u+/#\',YI"Yz\H^bxHY*A]6]c'N=řTΫ)L:Fm[Aɝ*ɐᣍDtUh*\YKYW-yعm#6GLt_U%Y, >c)#0Y9xȍ߾v<\|LwnAM͊WԴ (MBWOVXÐ "I-_QdQ;n%)na^f2޵T&)G; S-%[Yđ67v.P -0 ([\P(rb< l#Ha@ТښrJrfrعc!4iD Ya( vD_}LIHXQD1 )֡myt>AHf":w"syJO.Ru nӃ,NٮW&D [UϨ3kkK%|},y}Vg-B.Z<M{uV4dIrn^}p^_iT ' ű3-w*?}LI{\IB1 䡲ij_ؖq!蓬 {P~<$7J؋$x/d@$ 0 Sk!/&\øQ"ơx5{X:FXGd@ D׬,2/f{ Ͳ͜r< ?@2 Dָc;K)5ZRízq."yp~!6CVS-W  }+ndS|T<~DեkUUU%u=i!h 4F*c˂1>D0%3($c+rza|!'V9a<(ZVe+vMD*'b2\|-ZY"H@:Т=5Yg9ipm6I~ ;>#}W8$jRnXB&)n#-X;wZV r};YwޡR.ƩRZ-6(-iz0#-Dd&[1OBxp=ѪN_P˿ba _>UY,)_"); ܈@$^xe{I=grKrjG2j|DV. RL=di/&+n1I jцXq-/TҩWnr[m?+Rs܉8lKeyYqEwHeIav^^Q,/7J*o7IASgsM a\0h;4~L(7_QZٸKЍ,Y*qND7~?`Ff\q!+~}rsÁ@H"H?e1&,@ӰrSmebZ0P\$D P52/$6DwMʌ0 D>ֵS3݂;7Jz["P( E3;CK{#Rp"ƾli^l VdžпՂnAEHJ)G/Ԅ7@4"$. W䐫bE@g"HdSɗ%zP3^ڕEGeYAXB+[(D"E+MD6:T\BHi-ZbZc.H"f7ʢC+. 7h(Ї&Ps˂|]'סsW5f7کk֒[u۷w $wTq'Q/<2!sJKt/++InPMSsQVSd*C dA:)‡T;we3J҈iDUbOxSU5r@в#IT\|*\NX̯oW̩cЦ]'CGmĥ;* J x_llSFGG' PVieN3#G6|Cx,IQAܼxTtdؗ+'\*ߥŅ=aֿ/qTX>JQ2 啕Vԡ|gHBRwʟ0L5ɵE;8_euSGżwj3eMڨy9iU:#! /\ =g?q#^ ''ʁ HJtr=gdURKG"X^߰r;OHb -Hb*u8\6ږ7dX Ӑuv8,*JCI*D UhU7VX.AK]b\"f83mV9>:kUS>Ò)!玝c X_Ŗ楾bz[Z%}|#_dTIGd޹ pCr"QUT~b!)dp 3*17hFZaj cAL3E+kk)Ys<շ?ZE'?#⼑]#|͆sgNBgN0S3ed'$z,a}V9𦄄ŴN%B5"W WTq*1CA@㬥+0_QlQ9 JIDBi`LUZR90LF/ҽW?;Dq^r@;`(|ǂm=#Ċн+fr1GAUMn} #08 mm\ n17n?@|z{'\*+9V\E :e"]wP'U#HY ")L\-dFvh{o]i=kj}yIқDSRYm$]5mejs/pi1c ul% P |z\ۿIj:KnPJ*HU]}2[c|{"CJmʽv+SiTnDVU-fc:5f$j,dqxqAh3?Ǝ#vhIV$g7Z=}  tmN#סyǁ}܋V&Dܑ1&ew+jSyrC9 Sc70Ou'YejCdap^0`qe CI1L0B \L։tx R_W[fC=li^ u}FvզPXsle G8ѠXۚ:s_;Zi5Z(gUɆ,ᔎ˫!)jP'9ONkvoz(ã?/j,{|{ZdQ8t,+*ܠ*zҳ孴ds^dlȍ)#|!b3h&j\&m >Z<ro06UR^[koj@l֢4Mf?ds,%D/ʭzu9DIʶӧ>x=2Fo0> zXҽ7t6_CYݰ[CxPC@sk޸>3+3\5+i!84˃g4@"IE(5jmyy,S3WZaZPO$3}LIXD(2oq&>J5%= {)L:lZ -GC.K%%ф3 D:J+k<\[JqQ=1sE&%@y0@-"PT Mš=3(LLz>SKTZ%ǟSVc ?> *+˂*!yVvyP3E֩m@Ӕc}&~R*\?"Z1jFC#aP?w'L2TiA)r ;?U"Y.nrb+W۲.n!7uh 'wW)#hW_|OuTs[g`ս=|e._6Xl%pĥaYX@)w׭4Ulv-za*BJb-Ic_JppI_<ݜu1oXxC&Z>6\Nz}37LvH/QgOAff:;tCsHT`Fn(N1 u/3[ {B*OD.O`$6L_L= l-Pę/$bAnH7.E.Xuҗn@.VI7V­+' 'ѽjZAJ!Չ?6 ō+msW,#PL  #*E~ĝ吗 E9O88`EԦ-̋r?3 x79T3!îUzxx8ὗ`?>7[;lqHaN8O+R-=:)bQ/= k/] n }LAKW}UCI WYHv?ڎ/]_/cIxR&@07rY[/pu= _a&Qe5ⓍIo94gϿ} Iﶝᵇr{%,9uT`8?,+rcZbYp`{/Ɖp%?,Z %>SݑVDgS/iH8?l>Jq)wk>UJ%J9w%Cx s(.,Pԧ2yL_.c7PA/ا|V,߸v^l\I hӮO¯}BU`P$o j#CIRR˗"k[-1ցRZcJ&OrJV.R22":5-}HVI=;`|[' ׾ D/,IRn7 Le`gT`zpN?'WGlS,2l-1%"ư?ԱΟ>A7YlNU Z_ii;4,N=~mh#M\|{vmG>n-ҽ!ׯU<KZ>=$(8Tt|$>JGVMۥgVF`Z{ɇ0xe% /O&]!i{z$5T˔dcoI3" 4Thx.5gbWOJptq}TL L_ćL1I𥿛ӊZ!1dA$%œ™͈"[aјh(Ow|3K5ԅ)YQ[Bjl4f3!/- \DAҁ܆^u"ԌI*6rjv3g>nޫV3-@Ñ9OhwcyG_ gNZo()uEAp&R- c.SL@IǛ!iu<$@2Ew1S G.Aذ’kTwM@X$+vn]' +12s! P7175l$~ 5!2u߼%zEo-M;W{)F>! ]֒eZz'ƣ)K);uᷕpE S"U*84Lxex=zh?ƙGǏ1C2a'`ۦ*LVT Sz);vThBgq-d-lEby]Twmf-괟< S͐pY "y/ƝA+t0 srcc?mq^!ܖ]v >+V6 x(dAss`έPՄ\y{˗8jn2rя_o+~UuGO H ViS2\\5%w*,7ްF_tw saE@1Մ,Mh_os#0 !pnj؁`˦kFZEk# a=ZUY22ҍC"m Mׯ>/W.,GBiQAa2yxVw<[F"LrJf$"D?ųb_k.[EraG[HУMLH :񏟼/g-\Y?攽ٻprJW"/U~mmq^;׹wh_ۦx[PH( u=|I8p(Bk,G\)Nnuji l[ Ou)Z&e[bWu dCn`]N8KN.%uRw֙]ۡ rQW/ Wl#?ysݝMO;}C٣te1mb5W?,<:O 0j-0 {W/_Jdq,Vw KX8(BWL"ţxۇ5>~>7eh*5t>FW6YOrmigh 9I3Wy6`cPU1i:dmGր{ QL\tz–궺VE=IQ>έnRla0b{zsE4/u}O1 CA$)#VdY^o@nZ%jk,еGURy#0@}Fmcb: 'w$^<Y) &˹h0#4\j@tqu_& 7bʄ̌ DY6j<<2(χ\|YnDήjJݻwUIzJJSMhcQ4~MׯȺt!Ϊ^ |Bi@;;;Ӯ.޽TYOYAUgI&Z?5f́$uV6JJJ`%z]\;!гs4ht&e4O9WWIGðQczuus_xU7^bm%*9z)u1( F`kL$ETb1h-h0#4Lf,wPp(0b_+HKMw©GFPЩ+>KOM{wc BU4j"bwzV+0v4A8JΜ2]sLVo,4S@ b9 _sL[jo__Uuz(..RS>AoT%I7a_TUM.fnNhqvF$IӀ拄ì;צ|~&1u.2$A@t-#0#=%Ɇ8ts*Cn`-_\kcѲa-R:;i3 :0޼a 9OYӌ#0#0#05DJy`F`FzhJ vO&wlsyHIVP%)vO> a吞*#c&NO//7h*ԡDѹaJtyW| ]ҔG.Dk[XwXD*5%=)[UZ`ZUd,j|1nLPyvMXk?͇׮[Vmk _;;QryL*m=G$ #0#08uQ_L A#0#X M $Hn&܀}w "F:rWGIa>)qıkV.Řz܈6C`P0ސԞΒ2';]X5ɣQ$+.sy,=Ԭ̨Tӱvۼ%ۭg}A!Bor?kHR۷l"YAD#_cARUIv1Ү6qݾuV/TUVg,Z@ҙ1SQrlMWŒ=0#0#0#0#0##)H1L @]QqW@#?rHg0FRЃ#6Df| `-hٹG -05h1>*/XtSw1"M Y2UMΧ}QMc2tW`" zT&K%? 1V>SЮcgqF<|hrh*TLɳ`4~p\KHAڭz0kR#"B| RrZp(chP/ |?D;䫋^}fp3) Pس9xBQq)ܼwr}) O=p۫i͢=,VzPnN0it'ӵ)D L47ܸO'r'd}W/RGХ{oD?T@9OC. JJ!99  +.ͪ !0#0@A7)7 ہO 88BPVZ Eِy;gv/BqaFAtN J0KAN:&^ 7uZ@IDATS;ob y鷮@܁0w!i;(*V a-B/_0f$vIG(b;6_h3pvQyY)pI8c1߼w-TSXh.[ w@$8:yLcD2]a|}JhцB~4[V 8iyC8a؎wo{Io 0#06Ma( A4 u;a_13OXeh5(@,ȯ;Ci`duq**H%R=[ݼEgTX YޱUYTi-0O6n EP}^ygX+7~3."SuTdK R.'$)z!u[qx7׻B9юµ4;MF`f2> xz[.%BXHzH4b߁jQ߀`IvH[+?zS+\cS[Dѭ׶*|UYV -P=-޶-Ҳh Yl=ed+UY1ʡG'5Y'AqHM OǶ,~W}WfxCB䶼W} 7?DĵUB-.caF`F@@K+ɭ Q%%aAi➤0?^swbHCvN”"a)Gz_X|zxxk Ka4-`yHyH^7 ׶sܖgب1{<~eD+wlk$ܨ6)1H:J9sx>yə%yX3#I}۲*xy;ka?jмp3&6}Q;^|fM'ǥ5<싌oҠwhp-dF`OZWQZjղؒ[>Lhg'-C\ĜszͰAHa}nP]߮7FNRziAGZey1l3F<"+堛t،g5CI޶ASwzpu,>  dF`_-qi76.z1;}&~I@RPrҺ~~bߞ?Ad Z{2񣇀yl6Ӏ(Hb{Oc˿œ{ПM:t=trg\Ďm$?r1$-ۥcD0#0#Fe"o:c#VۡU5NғPÛ&߆^?~P^kc-=ZBL\t>}S϶TO[m5P><.ghN]Ej u,B 6ֲ<[j ~J-56Fvri\rK==;~-V펻52ZG<|pd٦jΨ=A"B1'Gة9xr'(RSՒ]0gǏ$IekB$_^|'1%&'OeCF?Hms &68bGI5Vm:.wusQGSف}`ӆUJԨX;Kaa݄(y0w;0#8 @3䴧>21Td N[{Bb+ĴNqroC rWUj2,5qHB4;a VԆdMH!?{V~v-7ɝS#GI|{GGLRDbꞈ3+^DoTrA +T=t8PV#Zp`F`~@  (/+_·2 艼"F5G M4QvP[ g<%PQQ}ʁD^L v :&Vm6RsMr=!]`=1m!ǡ7XJjOuC_ymo4Q/LxQ8{wCiiX I)0.y$zo3\g {=>FC|b2uNdi8mp>{xaB(z i_??h@ OŮ{- ;3vXg!zg[DXHDIYqixJ5cHPvV}(`Ϯ"4h@i%cY(=y^xG߃sٚ]4~*4PIƼ47մd#L j"[OT wDx3 4N(*'oYĔ.:2#0&(eaxwرٿ. yE\7}kDci{ט"ՅF%ZDRTBc4/3V)+4_NYHbnGZa|o neͤծ) _ i{W[%a>=KW=F`F0GbN1{.DDFB%D显J[ℷ'Tb#2D$zcgϘ$ҲNwHd 'qh/Ѐ9})6X$t<抭^ޱ خ>Br%g$#=Mblj6[m^1d|={-iqTӏĝ—CbnG aD Q+͸'yg;bf64UCY7w@xddپ<>0TUW#UA RemVNz.FJfʇ(%Ғ6xjuҘQve #0 =+>Q:C҇r^:W=g!P3.{H-ˊӾIQJjބjP=3$Pj|PSUecs:Ǥ_BLUץXJuQE˾3y`\fF`8@I`0A|< H:" =VQBLl۽S9U'!qĎtC# DG^iX2;|BrU䛩iԮ^X>HmgܼA{%`d=0~. <=q9D-6&pPx݇OIDՍ< D:AUөKu&HET7NvOWG&vz-<<km몃z`Yol&њwLμ.WY3#0#`q&Ӥ;vE?y'6OЦԯ ,ٶiA~Urj>qԎtcޔ%?%\N8iJ SwKACΦ V0ЗK/PA )L ț2}x~JQ퐍ʉN.dc>@";'wzG} JRRlC(II'YvGQSafz "!w&COܨ CR'.CAa`~D 7ғ!9!\igK>󯒷#vT#fR WRVm3xۢ*~;*/<]BK.4)ˍ>V{ha{*$41veM7XLF`FFQ~@uXF=^dxW5.Pժ0>0C 4ՕF4{O@f3PSr<:50_͡k躨2#0#4/t'{ƎH~Vɂx2xhG+@"A(-iZ:d$yUToWc´16}.& I␵|γޙҽ?Ɖ”yx[^~*/‚|?kIB!_k;RO~m]XdcH!-mEpH߶QG!əsb 3E #%8S߷HYlC{=JV)s~McgɅ&hCUnYv<^NV&|هn#0#8sߕn VwvcD"D$ L0!s.)Iߡ?XWWJz\TZenCV*E&v4<}q'vK !iqee<Z'}opbR' IȢVu+F`F +HMa ֳ>t.πXrKb.19Ãa} ,XaPՁz 1m(7=0m5ܵ} `νFmݸ{SSQ#+)RS݁a1=!?/V=9|XHRo׬[z7V"# cW#c/ ]JA[v :3 BR+ݥ2)XzrB<|]:']AH0ł "pÿ d\S5>.h"PTd$e#` Np$7cz&nǝ2|ʻAv D7gF`AZ%߱ Yg(*gڧ|jOsd3w{4`S݆MSUBqe_`QBdA@]^cfكPǑ$bHQi.z1zoPTXdTQѱ0x(]o۴SwЉcGB$ܯ睵Μ'L1 ""гd:YdMC bW[kFƵ5yp9RdzE"8է۽suyRlvWgBG}a/UHAH ʡ | !žfl& BMs8ke֖&W.P;rtvtO텇_ee%W`dt ~6_ľTi&&.MR41*i#D:q njCKA6&ʺ7oނmPVnL} S)RWsv<cg-< JMZ_}!L&V/OI_9^|q<*l#0#'Ƕ- H]JEr/U$ݲ128DaK)dWR[޻[hh ȓo/סVk򊤼ISvM]×/*´ҸRWҵSȶz踖ą#0#мh!aC}EHS///É vPU"tjMM<ܬicDcL*rx4Mv ( 봊u 1,CQ3RޙC{`ZB.pKu%̡ 9(ܞn̮4uw^ @,Z, !m:T! )E#?+۔V=]x*n\?],,|.Nﮰ 1%rdYJwcLLb20aF`ƾk Ж^pj Ta(+@$-B K[xz @( yۑ7GyISûꚬ^G'^2b ŧMq)?}5֮ZfRNUu \,uQHߺ[Ւ]PYUcCBl>tW7CC󤷾R9v딮8 V0#8Gߵ$[),*s)-4wqS|_{o$戬5[@aX,1QC*Vm; .2#0pkcg#4;hmliBfiPHV. &hPZ*<2W p ^b<F`FhFcks_ I{N۷|g{Osn{|_Dy]ڽ?8Qw4:Q<ׂ`F`\DbFA ?8[켜ztO}{~O\2 ][1~#J _{,BӴ˚\0#0;!ڍ·+w^{ufs1]/o?A櫬ݺ+xy)&޻Z)˂:.w;;\`Fp=L s`())40o[4Fy&x1w0#0#\wzcބ RC{--Ȃ]>PW:,6a7!5#0#ЄHJqњ`Fpz e붞wo+YE#0#0!<<춫(;JƹС[HCaZ-\0#0`UHs?#0D TU@AQ9Wi*F`F`Fp5D z'TAEiTWe:3#0#L "`F`F`F`F`F`F`Z^+q0#0#0#0#0#0#0@`F`F`F`F`F`F`FA kR@F`F`F`F`F`F`#b1d #0#0#0#0#0#0553#00)p۽N?y+g#0HnV.Bc\0#0 r%wE\vDDB϶ <8eKrh/L;~mG0@#{KHZ(.+<}8 D`Fp-D̘5WҒb&5JW @c5ڼ~p%#0#W#( jS{0|y;}CI'7섊ti=PVP 3o~Zha2rD?_ҽ86mU;bkǑk:6zu UUÖt%zf F!/<=<?9 \'XJG 8hA=k/MmqZ *(,,|[lIoZdMʠ'mAv{Ր/\mնmTׁ2GǢ#U$GUOpP //oSD )اG]/hKu j}kӢEKHm^yyp>C|iuf]uu5,~IgN0FٹU|W>']2#0#lwTn~bRjL ZEǹ>|Ca3.9e(I@3V1Bsϝܢٗmaϋ2/?̱<z+5. Rbv@saA!'$BrnϺqzw/a&DGh؝ucy+Lo//h8ѳ!}g $ 2r A Ƞa#mDdЁjr :r4\׳7Z>Ɂ[7}WaGwMlnp'&)D|k8v䐥a&5DN;żpmCdT4yx1No<:uAhxfrlACG@4$W.]yfq}Ͼygb3qtEBaLmZ{@" #%2* D oF`FY!r.D]vv M5v -EG5%9.v y0hC+%BDVƛ'C)m=}2^v; y@ 2 %nx{lk,eKƌ\NyH[6mއ~c1O'K %EP /Lb p:=WSτm U+~=;~AR2#0#0#0@45n}aUNe(CyЦ_~MѽIyC)¼_R %o]h=PT>W,G{ Fג#r 1g 6b>Jܪ+?茶;lm ,%)(b,zdǕla[u% o5 yHIS^G$}bPeOKC^nا?I)piCF$D(+~\"ܩŪ^G q9ǝ,΂@%R!ӆxcƢ!ﹴS' ZMtQ1,|d=cb4w tr\-l\%>x9}k51ښ(aGg_vE!Xɻ__p>y#0#0#0:!@+QyG£e \[d"$ )| ; w¡3'T{ >ޤD"Jٮ3VH4>qE =&/n[=V1ž5kG?C $.m7T9QOeWGNmyDKTyYXj8DaIY\Te"Y( I$1)s+|G$B9}>]$.köZS<رw׶}G՛׻PL17 5'WJ_"/bT PTXӳw?5v!|o^Af^K^Uz_[GƢ׳/Jr.Y2#0#0#0#$gwmhHׄv>&xvgx~aؓN)mwfG$_Q`a1%&1asGBdntc,-1L)I5r6]0֦$uDt@`ivѪ-Q%D-)bkǤMG<`8a#^_B <<<[gU՘gj5zvnA0[DLm駡6zżIy z`$Cĺ5!["4(8Z3rIUk1V_BPP0̘}9L Tqm^>gЭG/-sj gG)ض`\NСU  Z qԾ+ૃ{T;V{C0G> _=01WKeeT:|]`LLc)~;xN&xO?iUaU|Ia#BC;5z7‚|H?s ):f;s7u3ag^Ĝ0TzbнpoXɂ];6X.D#L<} .!IKЂ#Z\dpE <[J'\`F`  1A`X4xOOʲ"(<G͋BLWѡύ >A1Kyq\< 'w6ر/~˻|NXc;Ĵ%bAB0?@PD,g =ۿI.HTm{F7@D|; 1QQpA8a]J3wT=L94*}u-:V} 9sS6-'Tj:.t=.l->{!p9_=W?eLZh;znHn>A+J6y _+Lciox}f\]_/ 8jiږO\c敾A)cu <1mBNEسp9\8x4 OoCG;'Bi#kKzm3|אRi-^7"Y |,tރa@מr*%t&OrMo}ܵ} p b}1Rf)ѳA%J8P*Сm/s|BTX>mr>0By%2ey!ecAXDN = E2b9`nj!Õ?~zTcY}z`Z~{5f,RڴY kԇ^Sye|v."!4c\ gۯ?޶׵/6z=yry97 (Tnk.HrۺYc.=I3}Dz97VۡSdI!Ho?>O`Z]âsp G /mR FQT@\9GMൗԎM۽:w끹g}ZAd_Iq1nz3'2*^|-3ߤ{L 2#0#L R'?Oo˅D Ar[ F=<@LU;ha (L۷V~,V*TǦBA[!mOUx rz[zEZm[T%%NZh Yl=ed+UY1ʡG'5Y'AqHM OǶ,~*e3סu!r[+m뾆~ "*RI w&OkaBtmpw3pƨ]h-SvMZRVaܫ l`1Al$m~&18kBL#~ ުB5lHˢ}G]J aAxI|uK T@j|R436rFJR7I:|m<|zo/= ׾>:a;ԝIޱíooןp*8u8ǀ]x{@XPtm< ӵC7xZya)#FBeYrkDviI :$4qOR/w"!Hr8" A:eS"p0IH YJ^a5 .8ܝ0m@=ƢcPËV/^PrzYOL=<?l,캐gτ ;Ă<#Gtq !|cif^>Uq#vԧ5>yHKA@F֑¤zvzfxYsGOk!o@/I8:?I7TϜd9Wq~. <:}ZN$⁥Yhy<,/-/BO PYZa gxz>N8)M97MQݥDeL\h vNH펯&Պ/g"'!QwPxԋ2dTQy>[WQS] ۷n8B#ʉhׅ03E7>/pC!@$7  sqv[vs( ֚~[;jzL;`ī\$}{m[đg,mV1*;v{'Lzؾ.d'acCcYcĪi'>V6]8Mi<$o7̃vȻ8tu@s-"_{[@ Xa/:ql$EQq)H+{'l=}~e*? em8 WltI tq*de^ C@O$v(HcC2deGHI1"È2_L&ڣ{~㔇!#ȈnaQa!>[Aϛ"qקGk+iX֖g:t*^7] Dq@1 8 ?KyjKSa[#lHĽp,/%oz?6N10!|ЧE+5ZZAޅdRxar؂91 [:u?˼w3 {4D k쟷y2R1 ۳yG0!% 5 DL۷l#KK>fIz8 \#z7N݋ ʄw3n4x;* 0#0BE~-F-]/p"%kބ@$T *•op6 F '?!& ]1\H!yĝٷFhx'ߓG,އ^7̆#f;?8 ;y2"˟!mY+[#g>tvz+l^oil4 4 TDeGt T S,衃9D&vnC%!i).DPxM#pf~ҼB:@IDATa"b:7*7(1:@dʀӛwI}Z +-xZ Mas6!THq?4X,2.&JI [.j$  ?<ΦmN7D DHJ%sD6-@N$?'\Tam2)HFPv3k7@٧ܒS*ˮu-;v漳si.[v蛅u{ӕJsm)G1$ާ4x?'Ս-[Vh1˹&8n,|f`5>T*M|̥mwC19ZС'!ϓۊOo jPd.#){.\gMMPÛ&&&h Y X\~y5y>w!~zhʝ~큑 D=t^!~|x=[)o ?KĀ@>nee"A4&'ߤ5 D=Rjbkm̷A7ן}DC&`@['o1@m/~)GcUhtllG6X,a:!Ny+_wD|C[(끽 i՟$$<|~"ߊEmK^t'$e yj=r{xa[(6 UHHS>VTڽز 'cgIHj吕pU'A[Xv&qR8TGj"ZB@HG R{,@\|Q0ǡPCI X<Ϝ@VX{pdB}{{ 2}o۲eZ&-P F`Fy!@, s.([Ďup{WՁDäH@$eQ"-HRZh:]gRV+?2ĪYHjIVQ:7R&8ipd2i*BV D=tE}lqezXʜ^zE]$#/_[tg5>}= ͏%Tayvr7B-w"U 0|wɗ1>{ϫ{)A͆<}qI$D5G-*?a4D}8Ӣ~s&)?݇O,vg| B yiQ\۞`€"|f#F!z ,z5R^ՄXvzPCEF^B;ImDNW[;@ w`NHDRH9"D4@LEII(\ 2ЁDȗdS{ 5^HrV~=D8F RcЙӧB iSE8hHQLG.]8/] ; 2b4ЇtYH^$̐d ]8Ϧu-_׫7xdog~V%-Nt {}w5qs~abr[H١c\*ʢꋞW|g[Cm |YR+PKKKLqQACb M 7NWb8]utEV-\Qt ;Dne=5$ERJETJBF`FhY1ge q>$p^Ub_iXW3TUmh[(ۓZwںc556ԖPT {Xe`u&,聩TpZC*5.6pufGJ|$w8CrFjmMKs K-,-+>tv#140.dMJ|1E 8hPȿ_ɩX$ ़D jCFJ#znm:6&𦭻?+ByxED:AU̔R'g"hXSX "+#;b[fiԳ.$ DkR1=t8\IaamR?ۄ#C3x`PpԿ5uA& IBy~t?|;߹ [|n8&=[ @WȟL6VtUsy3(Lzc7sJ[s'c$aYs7?I3T([3'xd냀waUBsCN=i!ķ/#<;l# =-3DtYuvŊ|y'6O+ǘԯ ߶i~u0ZO>7%!dКyߔDxSWa~^/fD[|Ryy3# 1:[{PeA@z^__c5 Tm9)~eoF`Fy"pX޷:)] u7 5QO ;)5xmSښ++ʵ9SC]}\L~GirLEVμ.!rJpw`{_Ce߁s๷:mxWUN 2rVg-BjӫN.~Qkаpó}TnCe? 9N Of4MmױT綻i>WG y,T5u |w{)'Cԥg΂<$g_| (]N0qt&m<! 5x`dk;TS> zb 2M hfmb:SD9"yI;=v ۽оcgaU-B1{tȜMbjynbOukȑG-m^c%E0Ǡ)|*Tl((_ OuA#E- cT+Uv k`F`\I9cC{1’aޠט@M V„dM74Iue@TWL=DG?G'^cs1.踆Ɣ#SDys>\tG[Ѽ^Jאo1 ~^Լ^ CXbg{V//o˖J"_FXXo*CRk';5Ҙ`]VQaoޔx"{2\h Rkг@[WJن^UW5X1SaU=;yx禵CRl.jNuF3;{C9)öǚ\ߡ8ty&NgV^g!4+CrARN=-lg;ֺlY.# CO4,"Nr9גCV#$&<ҪCFC}J qD#l5zgԅ8cb*-W%&seK=Q:W~T$:tt?z`.ٵ} =Mp`aSn\^Ƚ ۝)RS݁a1=!?/V=9|XHRo׬[k:1̑dtĎ82=ХdMhv%i(ԩ5ήDFK ='E;4zԹIݑ@VUb1}@||qKDyn y>O"y;{k EBRo^}NDe{#m}IK"H.sg1g'ϟݱqzeKCE:ا{y+(=L۰g`ky&Ϝ8o 8ƛEjU(>5H $\c , 3ҡ"caQiHm]MMey'1RސuS'L1 ""г_,s2ΦAXUeA4ðc !1<=,zaK˹~u QJCe;wت*?4NܞR;&Yw(e.8ջn^o?<+/]]{s3îMzp) Y NO=mMh}̺9&W.ޤEz9|t:MïcW`dt ~eQ;H_L\&bT-+NG[=1u6QzcJnɘp*,Y58+9\DDoZ-:l}Y<:x(1?fD8dX@@A҄<C$;3d#¦?A\%GBRDZL^oh/ü)nsQ: ͫyFݷ }#oiH/?@_t<+ -ҋ&yq˰hW`s]q`F`r6/| FDGS`,QɏR^ӦE;(BljSW''wZwfDi%x&Ck#Q vUo0r GVH)ӊ:?zpb6&=XHf{ pCwBya1 "(eEPUV1;BJ_gY7뽗O-&7ݤv3_Oŧ4@`-$~ ;9y7t0⺴{â!!Y >A@%=zu.!9sMw #O" yAThiEaM\vUv] +=e':"]'y/ `HϤqN؃,GH)`tyZK )'?r_n!u%ۈ@/5}:a 9}JpZH򄊍Oٻk;P8Kg;&aL%yHDy9,Sg^wT20ISA3?wlZΝMWn9ϓr iZYԴݔן} ϟAk3.!ZY "yO8W7hJћa>&+/4y{#GPng#qUC=J͈PpDw#oO,M@vF5{5KHb88ETJLBұVaµ /5 (Ĉ$AH<+prcCU8oh!-[wkl'A/@|!MiҾ'/bDWvã=^(#N2-q׭`8bWc1h IR+h) WXQX?ig5bX4m O_ΤPjc.6&k CGǟzJ1[[Q׷׺\$ ;'6 "ׇ*P$"r]4kȨxbh;i(#S0'ME7ۅ9\u R[k?h(x/&pF#0#ƾk Ж^pj Nܗ@q wV8^>{*j0-lCv ih%y)@䪝^G'^2wc羬 ZÝKSS(V"O,-+(܌k7.4С91mϼrTa1뇤dW$30Ue"D+y#d@8 ~}_\4%^.U404?^Hg.WL G#dh7l&Q(4EUڥsP,M-&OP\/fg* lJP\!l6Wz,bZTGOS y3Xhhj]0nwnXL0+=aI9> Z_[=-TrِO] s}5_xc!G916# iZzhC}X|YfJ PLSTVN4L-Z/C/AvQW5+ZeCA@yv{:VWūJGw[ ĨFi+Pǫe~)^Kۿiq̨@ePCؽg#\FqB l=FF`0Uc׬yB$+-;P#v{m?DqA$xoڷӕvJbrݤņNAHg?XDOC"}|!m߅/(jR.{}O/SO}A@A@(] @&zbw%ߪJK*e^{$mF*-ÄΟNǷN8\.k tVyqXaΦ{I 'ŴrO9Z(X⚧k:uZ~aA@A@b71Buˠ^9ZC$d[A@r@zUeϟK#Dݘ:DM:PA޶6yH̨è^IA@AvA@ABPB9ٔJYYA@A@; };WMYsK2‚  &(U      #A@A@A@A@A@A@A@p!PjA@A@A@A@A@A@* B VS)A@A@A@A@A@A@G@D1A@A@A@A@A@A@ @   kϾ=ܼq=ݽ0OFk>T~Cf+I  k{ʙ=<y3#z ʕ*QMIzv&;}|wHhݠ)ՋҁG+ B VS)q'v1#=\ BjRۈ־ggӂ)z6@_n;JR:73v~ \|eO ѠGR6("6U*U)77>H?p!v( @Ez.=oA[5aqKF!PQPd{>v.ZSrI͗-5_nOo2̻;6mE TݟB ^U}W*%|ںQ[zգ_ӏ^ogvzx Tb"BxϏ*WL9<@Lg1yɩTYAWDž (9Jrg=WҥKUɔrz.\ȥk LM逮EW К*i=z>4(:WN>~5RlR"2jUd.]RE0siӝ{ߨJ*SF r<2*[i9994ot&c;NG%,DOѣ8}^P>pnlXU2ժЕCkNz۟R@@`V ]A@Ae-wv7ՈuXѢo!:%QQc;+OIB ]2aC(f87ɦd5]X o\$:@U|C @@3!­XؐQmÓ!a&63iꕴMZ7ڴH >K:sZ옅6CGg4kڏgWS'h2ןi׎mJsUP7\s= '%6iւ0wJv-d|>oX%'/>.5ߜsk 3}20|XVzי6Ӕ/?M E~iN-2Ds hV+B~UP^׺[2$2;R|RXA@ wKB;R(ZڌO{WlBM ٟy ,?Dzv.~_>"+~mgxdm4Cwb;tÓ^DOBg \FUDէčwob.OlXE'$<]$\3?|_Q*v*0d)+7?,Cvml41aHca/񄂑MxvW} X\w_2>7bEr`,ņ^3γk>Lhk/YYY*Y IL6ß(=I`սW٧?UlrV?w1ug˹64kCY>4v">5ӧ)=-U=P=/i߉0Yi%3+6ܺY]=F&)ENlX/"ۥw(qU]u[(omܤ8D2"нtCO+ ];uXYıN쥬Lkl  XعfU aGAmeGL6ῷj4oC!$''7, ^Sp5rx.GKUi C5,^`MvT%WҬEBѵ"e=p$>xe5uMs_Ue넗ʗW-;/Zx " z.ZQ >e%!zO ofCh?˪;nk*ع{/+ybB{ZHuݍʪAw[u#ܙxј:4luL`H ԈP>b^R cmhI \\~Gש:s:AY5-J6\w/F`r`:yQmNj ^F-XXzӫFMSh)7 c]^l1a1;m=m5ҩ4} `u+K|ϛ3oݬe˺Yem`=:\y许s B Qf]Ei۬_hⓑʞ  -K @%! n:FitNm߱*;ϙ< VO\TB%+KT4L^0m eR>f1cԵE;ةg&Mbr1Zz ycGU:kАY>We=LKWGD2Xݾ 2CTZ5/6QZj*P`5kHa3\ml?ۤBn&4eh]YÃ_}j^Xf`ZX6nJᑑ*yҳ>4'![xZj:vZS\7ߦS'OZ8}1: KgcFٿ$kX ju5nX=Ei/Gy:xn%㇈   ) }`I߷Z6KDڭ{Ɗ?3!1ɬj!64l?h.?C,cӗY?KL'VpӸR0.*ޗǒSUCIeH ǴKݘDmyS-ȡl@Ԥ*Ij&鳬ۇ@ÉX$qz1CԧY՗^¶8p5׎SErs8b\aΈ:NQe}և{K:i@`uG[ְ4 vT̚}X/e#76h܄[aXO;vAq*s[u:vVm۫{m.-8p.nz v1Ej>#I`ޞ4}tg#i.:nlQe_je{Pp]K}Pk*N= k Mik;lMr]Z_Vm:R:)g9Yyg\>sF+КCGFMysslRks,#fMJ',n{@X+ƿ,Pm14k3~0B]{QǮ9,2Q\OMkۤɎ  B"S^)^+ N~TrMϥRtdڱz&fgv̋U4q!,|)]f%љwßtxJcv,;I'Ҟ[_|fk'ݤ# s> 6!E+)v# u2RNɃ[iۊitOf݆p^.US59(m^njQny>6̨ÌٺX[ciFBa1a;.qlǧvڍZ6Ӵi6*c,S{Yx?iע5tpV^(46TdJ9@뿟Cqj {v6QxX Fي0'<;-SN5UՂoZH㭲42mnNاj:7 g̳RL8!^ WV(k,Gnع_8DŽ'{͓6Q="N7~j:k-K~D=?EbEzFh.!oBy}9p`j1Ng߯}7s6 RekK}G"~`& bCow0v/~nFzZַ|kK8IXmӰLU9;.u4Ccn06xzgTp_؊\Q;SF8]{P/ I^ Yc8?V o(&8zT;kwXCGMxoEžu4bj8wO;~瘈IIT#4TYe*b f:aiw`u97 H=5oj;}ѷ[!EE[^WX*m30Ο}@vv"x;|3!4n$^>xfuέ3m1,]8v?dд5R%{ۨ/?5%]d4MyX:[WK$׶h=DSyg#Qcv۠f\`\;^_^;H L`̛u(oT[ f6ԋ?fb'- ^nt7Cxf,5^ѾH>5jQ~Tž۬bnín֎ٰnUïe3HK;`ZHxm봔!ȶ  P)sc d@8DRL.TeO{I_*# iOV^U|ȯZՈFLؼ|VC"Pר;RTv-$x+r4j?p &dTAz%Y^9V"DQ䚖CLeykfGiܻ_Yv۵.F5ꋍ L*x 6`A=)+5݆@aLyQ6bPPj9Ѩc1k/(j֋Ѭ@IDATgޥ}+֊۬ O& ;^:ףVCҷ^Z EZ~M%F '޹YڨRlwGL/NzKQU/x<4m珽L Hr'""u%Ôh!~ޤeY⮤Z{ *ylC{],GMpndںxavИD4} !''WɊ qb^~AJ٦$ ME@6 >_K_yN{qҒn_L2ϱ乳bqo/%=` 9د[HOWnAar}>A 3Q)Jԩ0UValU370- 3oGoHqY%m&V*k.}Fg00(HY.Κ}}nK}T=-XW?NF%Ι(dWW.,2ܹWЃ,,Z9 LGrz{0,H: 59Y1&NKHAe2QM[/p$-j5;.|Wֈo)@A@->V@>~Ӧueub[F%ME'RAoH-8=nJ;CnNmoRX^|}SמɅ+lʀu%{ z;ÙZwd+pD/yoڷa1^;t]CAk2Hna ï~03 !j]`wʢltA-5YB"A~ϴ~e]Yz( >^?`" /Oz68$崲%FES'>S_caLD\ݣM &F֒uFboA,9AímTKfa_itc!c`m[=zmt[^vM@<8׳_[*L0"zF/ѣN-s*,ۚ%@ҵ7ظcW +:b:y.L]@P7 ;&e%/_è Y]Fe<=Ji*M;uQM߻˰,&tVpe`t4#/*?\g@ ӡ#F4q]NnhSI;p+AIw40uF?3Sn=Ҍ}o 1k{Z>p%>X=vEr"S*fn& 3ϭ֎}ѯԤyK{V.]Dz1<ńt`~/oQ}ÿVR#(1\~0Z F͊$>/CG}_iJP\H''Ѥٶu^u7Lc @h/ =>`iSIN}jiz4]̊DD=17YԠQS5D xC& 9էaQ Cc^ A@G_NWQ{z׻ʍ%ՌnlC º JM}Z,`ش0>~v1ilhRD;y)lh(5iüϩTs s)fyHU>lZZ;6jkQݭ}yYLƮ#lcuΒ8/a1nb~%KZcOsX ֭m%#WZ N Ol_ԩ tǿ*X~{<-ŷm ȏ]fَ1ez3lܣf$P#LPnm)i}Cq'UkZvj͠ӯ#OO*r1 ďf@y0nj4 ٟ.Dwmgi8 >\’IzԔ\&r &nKvHǣ 'c;qx"`Ք v~R?, g/E ϫxnB!)j]V?`7MztуԨv]c#FSP6 !p^.@ ;JUy,&UZfԡNmHq5W33م>tË{֘]7+w}>}\b.XTτ7IXVi@'Xٻ6 S6W3V9,Mw)aTK>7*iF "~qe.\x!6UCc܈>kJ]4vEG.#<"cҬekϝv/%i)wG5j}NYI*5\ݖi]@ub+"HWx |jN k\o=q/WŪ|ކ~Ms p:Gu;i-gR2e҉_(5ϮIs Tae"܁r\GHqPX؟++P.EI?;lS 9L̔?aSiL^*4_MF~lDuX6?͂yOvR .5U.x[lԊ_ i6yBZ>+]d TV۬Ŗ6'noKW.$iYEv[ַ> aƨbnfa 3"65Xݴ3ڻH]'5b_DA@g+˔3ǭ[awSL.tp2:=ت uWpXepōVnt@~Z1D$d6%YUΥX&BOsjGFpd59ɚ!})AaزԺ8iCь:by9}luu'vWb[ jޤ%;I(Ikc,'\gͷ>S{A[IGOXaAћ]VAw `, 9wfSF.e!\FR..1vSEs ;TGu1kEK#88<$.=#o<ۦ=)}F~{т  p9"ϟ_S [.K@gщ},RւyUIY=5Aq.Gg&t&H_x?U^zyr Y*޾oךYf`;/ϥ)wF]ʰ;/2Qe)<xpa Bach{>ǑHT$?1/ٺUEk4\Z[?ImJpd4FE#V[yD, = 5ɾl1 P|%pppxy~F"~[u; F3V&8 1]Nit;7kWćϞzFZF!WQ}9yd_sQmÇWd̕ ˢ|[V8wq:$}9i DX#q@aO?38U.7s|y=,LoLql s%aVmjWY֫HU|e1OQ DWX " \^;mYiY7׺Z6r%/gU1{YRmuiݨu-v|XLmеZ43J^I ёgF%y^9s߲N׬5BcR{ݘP"=A|P#DEh 9LiEjVZ4Qf♱e.|/+ѫٝMmt~½habH.\%t+`B^`{b$؈hը>Mӧi:j;y&mGHX|%qjqȓQ״Nvش!"bA@X)O_JQ^GsD>F``H$5<:^u{Xx3 P_eH؎,G朷%>Q:U-530 dۋyRG:/zl +[mRHHR>/VUjDbxXբ*`⿰ KޟbHR@4Y{ekdBvRp1PE[iM2-+oqX|ZZ~R-~ا;?9l"={~ Hx+,X='jHvFGAwnQ w 9 jې3~Y2aH~3\2"V,qh*}yf %-optLyLke kA{/Kq>++l GbzQŵz{/v幁;UHbٲTí-?ܪ]SAW))L~Cp$ ڵ),/q^!/Icq!7ߥn9%c;–nҳn}B<>M&6 Oe&Erʙ6@j^h@³DC@m.+5ku.>T[U V \4A@A@<Ko}?fC%#!HDHpX vxTtcl]@ %L~q ijƝSPhMO>Mey LOZa!w{iuw6 ͨ"3 D7 oRƝZ%;CA"Qb6o=Lyڐ2\;,™ :qL}l}r] fl9Yj²;1אMfODu9M-WVnLgZ1޲r ˓v0cB_^TUX FS z\lӎ#"ɯjU,8.n,ifǏ =BQ3ف;ZAbjtokhjח׮\^iXĒԨn`UlR"dM_k֯^I˗,m^ۗY{r&ZƿŌ{?-u)ARSGuFӧCq7mIswo6~20ja!U˯P>5˳8B:n/ qT;yr_b*iiI cnuџ_oAn} }퍧A?{w(?!8yxew%A ja }#Bj*7zJ^73˝ e^ fa׾#*Y*!,g7]{F}[s|aYҦa3zjVnuά 1wE 7j{,aZ'<޽u>yVw \TQbB`Q#30lM}{'q܁CO\! H30h(7E[6MǎԔdHZ|Q۟BTVWS:g[by稱 p=n< U?}ϖ"Y2͊ W4jL} T ~(auܪތ{?deLkߩ m޸E יBTn[ڛo,X#\+]q6a+5It3]t|Ntk &<߆/>-K~e*Wǿ0N2ӒȇݫFF]2U7r̓ZgOPQڔnD79T; q??2RsiOyGbfԁzZmM9fM-k8VIXET0D: <h/~<^òV}zX{k7aqx6dH}$ݏ+]oM<?x~,tGMQ}};ŵy A`I霟k'mW"f=*<xRi \ Cݢ}m]^ζ^`>ߧS@d mN>fQlӦ:%qy7Q{n|V-̛cS$7wlZjCy߰rSz}dU`Xx7Ï4=PeDT aĨֶx~sŤv\iĴ4#GpwETRWq֎i@`yXA_f=yν מ}+ W P %XBr)`?3ٗFͨKlAqD ܂f})8^,H/GKH|`3:fD a7*}e^%if`:O`h$:VxUL5%0%#T*0~5' F=]jnkV-[x"СswGdȎ  Tx@⭞6־b(laqWtCY )o?ߺ@9$2IO ؍~EZf A!NU^30]6ꮷ1! S;{Zfό:׿L(dޤˀ5ȗc^כf.:оբ=#JKH\ S|cN7+w+0xZR/O0 P7բ^#W*v#Blȴ~uF"u1 AOYL$XLzJ13)IlVx(.Asêaa&@-%#¬E#ÉLA#6Y/M@=pWxZCAf:Xְ̚tu4/OsYȇC#:X܆\4.m} B=\1|vV~nivZivc:i?H85 nK2t7QVFA $qt(Òo˒оF*q[`f'l_ViE|-*܊ʌ:*ycζ-驽Vx%%g?Wd&DyϼC'6_!lam!K]$P ;YVaZ=LBEFL\iSt'ӟTJaOsXŲ!:t8=4}| ӊ3h/3!Q VFZxPSg /VCS:4.O2F=,ElVY"t_4:1XʳL>ܭ 5o= D,A@AkRPKDvvށ~ ) Kyba?ư۾j&Z&knj̨CX.Rugǫť88*Rނ;b ';6,+8C!Ν;bFZbe~ IS˾kCXcvvêPmU؋Hة=xD3 W]<@, fFFYf'iuBZD(+`kM.Ү   Co3?)IW>y)-yW+LTFa1X]۸?(Zm#-$[3ͨZgl;gB(]7n}j,yr+;v3v[2kƿriT\Ş -̟Ycj'ã]jJZvQV! IgΰqC$Q00.t.a," '# /̲nԴoBZӮR*e.J59r8^%TTz-ۧMOkgodžuTX;_'@*8 17/*&7)nO{jU1}5o:ӗeKyn/^T\by*EwA@l@5vٜr ӂA@A@+]^\vӼ+V T פV.f$''m3(Pi&;/M )W8%DQĢdezp|Lq_ީHۊ(bXϪIA@A@A@R@FD=rKϥzԌnLL&? o[MIm$faT  f!0yt tqIfU+T @@'S"    ` }WMYsK QK JaA@A "(?dD 1NT#E*0B V+]A@A@A@A@A@A@\EHyA@A@A@A@A@A@A@XqϭLA@A@A@A@A@A@p!]LA@A@A@A@A@A@*.B Vs+=A@A@A@A@A@A@\FG @Եg_v7o\O{w0̓DAڽխа?Ji)y(  ڞr&DOE@=̈^ @" b+  `  hƍdXWFzAìulXRSQ7 z u֋jԥjU н3 dX FMSvyZla$Q(k:5 UWEF5'''ƿe]Vi>>TreUӗU祳 fk_6PG\BPmEy7 A@@AMxPˋgeSt*]t)|*UL5¨Z@u`` g:z{{/^Xd93 uɦ .8U:Ր }ub)vJ@Oi30 N#ׯ_ϟg㔓 r0qSS\뮈֕eݹh8й;UTI%ޱRSK|ɡyskj{l ۹硧G% P0@^#}m ^C!k%X H֮4Dx е:B \ o]&?$ׯ.PYzW/PwY&q7XvcmW]L]i˙ž. !䗩_gؾ6nڂ{e+~aJJ:>Zq55KB̊mwtPG%Wpd8sb$WSqRؤ}nm*wbǬ{_k .XA<Ľ/%jr=7p sUe( ITeRMR YlJjslZY']{ sA@A@A@,\3c+BFMpI7 a@IDAT޹ӿWej!$Ҧ}gS yĝoިנBHtG%IʽHtNq;0 B$G[M'Cńt]30ugʸ6: >^CޫW,)@BtU@dkPd_=n|+ALG~{wxЪ> }'a cWSC}rnJ! EA@Ab!Z&_-` x k&z]صeۂ9LϞFv[hU`^ܽw?0^|/T“)l^~o](dnڸ=t3 >!"W̺޳-nфIǧtQoF5- G?zi$`iTkxpal] <ϓO eْj.WjJvumF̌S!'sksV+̺_ܹzn}%kA@S 93~*/>޾2Fjkk-gOĩ٪ZiW =PeP1Fک\.8?4B0[Wx*`30- W鋾Xb0K)Z=308,jh88!Qk(B\_xȁ]M/έVk}`0-9! ֬\*Sȕ(nlu|Dڱm =yF%k}x)W5 䉑Yi~AAØp|7TSۻ:[W~DFve] V~D +f>3*/B|Y=X3]qUJe0D޴`<|iZvpK\b" BO=r"He۵#/ϼBvQlt;f顫X' '>O(7jzxe]`^BE/wgg-`Ep::_ΦÓKo~D ~7\a{+KW,n{oYz{yߣ] k?<gPIM,WP"@yE{Ν&ؕ'ӵ&X9NOq%Wߥ;o]rOyw(X1{JiOaa5jM(į5"[w7ݠQ3Ew1fhV\M~H: է~\HXq/V+({X,@DWa}CH:,ܢcbU:W0n/_˷0mXyluxRC˜q6y#!#Zq:(r t) Eb{<[\`+,CKBÆH[U)<]$/\{"f@43?u2Nu OY<{:00] ˛tCwyX5q/)=sa<巡$(εm%e̟5_@6kٚ&~[U#MOvzyx@..ڪM{Qfsf=?FfOJ[6m9CՄf[yb(~k1xX:/ٓ=f` /.yw({,f7YkF%q^⨏e.Q/ιu9@R[ʌŌ{_;%qjzZ@T ĆA^ưynC< ;zH<;UqFԩ=Mrsj c̟ 1os`1vЖ;!, L}iѪՍ/S-1TX)g"%0(ȡʘhծy98rM㨎 ݣCX-_'^ynvV~̽8:s{t@;zٰݚ.K&P[|}:qLdcEC\i ŐPc ?F0!]dAy̧)Ɠ<-e>Jz#fN?‘~snRWԁ׭W?ՠצū=aUkӂ{ *ubܔ!(R<}83o\ڶ Ƃ@$=Vf<\ |g:I ޮS{xyQ/n^o:H@BTܰp|GE0A+˻A >օ/Atu. ~GCSR^弼[͇hGypÚlAJ tZܠZ$438WAMʾQ`سQvzFDf50ѻ?gI -vm>w9jԡŸ8~1GW#cs hOO{ծK!;;QE{_tę-[ Bp. ٮi/5pwƏ_)`Qh ϾO3wfmJB\_^p@_Swoj#*C i;[Oyj`40¡Y6`f &Å$[WWv:ߐjFDϿ.ÅRMOg"1_GSrl/ג1 d*/Y8Hx(nՌ @ibPV-[vOנQ3 *TЯ G3Yfv2_{(^| L!@g;Z\_/o&,w n@vJ](@DhE4'M<9Ti|a9 &8 g-Tuj"/^h QeJqZPΔnЂGuo/orn!Jğ=WgO͝uߣ|Ɗe0Lqϩ[(,{j'\Al2whX Lq)fIEi]7M^*jԡŸ8~Q;h`Y:C"nUڐƽ/]⾕EaB \Kh b4ygwr $PP*H5X=Pɓбl1^'N4ǵp ٺuZ.Cfʔ@M6;)%ŭ^XҜ0JpDMLN cIp?/aoD':ǘ=pa M2jTKQ }=Z9x'v]hظUăHdּ9S=\Bĥ & 0Pp+QSW߁TЙ'aRŅLDYLH"ѣ0W}L@Tj4-]4=|,^}|ݿ$&@H|%k]F5MkLwDQ\wW\46Qp71`kDMfm^ ]P)p.:abpGTYuj Ըz~T_ 0ԍ^gb3sIstȂ5v{/0pu<#n:o:cru8{YtfCV Bp% Q5S Q(ʴ'M>(>ŢZEswy1Aa׏Xx=|`}h2i '\)a>+o -Z .4wf4-&*c&𧴋bڇ"G0]46O ?n5 ?He&6Һ3B'2oaV[BAD{L)rQW63o>gαob~^>ת2¢줴7 m| ͓rR}\䧠*?(|B\Kdf17Ess@$| @x9uu7h(dcII1r`ɄkVZaa 5N}.A5u:oBx}hK}zy7hŇ:7W)ɏ}!Bh"[Up̟`3j'@@{NGu*6BBr5 NeVnD'Gb/5m 5@&{/ n;j<Ǭse9ZKsm+Zݷr(L+T_z#@5z)N5 QX-3M67eKEE: #|XY 4"jǔpitoZ3ja"0:Cǃ38ZV LQSjEM[0m\YR2]wAutUˡfdqmInܪ iP#/{֊2DB'4z3 2;)7`RG&;ʇsa `hŶnYs'2b}$VV-Lqw^ި/\lݴ!Y3'18;wMG}__?^FZv3Y 0Aʋ 2/k+vԎ8s7|qI&8DB@73`.DwZ_Ozh鯘-N[3WN >̵e)t Esᷟsk2hU`ɣ,+3;fwSgJKKy1auđ:nЂGu^f(<_oj7dI|j/dirߞp!1WB3D@UD{Nb!\^g1r~KYJ|Q֫Ÿ8~Q+h`mg}zګ3IEo2UU~qޗ?6!8;tOM{AWК P- Vzjb:F:ÇJe >eY.jb׌,vNjhĎgF>2e Tv=}F8 S g/jhD>GԲ3Gn]knB@UY0oW-|$Yl.,\BX@M66Ds±;Dw}L8t#/JX+G鄀Pa&Gsg1H|e-(%mA:Ϊ4.9r.%4wp=2>rr`*Wn&Vdg}GoEPU &?>.^{)E@8|p/75V-MERf/#?|`i2H5#o 2G;s)z¹5Af_{Z 'oS_xtf'iON!F48W:_0M_䪫ȑq^ffcy:jiYf5K6oevCW<;rw7`ۿJGmsA#&JT ;sZ# 4S!GC-N͓VP!B/|qyvijFY uhbW^8s~ x oӓ+n  w?'>@/nY9S[ 7uMM^V?M[Pa SlZ50=u8g [Y.{2HU!FUi\*@Jgc||+XOV~Q޷ΩtZLjr@B슝+s0f馴s?+σ!N'|; x_ϙ/TsMT3O LuooEkW/yֱE -H LO0|h鍗+J܃> 5_?? bv .أIϕPGiHtЭ#68N(<<iÍ쎈#>J[6UVcAь oնt 8z #IS9}&Lj7]tQZxϮ҆}j5 sv-np׷Q/|gs>ƹs/ 0 :p{fȱ$s#bb㥟w7R^8K^>KDT==^zy7噌""?eVo%|U_}-^jԡqQ&A}xVS{GQ~B;j } N/?{(U3=BoӾ3g;N}4 ć9[\|sd wO FE"nL#n8QMLuA706̴׸s@+p0͐LP?$Լ (xӼY3҅s(݆7]&N0fw`Pb\\DB-wd읏 |ύ/O{(N/'(BBst ~ Rc@iiǍxs |Pf1 ̷X-f/~220zc-^D=<9Y^ 84h(Ͱi'n^)|۴*z㇯?a > <~A5jsPPP9'P0lx~ϊ:G_f-G7_|[y|s4}Fn7i=n>peA~>zs} "PQ}zy7噼bإ`п~6Yc&k-Q^E4wPwxVS{GQ~B;W~6.зjPE^'. ZPŷeŋI!<]gPK`тrtA/2tܝso ]E3g|{4k[qM4+wf;qwr}k =fefKL 7 al[Ncf+CZe엞^g~=/`&l@\` qG*~7T%C BQԆ9LZ#FOP N8|p> w(FP;qg|ZCNh3:6HkeB5-I/x|fMx&]QfRm 1Gzy7͗ c'˟bj֭Y8O6"='@.`#9)ۚFAIKߖ+l8;V^/gC0Eᡟ?{~K|* A||%~ըCO㢇9?,]qU/j>)7!@t-@A"B0 5P93䕋 *B-}c52Λ]F B Bnhm7dU[7?zH홵{]vA|<usw~U_hQBfC@&LoA!`+/&6(?cID \6& K!@3\1jՁ^}]x..e+:ve~d0\Xxbu!"2#6l$35UAxKnpб~ ZzJ%Ju阝=@ vyi)e:طҎjУO?hٺV} nZ{vmWmV7Wdެ!יKF'g?Ё}JTVjAb|5mx;r4FcڰqSo )˗Bqqbg# ]]x~Kk٤tuVic(d6j,x!3uי17EEwGV+  Kų(&<gZv;k_~`nF@!@XE*̵v2 9PHQ^wQ]qQP#>(NB D:3.HJ ZTטj m2{1ړ 4p_XX􄰚5aF|"LROP]n=B-([sS[۱53}1ma&\x׺ԁ%phKKIܜl(a clΜ:.ThHDDA~D( I䔐Xb̌ty>h( j$`>X P >,).a~V1΍>L;RS[ڱGQ}xeڇȃ`- oPplR<]7I:/ePJaܝrè <_a3Au/j:@VmuSs8_#:lwd}zI72.gàCu JK!Xw ЏtK'5iD+hة< l>3OoOhݿ14Qs?^>i^F>\:COT(KUAf-y'&5:?t. KjBW/M`ޜYRD$<<1IJ[d{ 3CS!*:W=س񖑞NcŽmmWkL-LB E ,.4> 4Qի{Bb(φ+ǵp`\V\،sWѰ=ܲ&sa%KAN:^8 G.Oh/#. uCQA.?wW߰ti ܌- `$7$ܡ^~[ <>(̇tr/[; /(rq,wshd"xqͼzvR/@HEz)1Fj g^c9oY)`犟! $B"lnYRTK?m=@NǖPRXYz옽=b&IІݸBȺgwdu,iP#Q >ցV/fop } ׎cM̀o}ȲG6 5ăO?&sA1L<{&V+S!:[6֭/DguˁÜ5`rJJiFf߇ ?'KVf4pıԭD y@)$կha(\KHd| dU;G!@l[ō(ASPX $4ͺߟ=7 dJ"ʣy|Ftm'vOc$h6>{pk/.*ρ6`<օ?ܐygũ:[`JKJa/a 1|t6Ⱦ 7J^.n;Ad"wlc1¼Aո X:Nglw&jm˦F6\p޺|z汑5¡]fpߐ1e[Fu#&W.| Gϟ~Rn&  aT0+y,*{·$C{"8w\gI aʘ{5aZov'OXݏ}*OMGB K1Vms& fCeZ Y RSr>RatQFm\g/߫W..(Ɠ4+wF9Q/Zu$3}(WSB -]'5˯GvCo vbͯwK9g"m3afCc]JܽBJBPUCH@IDAT('aπz'r^\N==1ۨU0j 9kҌY̬3qT FN< R^wR` aOaۺ`/["cQn.lާ'+J8if GLnp3HffGZ!@DH:b,h=fBvMbOQ@xlf:t7 Z_Z^|kcN]{U 2pT>;? fFr݊.̴ONc |^06%&Rԭw4|\Osss_%Zը4.֬}FA2?mܴRX@|D:`a|[  {ewb~nO/uޥ&O!@ Ege-~2#nՌo`$@D$f2T+7\ 5u ]F<݆ s͘T%" Q#dsNjL'n[mnZQ;?8 ߋ410/e8#7sPτ؜ߛ $6ץ?50 Ħ]nḬMͲ Tv&]GHyLjԁuj1. v܌+LҢ]&Xτ9umY<~;C#ᕣa (\DH=.7W*ҳ.]Y91T "'cgDՎ;' 'ߴ+ P3 >CG>7)iX&;S}ONqMCOH{b| >;&ՑϞqУUhV̂DR\c3<+FB4Zo>417CL,.Bg^dsLE(,VR!BULF7Pp*m%rn[Ghl< B{=dLFXkܬ3Q&UZcE><ƴ7haf<j܍ijh%^QWKQc״̚M:1!!pRYӸjbjz:~s 7+]|L#߻[z&U4z|*'Re߱$1!bAMx|t<9>\()eP$6~DҲaY;k@bBD0ƟyɗQj".uNň<+C&e@ܳk_R2QcT 'K3lݲ^:GMu!^@ A-JK÷_~|X3_eeɞa:vx}ߺ|E5x-^8gXB_Bxuk,2JH5#ҟ^,#7#!@zB5|$>P.c랝bwjC2?|,##M@c{!x%ԂFc 0ayfkU=~tLg&Axoa1HvpsZ7Mghwֻga.xjR@dxTqD&T3I 4heS`5ws9;rP~*-IL)L䉣g۴+\K>̰hw˅giodjSמ(ڈm06f?=$y(m!=\cy)!@F۳RKCF<3δPk̲Q3عZ1/ "fL @D!ZA2^8I7|g?g10jU& Ug65=JgG0 X1<;Y:N\eVF}Qs\RmRgwQ@TZr 9Nq^,x9 D:|N.1/= 2]E"MV|lf!%$ې?3B~ 62$o]m+ٞz.[ڔ5PDh*tǑ}0soi:˩K票 <66!֗yD_|a(=~xT,d!2xw "4 ]52r,BuD[TX#,i5jpAȟdPH~W !:&vm/tY#GV'۳ $,DLJL&&8uz ^I *t , )'й[KH`f<1l8t`/՚st`9S?jFfBrjAMDج~ZȽ&H5K|#Cs7 /:^>寪<//r"-ٿ#Ԥz$[,)9y- 2{\֯Yw7UݍD禘;jCѡsw΃~$a6""B@޲  ,h޴NiutnJTûܚA'O ?dD4jDJ$"u _[EP-y0U*Q L=M"(@4GԨCq|JY\_Yfn:Jd'j1Z"цOڤiaMl'Ʒj sb rW.P>Gkt׊-/*[;fw=zIEـډ,dpRy$QcOu G`*3oO] w!*jϝ|G>~ejhj9ʇ:E.!UgDb.3ujJǎT}صSEja/Ν%uܣ،;?99pRe*R?@ Ee!G}4݊cnoFipڱRmVZ\ʋuL7e2ӸD ʳC%jř:=.8 uێ✙?A.=nm\ڪJ)IJıÐYxeKm{l!33wZ_M!Ns -7 =kwueO&J,x!+ldj'rVtt\D<ըCqA HK st\s)!X HoQGT*(XEnЙ(,-Le 51uw fSk˜ w#I;ڶ#圹^ii@13[*7I"!Ƌ+E(Lh@!3q.^y> rIX/|646뒗`_3MN'kY!1K̇?2oژUF;` P#0(,v,, P@1CQph6RRd͗|0ه3!зk#{l'OI!%%VL(8HhRԶzB%2ː3J*c'YIWtF$k(@ѻ?DF'trM < Wqٲi-Lf @3o )W0ڙCkr:{w1?RVأSO򁎬2QxBDx#aTNl&q ùC[:E );BX6`&´FF}9Lnf HQ Ls.I`uGIq"PIgpT4.r0"J/AspnZ¿׌-`&Ӫ3GPXaM2S ~!4-)v,;7Bb#|"0韾Eq\8^͐f5&^ غO-+/xM^N3f ̶%/>A'^hSYy=&B*@ą~~ٿhRG3_2Z]X6!3ٳS4j6۴3B!-+050uVٶe#zشvrߛV+u VPj0ԞwԮ$hˆufr\)]<({ \EE{!3.;dԩ:(v.1.HcKz<+t]<Y ly1R¼,I,+L| *EҼ̫PfvM;޵>_2`ҥQ LSǮoBV9)YۡVNҹi@:Ҹyʉ?̩Pۼg@뷇^ :uk -B6,w< -|d`8\*΍/$u 3W3)RʯQZ a5cVFq'GΞn9>;C: u`[WG$%p ħNk= Ӿ~bJ$B@*@;Qv : !F'e/,1?h(7?YRRnw_` HҔ߀!̿Y$޹ αY| Gu#"xӛׯ5eAWj`9dŌ׆ ߻[;sSOOO5n"93g0V1M89}Tӟ/蕲{x?Ը/_`8ڴo5ZqנxEButvFZbaڼW|v,1evIL827-&4g SbG>D݁Ilwu=|6JLP-oYAl?̂GqSPgG"v\6ldC$&}ief3Тe[Ixyv^w* 1D,oؑH7 8mlAX.`0H5f g(N> } Ս & P5!fB+# xK˾:-zHB3PN1+%9W}[/} gL5ހ?_K!Sq&jԡq1OW:F?r ,Mtuྙr,ffQQ8(hW3ӞƂ?Lk?n(ɏ1McGsWũQܴ P(' Bn _ &Vm2.:ؤb\x3q 2Xx ߻ xy3(, 37"Yy49fM_s˰ eo:f~ }sp-EI3[b՚:㹙?Y0!@B\]CGl-{><~ь) UP*&. Pj-,w1CeKahłSU;6Z\nU]\f[<%ikpL2œWîM'& @3:;{l׍}hgB:X8w&Z%B(|WU TqAߋbkե.j}(9[{ޟ&qC+7 SEv͎z4,ULm^k0 39X4[;Hp V-1]p|JSl) Pgj X*)|WƵrV$u\ST)҉+s3o9e^9~~ .Rv"QGU[¬)o]@X(7GCԞ5o%gM-}s:lbvٹ<j߅%H2t\gk zkp8y Y&Fgԉ4ox& ;~?ذw;z@xIxi3W-'>}]u }) j Sv㟼W(5lp;{OE]3 Ǜ32-jy07)腻m:T2!ժՂ-듂C`3 =.X\`~]aQ3oItk آ^zT8—L3T̴.g+7ۥf`ص8Kص&IS7&}v&bfH}2sx M@OBS_rZ>dCkyL~^.d(@>]Iz\U{Ʋr3Z/h F"=]c€%ʍsmDӟhË1AF.d\fmߑ5xzB(d+>mݾLmڀ޾P6vcs)jdzz1)gck؈V-0`[RB^/V/@nZb' APb-efW r EMLq00&<ٷeuOsuY(ak2ȋY(õL8ydZyyz0[#_9Ypm&My)L@UE@jf"n***U UKg1sơ>>3 F˗ 4?&g̟!yc}_D"} pA%fC-DKZ?^[ oU4o-.bNz<s'6!@T~kТ& ;}R” +F<5n4_p,5@nN|H:wqΏ> 嚈 YbrnRu|zL?.Z.]K(dƁ B \ ]8Gܺ <|bT])6LCෟ9)bq%x$*3]G#S Br"@sm MV{".ɟr$@~c \e*1Us'r|*),j!Ñz Bp=$@t="!@v#>\nM(AweKw'."B@#hM>&/?&n^n_ޭ̫m(V 1n&B ܈ >5M!@!PyTd啇y B_=^v^cwWI ?z  ۃpfQpxzؾ;()ʯF*B B \@ZZpq!@!@!PVOu B 6(@ O(V yيBCK(Q) B - R!@!@!@!@!@!@@%Ez%&B B B B B B   R!@!@!@!@!@!@@eEuoB B B B B B B@HT%!@!@!@!@!@!@!PY𬬌߄!@L$NNz)vy-p4$FSXb3K΁,4$B B@/\[/#A|#@/cL-!@THXuƒzBUЌ8Iy9$@TD"] {Ԇ5+I E!@s *9wx+_o@Rh퀬f)A;E;lfB fD? >Pzu().L`(ް jժAPpQZZ m%g@=PE'B .{B}Zu9ؚϙѰq.}}`РQhܴ9Z qU?%Bc'1q,FqjQ6u+j/ %H!@!@@BFT"VN3WVU V"B B@TWX&d@xlްHxΝFu( 1:I8/h#!F;{W.Q1L?5 9ِ2! jC9ɒbG"jIh$GaәԦfԦll_LנD k\}Qѱo4?k*<ĄXj9σȨh׺]G;`C|%v>.Ի B]^m ql'v؎:y'o$;Ľwpl0E4 wΕhvvv+J#823wܹܙ`L 0&p]0r?^`lB|0D($jOT@J?v<a8j2Wx}n]|Y[Sy6gvq^ԑTLjcTyR` u[F޷$ZS߀ [Քrї3IYpza~ 6fe wDacۚc1ڧ2|$ 9W a/nxaXr| "@̾f~vw. ;!R?8|GW7h{rTŲmx~0-)A8Joz` >P8΂~}}̼y> 1zQR\g2Oo@q9=<=aҔY0jD' xb(jR\Tgd6EXfXh#!z/Fz:&zjBdT Phkó;fŠ!#04gaӷ_sb^_nyWamޛ/7QXq6rIV} Y/aL 0&<}a!.u E ϽnP_[ ywܽΧm O >~r5aٙCBmHq$ ݢ7`xA-,-8g;Enb1|QOQCiAq0toBI>TwY(ĥo<<+pc֏Cz ̀x,U49Īr mGBV{|6ðAPx,5^zR:t0޾2f 4EWp,ށbaS{':^ήk˱гD4znz`*O:P@MM=zQcd§'mُQcȾu$ZHdvn27N^&&4ʲ>Ԗߴg"~Ŧ;>91ιmP__'w'-x@ !pSTt:tP~X :@J0c<я2On/g'+}koX`xBϤ>V(2?X/ޱ;FA텆G(thXrx͵\F } u`;R^fYt2[{ZA\C}?JEJaӌG`N" /W/x 0&i tt?/ǥX*C"m?,cq*ewôe"$yE![/v1ɣ'usD.PGoן jQF ,(+]>c8Tit,$ѧx,PW|1qr3eǂJ6'83ez? 'v>QP5ڸ`txp(I [MEuO@YnD< NaL 0&Щԑ#Ol|]^V&‚jdŅU;1EXK6eSpcTt,Ӛ: yZ|":F{1m_Fz&6>$ʥU.7)=`g2/t=(C\$C ے=--'M)z:㷳S_P^&/_O ZkCm=W,Njz}?TC gl;C߀VdO`L ytiÚJ|΁u5a |΢.x]ObO 0Y=[Se"tҐiH6a*CYޖ(! 5`oiRF&;~t-vEOsL<ֱCtSIqh.b^`Lǥ{`ح#FӧNBk ~`G 1y^ە7=Ag͑~P2?jNFOvxagѢ~{0ء#E62h{>7i\Oy8[l >)36eM*5C=MJn#V"QO}ʽ[61&CtL{ eqǰ?d2&AK1`Նհ|&ԣBM?fРxv=%<&'uJD$z=?Ɯm 0g[S“㦢7b [!Ρ=rݩm MG %(ݛb!GRi3@Jɻp섩2]1TX=0epQ4ϚV}ޜֈ6fzJƋ곬!]ˮ4U]7#OkD{u+w_-UJ7(,ݚ1{=.y{wu,6}¾R o/eaL 0&`f!JȓGE2-Iw>%I; wRΐyp)YZp(jL|ׯDMOړ 0ܧ4 W;wI;f.])GRzQ z'$ )LWərVwJ!yQ<节_tapCg8=O޵>EYU^JȫuݿfO\,;-,(J s DZaSi.1$Z>#\:_8N1k*̈6m[[ 0& HݱT""M%#dX.zw˂%=Zr\?1ga+ddW(J|Rx!/-gid]W355V>̀˗.349'Bݺʌ`ZZR ~"GЛ$AƣpD@m:d%w*mQ _iWO϶G^}{iHӎRIu:B~ƫ< _ٷS|i!Ab;ݔ w_C]+ g-NٹvlgNEU-na"(@IDAT?TS8n>~Ѷl? <[9܇xg_@#d}1,{ž[B\rwC8¨ $6цhH$/=45\XV&?UDW+W@>N]ޘAA!B@p=0r {v53Co-^}< & 0& U}N3)E<J) B:%b1iXV rEUbsd/_G6E!Gb' dV'ȍH˔WNba^'[Ka1ekp!}dF jp <服xjZ Fݧ1OXynl/`'킍&:؈J4MAP(]A)4#~5 [IL 0&:v10UTVT' VH"F5؎z|MRGY7G $UUո-yb>j SJ=m?i{i9dG v-7iG<(K$չa0(D s]$Ԡ MB `ؽ}+^6mn/he2{LPRnozA=9)jk͙6B(rKǣcO@:곙7O?~Ԫa֍%sYBm'83}(DZOhCoTF"/M|?ت\!RZ9bE&wFFojS{|)\=,$r2&0# Cp1B mdJ ўgŹbmڼvUg\ TzF͟9[&w̓6\=G^Ja` ef&IџN-jyflc}sCeL 6D#ˍ>j<`L\. ]pFQD9z g"=F'.]=(\`}Ё\l$[3(3-p63C|~G!BzIl<}E;z gNgD@4iG6CEf/^65;ыm'LI,$/W2fΟXp[`ᦹ > rY1s׾ocHK)|}=";6>ޔ j{jLTC@ =yZpz}6og@A]Zy疗jb/dx}zRrzI$Cj ɫ. )iF!N?yEdLH29]1dd~*Eb?P)#xRWY3L 0&LH`0ɰm"NԳ\ZGQ[¨=FTm>E+J tlr­mBzGj˷muͿYi R5sR>1󣺢Teck /3&0= *a>!|[@NXHqm۲"j$dGKm1G 3~ aI!=tDyiՋF15ñyԻѣsfZ ʉ `J?=!CCpŭ zcGmձ6tЬgɛ<aUdyLYN"y#R>3g[ϲo)\-K-Uiq3mxDuP.FySz;7'R+|j#JFCT"j ~HiF!NOH2|}ȧ<ቬ 13[gkLd [mp9`L 0 8p[apΚQ@S3RUllM}4,s4ʺr ͺnܔ+MZhغ7彤C$і[gDU{55ߚc$S-5lKmzc۠Оh=?fDj,LuY\T@#:#"mn↡H@"C(=kk?Ԗ"S?8evqO"3}lYL\Xe+ߦ-w%S:g4Ot%==hJD).*t#)kk֔ck~}SXduhB˾Zz툲ŅM 1M/l䔕'XlgKMr;ZumtlE٥"e^ t쁞b!#y9<\ŰH(Et *ToomʎL8ӥKξb>W8qLl|[97_n`L ߢWX30 #4>3ZiPAiD' 'vYj+ɳ*GW2]1Sxl_]ψ$ޖBUNiؖ7uh-1}œ{-.uU,0 [IL 0&:CDzp?}Ar-mݰ0]poӶ ;/fY4BV3/7t(":t(4F05ݵ ^jҴ;|^Ƚ銾)R o bCaA>s{rYy9ڶ6}\ԝ򴑕Ik,CwQZs,F\2O݃lܮd-:Ն)d) n?l4l<{J}'u g|gJ?| s<9`<4(Bv<ԕzuSLef23-Z PKKL 7]$1Wm1\VhCī3z R|NCC,m)ٵB܍KG%Ȉ_\ Ǟ`I:wtRDSr]ͲL2jÐ4T7LMEUy14QV4PVi5UeS YZ^t#>\l2,֭GO7u+ 1,[{P{A"C\heY;cD)ZT )iBb/p_ڶDrو6d[ff|e! d]7!PTT9~a i`LKDI;n-A|P^ [^ט~ÕCB!">U{B?XgBD,+B{CצRƾ-~h|a>F\.׿km^>g- yh'[pa,-@!bГRT)6 %xnӓn9[EJ4bl.(Cb~%yP]Q "ע#ڠ6e&`;'a)=`}_ҢJ ,00RӳWW]] =}=;6ڰ􃚌QIg3OG!&v95i{2)y_|PՆbG襑.H޴!#[u(2ى]7[xr3Nr퓇Iӄ̛ S tL/7#ѧm)'zᨘ1oSimii-p)6m9hdg/bvo<yP'pӷ kVGxV^WцU&)p6)u{Ňo-AV vo[eL 0&LIaԻuOPt5 *P5T㊙M> xY<}!@]8gKd`'˧1O@7?^ӂ+,Vaaߺ!qDE"DC=;މjO:U {c6;\mFwnQnoto0wm1oDZ&`7_'&9u336HaLIT!O1BGO{wtط:`T󐄃߬.ڮz@&h(Pvޢ۠wrW>xmݴ=uµF11E<"]TnN=TG.d{ڌĤ^"?+!7d:^o}{cIZ6}p7z"n֯p'oPUW[–R?~q#y'+v_<> O[*Yl1fg=؈6,vhgCR+'}WÓ{\`Lvz oj+xAX#9U% pC]o [J> I{Fdz?rRr][U@^x}_xËֽy>bة(&F5 [IL 0&:.qqmu`4xyyОF]Og7mHy:;JC< #LPڒJAP>SƮ]ki3ߌ4?`@k~kK٭錦C?(wFm=OD#c )I6 Ӛ/<2 ]´)3Yy60fɹNÈ6dêiGY~QY&`!`j4#L 07O{DY#i6&QKW{NvT_xL 0&h zǶwCd߾[g7sm}|/8c~s3c="W,vцҺ9fs 0&:;8q/ س2] yϭc.dH^/Vjr.dL 0&om3F¥zx: ekFJWĨC{ bLʗ 6(rƈ6d[f/f`L t, v~2&pC(++14[?L 0&|\v|<Ħ/qAa0915ļmX5ځK]3&@#b20`L 0&`L 0&f+'⹇`M@L :6־VhêQ.`L 0&:.qqur'`L 0&!@ti.l.lL 0&`L X ܡKtEC-Kh5cL 0&LD3 0&`L 0&`L 0&`L 3?`L 0&`L 0&`L 0&`&&`L 0&`L 0&`L 0&ڛ M`L 0&`L 0&`L 0&LLDw 0&`L 0&`L 0&`L 7ۛ8 0&`L 0t A0.&B}|L 0&`t~ 0&`LM۴5o`L 0&cnn0/r;/˥rgw*zxn͆BPX6 'w 0 {zH Kj!́{mvBn:ݿ$}di!ܥƙ`L 0&\GDױ喙`L 0&Юܻv:vnisc}dN- >1v D׮ͦԁ񹩠& t=hCNNvۥkB/N3EJ90&`L t, v2&`L.FO[Go_~۳2&nBPXX*Xu:\vYv[Rta}ly$}c6hFNNuʭ=O7g</LOO|~ٝ[^%.: `L 0&X@3&`L Nt4]7V{oww Eۙ[)y&)wY7^p޿I}`N>eh^3K?3rKCo?;.e9`L 0&`]M`L 0&`70.tPƼhߝ;sCttUpϋylQɈt p%M`L 0&ox}/`L 0&p>mV*ckV|u.+Xf$lZSHO/<<ŒcNͨ5A!)6~TZÐaL 6''\xSW6&`L 0C3VS&`L %PTU Q[nyzQ\g5E>apb)^߿" .8(X.*+mN E@ o9p,(% ɉpR\Д];/f&U0{ _<JAh 91ѱJn̞J)9[ThQfB\`G))q 5UTU;lsa0I ^ʵ{sQH /7wGiM P a6@+ҏ=D{KWa40ϩHRb[:d1"+6 Cz󡦾^wlpǩ^ Vf7}wQ}j3&`L t;< 0&`f&-7Xtq>^xy'4¢0_P`x}/1,3$%MS[7;(?>~2f2Xj7|`] ~*(6z%01!I],=}|!?$.7~i,O;hUO]@hu2_g݊]PD<ӌYxXtJgaI!)7)kV;],H $#F} P"'Sd$R?4d}oT<2l4f˷Jw",iz`[^|P)ޑ2@ԱlPdvgv8(XY^oTMW HHEP:[ E B}z1h([]?tkDaL 0& hQ>1&`LDː⡽..Hd%PPDDiaկOx4=nHD9z' -f{GZ9Iއ%5#jדfދCBxXiW@vi 'R0 eB'M( R?t>`O/Ы8(>Tozm8 '˅D Aӧ0V.z q/GY%JAx,I'jZ.9pNw䵵 ʶr&ElɎgݷñj=6#1 ^;_nL3˫l$/ݹźYWRg..w<$ґxXs=1aFa,C3dA5@)*$Q%})!ypnC\PP׻%)@qOP7dT?M|\lz!hlK2ilD3m[qh,]6'tݍ-8D*]w~225=t~|D`L 0&YxϠ0XLlL 0&`^1GƇƔ/B}RXEg=ѻPp%QDx7Sfe 7&6HR ewoKXB9ߕT+ .jvQՆհ|؞ mi7 o0TxD'1_|QXoYȉ¬b3 C}f- io״xH]dQ5&t%čM$aAQhV CQދ=GxHD~(|h1E"F⠥WnIĸFlRO* /&}}:B 2& 4_m3@f?ЯHe>zTTw5gĔD )jqčjzdz„PsS9 PKϓ"ףV@TVvLMH(|eO"dkL~uz9 0&`LyΧO_|A/R ,vx;&`L t<$8bؾC !}4Ž/.SxPi#NH@LFyˌtH~3zJ> S07\)|~7kY- $N8cøܞҿM([kFCC3t|K1Pɛ/3(,]gz)CT0Aܳb]8 0&`LiG:܈8 tYY|W.Ywϛ ϼј q><3Mm;,,5?tv֗c] ])*S>E[vsчDD̽٧[(~ 'MGOӏw\$_mqhhbD9NڪkhH [1-S6i\`"n:+Q8a%-31F25=ȷ.ZL 0&`m!@pHg t]t.5m+gӽ `L 0&LK G@AϞ@!PP֩me'5UˎLނYzRj^!Cu/Cuc!$k3<:zX==)Yxy.+d-F91)6`\/۪G$BoDHy!adNmGӑvj917ǎMRxhM#F556F0%!V`^Z)Y^+2MLv FT2yM0&`L I8) u:gaU[gZrn*չ `L 0&@'$PvRLP0tU9/ظD:T0>'vG:bq6raN~" TGϐ1UânǕeGҦBd=9'B,*l $զ0LW#!< spgeᱣba-jx=ʊUp2(yJPK(FÑ:S#MnAUxH,zعau5At$҉s(#'`L 0&p}0BsP{23L 0&`'MK~>!N[r;O9a-C&O . Yz`yݿуF!&>C6~7a*j$tU}GmKGb2cWRu9WH7nN~:j<<2|.3)mltc=,Qx-ml Ԉ~(4ӑckS)%d 8#/'L`L 0&~\31&`L 8ѫB4="q0O ̀jUM 9;a9ClQo15 *< nOo=Z;W2uWts, zmu_/7P@b~l˨iGL^!ґ=b`KuPd"tMM.S.RmhkeK!9fDZ;oD?`j{P=[{vL 0&0EhGl~Z\ 0&`L @`@0 bzP$SW!w IP{}FsۿÔг[XGb=1sEV;/ގU/$*ymFêc|DhUk߷,; T5E<(̞{ZgCfa=/Ȩh O(Wy$z&S'N#dкܫ`8雑]WBK5^}7o_}W.[mz`@8)o" Ćf;!=Rh_h P:dhkFWħGF !rqT SEweҝ. Z3vۺܑcVH;(ѸFyIaQU5zhmpkv`j{]+:Hq`L 0&pc7̎=K~/Q&`L 0 H$Dl 5K  {Jއ2VEm \B2s"P o*Ç(*Hb3Fǒ!T<.cҖQjl<9fWץМ3QS(!H]m$̅p1i|)_R,qda+<0#y,yJzދڤPXH K!VI0#HnVG#ONܯDO23Wy^7Rq6ye:jF}T ct%R<̘F[oӎu×F`xQ@drhC`L 0Sh>);`L 0&@ ?\a'{w*xL 0&`>S1DTce}y\<`L 0&rm9ˇw`L 0&#.wl[z'  Yl^CGu`Lx27U SLa7bS}h &`L 0&`L 0&`L 0&`w#mu&]K%0>sHy\fjn!'\V=mtkwimf\R'Rz gO  \bql6k㋇QF.l3^ڌ4b;c2#`L {зWhy޳{xq+! v̂7>ާ[ ?QuN٬+Z&ȸ 0ǧADCuSY@ZSM+L 6RYU+UNAQZ6 veLe}$NaGeu;c1$a~|Sc{ae@]khŝ܈oݢѭpf}~vʫ?/kW`38!}5#pt_]/ Fi3 ;3wcL 0&`L!#`.z"tctŜܞr-(IWoqIt lT@ѽc灚adKmP2|xTSS*10 |=`@Ͽiц쿭i{2UwO"㪺o ye7[0ez fu^op<#+8s>~pTцI^D=M!ի UU<0"wxSO L`E#ڰ~G{ 2eud7xLEUV׶X+t;/P P{_j6sbo!C?3r"D"tO%zU\Z??ץsL K+~?1@_{3t <ƛ0&hg78o$UAQv-|;h:tEkƅA 5ꬑYm]=N|5Ix$=hkpj}F6ȄٲXR??D 1shD#Pv3:pp)>O]]wjц;5oYQѱJ/v[z'++m$ b5بX{r/7oʱ `L 0&`L 0&lD)R 6w|ĊQ/bD7PHIY=!_,5My17zdUa.r C<͞GD Xa?> ~Ȉt)*wVmƨckүOw9ztoG.Kp̰xA@l(/muYy K}9zKu<e[ک3ip0_ ދ[ĸwʹ x̀Wk{-0~dėraÖmݤaL}Q|~a98 +9\OZţ-t79~Ȉ ړ_};xn͙#V6 qJV3dcO܍-}Dmw-GR]Jm(afA C^\ n7& }zFAHp~u|v=cB_4Vt}=ϯt{=<ȉsA)T͌7  8- 6{nwPٙf/8?L+a~5eZ/4' yb c'Lʊrx`0w>PX9ٗӏކjtvyà{b(xӏ&7_;__ #n mg˿ RR'y!Oxct{P!FT8>v~jZ׮,K R;1]g!݃+1{#c9!MW~Y8N0#t=s6r}Q\ l ' N-WC)sس\L׿s(]rt󓗎2^m{WcWEFE4 ^/^4)݇<]WJn_~=ǣ ۭ&c nuſJ0gٌ%C}N,`>P׽ .#naǃDʱ2E߅#;a\z6mx|7}L}.(̶rX<3a4/Mkf8׵}e&`f$pC $:OuϹvnl_yx6fX-`RaD0xr0//mznBC (z=xH aQj#bgM $~2(JB-{VbA3A_qŌ~ J$X rl<|h~}(L}G.ZAFnX|Q@玡|DGhzĻ;^ dΖQrtHh VXT)B& VeA֥"9k55 F eknԐ8qj#eu)kO@lGdC[o“NnB?P|aל<[;%a|HQ["1'{n{?\: FYtXBAo0tJ 6tX@t<>y"EGpAKtd0~& y"voO?Cb\Ĵ!0znl'g<=!{Ƈ ;?1Y `EE@msr@E@$NnJ#fr,r|بOc|Xb5pjш6mʱG-ӛ~St~O;xM/*Wڠ=/̚ԣ{iE;F/ĦPE{ja`hQ$ {`KOBm<>}tj$}Qa>;z~?Y#{w=~H ۷|}Foc֦,%S{~(-|䙵g(xn1yJ!Q]W}s}1g-E\!B0}&}_R(]4H^z#(C!U6Ij#1~H(}8!]]Ew5SeGd֟NPSConۋ'e;R4%1Җhĸq~wzn̲|;^+tc^|M (Ƙ5/l$|֠6{"YZ[+H|卿CG:P q &k}bz mel oMߩjşc{}_ꉇf9-: L 0&LJR2i'>$3qվ1|;m Z|fz0$x{y,"Kkpy5Hav\u駯yX'"yLSsV6aL EC?v Pxi@uFSNnlıȶ֌'1{ǼA"/z[7O+Jn B@V2P̍Cs'lV7цaj5G^'9Ïo\U[tZsp]GPJ)9yYS[0zy .runyw߁B~q!ޒ ׃Q:.I˲ECK9zv@K,>wz hd=g<1J<,"bs=ax~Hfj^~mi#Q$.7Ol% H\V)pQ$<Tj|H)-EGigemXh{1!y~fk͈6Zo[ۑxH{a EHދQ!bS]qRXOm}_G*+ Q퀜sZjpxC@zhLb]_ =&1WƈE[gK/CfĻF1jgϗGԛ888q>Qzqd/gd;Y@2oR/`7VccnmOQ3ۛt .5CLJ$x yOL#O /\T//+]`+&Z&΄A0Ib< JC:kBBuvG*yxhˌ#[szmе9)B<$ݯ-+ F*uBm^=5KR=4lSa O^zfG7_'+wl^ aǶFAv!0yi> >V[&7ӝvԸq0 Җ%~ћ̢]jS@$ѝU&fJ]b߷N|5`hm0U:/cO8F /q2="ޖfe /u8iZ^>mעO{3.EG=a M%V (,ܜ"]FNGև Bcdoq?|18p}{eYaU׷m6wΎ7c٢ОrC+A %#ox~ڼ|'%O ֑C,@k, (i.QZY**vۯ&|0=1SgFĉ~Br^d Ksќӗ^QZ]~F#m];NڹKX^x+;{G{"~#ca+ "R(؅#7F\bw->/zux$ճOyh޹BŢӂN1+NԖ7׸1QJx2V,z D~هe[im@@&f bkUQVP3Lhcb}Ej;d|$vLȝc.+9؊כqж:y\^ 'fI0**">1^a/n\*sQ~blc'$N,`֖F Tsx:.\ep{rFj@ψHX.OyT E֛w G]1Gnes5e>{ ḧ::%egζ_;x҆m }/VKTüS c uT׮;d:؂]q>2,O~p[۱'Cć(:E?8|ڼr͇'Q+liP)9 Dv[V(*ŠRo;?4[*YIUUUc* yxѴ/(R VdRRHuʼnf='^004OX!jlժϬDB=է0~}K Ǩ/ 봎" LCqҜ^pVin_w\G]Ņn[S_Iy5J*\ĺJx!%PRuWD,^f h+%ck G`zcCWz1b_(q2tbs~^e/m U#CN`M)F5{鱷GWcB [2狿}9vhTwS~{yU凉<22{v\T\[p dz=# 8.CtEc\?>a+kFgկc4F})hm+̐@@!&lĊ";2VًښME, )ֻS9!u )V8 :'캏YtTΈkߛq,󢡞h>/E6PEki '#'c9G*a2'D잖ce+ތ Vg5v25$,.xVy-n?n|y؂k¨t®OonP&F5r-Fxqw0MD,%KhWrkZX/^fJJ=/Uzr7ǟߠV %76m;A6vv=Ѻ`#;YyNfC=xUzR`ܕ7ag>epC#"[+Inely+\nW Dvc[_{ÄQW/Wǘ Wͳc?Kx=M ܩDoʅ\c g]M*j,j5ejL=?.=V;L%荄uq!07D1tV֒]xth={S1}r[si4Mc즖ݿٔ)N2Liv\?Եzn?B/9_JDV Ӯ!aNv\TeN`p,RW9ňÈ:\JkObFp٭sדv]#@@@HUH#kƏLIE"vA׉Շ+K9O>a"?7^3DXK%E>eJno\p;cK+^m{ތ [!y#lJĽnL1H,:op_&c+^% Ň-7mVVܹ#-)Q;W {y9uMl%ʟBע3q,Gcm25ӻ6w]e֪Ǿ7M|uSU-[IkH](0zlKb{"pO*m%W~mJq$a qr=p}_v3U08uE'uxgN{{38j3a^ek ٷ;[Q*Uy>hW|_"4nd/60QhvmwhԵ=U|9)E+*,IP븘TU +ā})v}rZlY8O՜zLwbqB_IR!GWW?uM[OkVn[N_љO*gL(?{+ze3>tD؇,D{՛ӋD_ci',=z+wrWP"fIa! {D;t'Z\>hĸqhE[nu ;ݱu*|^#uyuqQm`Gp^sňÈ:1rnƔCfWؿ}cdlDcJўjJ{C:V@Lz]mol*jZfR] GE ,_˱{$9"RF85j!@=z6K'ע㢭}b]~~r_zxj_텼 {⾉_Go|4׸e?sPf;<+=w_5Ez˝:b3 +|Sm8S8삶gɵ(Wu+=u݊>K b-<,sیr*-H_;|7 hpKJ!"z*AXd+^Z.ddp/H[}fM2ֽ^DoǖcQqTuSF?szgoFm ^`#) 0{s*1zK9y4\K81qlI٢'vҳ} #~9U>e~aCOߚ{&F$nz'Eu:Cфѽ9^m,cJ7|-MKrۧg h`.cq-FȺF I>ՇVV*F1^p[oԔx1b0)XYs=v ƙ? ݓ19R'X}63\'i-SUn=I]mTWQ-e-XR!~~Ғ(!Xm}$QC?:mUYWL=(*=3 hB8Fad鞴wO1.FzWi[ړ@隘fnoǢ̞ R,ye4݈q1b0OڽkS*&W+bN32@ Ъ ),X6e5J$0ݢ1ϕ g^qNleʸ^ϥ_>9Kr=Ǖ7z";Pd?*fL#VNVg춣׶;iF\jϛqQuݱ7KVWzU/ 1%*j[/ǖc:r<~tL{\"DS㩋prL +UUO@@[ZG76FԡmF>sܧ*&ORUUxX{KOldu-*Q.uocp]:#{ާ"~`8օteŧva\v:W6ڲVO?8˜狵ݺ7!z?3\kN雏پb|Kٵ-s ʕtI }G.Oݺ՞ +}r]ɬoQS,&ӢghO[f,^5q#!- ~C5AN^1 ӵ;,՚G'Qk,cڸ-OwPѓ}{eTګd+@%Vc`@Ǯdg֞8ur_ /ѱe0𒬫rs\Tav&\œƑ򸴬҅R,nCv%",f ץlqrFԡ7L|OQbaZ jM3j_YzrwՃ%1 u^ms[J>wZ 52GI[ |zJ76 RQ)[=jy-mEWy{2[r>^M׍n]hVxVŶ@LQ(&QO0(aG3bl{z^*,%~tHS??[w2Je̳ms;g D,SnhjwZ_ -VJZDg_M)/Qe嫲DyrF\ Zeᜡ'^-~~VVDT;h t`Ǻ#ۖ>J99M4g( XnP3,,Ԃ\p*m0 lG 0sQPiw-UJ(ts-q5bD_N W/pi}2ќ&=)[9럞IJ+=/DƤATyw7cCn%WAaǧˮKa|&[h?lVt[Q\1VBmuM}kޫ(1}M"- aĸphdb[ΨcwCNȦYQuϋי- 9 !Tug*sSc.CB:x;.>8Mo!v 1b\|aDȱ=/;rJ[ZOjdz6/u%=_J[V QK V=ydYzJ,ZT[ޥRFs[ 7ʉeZڹאּ1W?*H a]3Xe(Tn_Ms#E]70r˱1Y և,i̶M1[ Ðqt͌nS9֣42w26aw LJ2G?[OyY,GgL52]h#Fԡ*ϜOsz*YpgḂ{9*cXDd䱽hPE+~!!"d R訽Wrd1x.Me3.lqO|'BϷ:eqF$Iyz)hɗ!9f}1מ ٶyW/ş]o6[]7,MF+胿+eSm;&77&)p@8s:䄋J֫2بbT_ؘܼٝ&b[ +?_mRi5h/Z Snoei Pk&{z.fʿ{Uo7+_q4>E{wL f]4*/N jmg4TkwKuҀ iwQToc0nxgT0߽Gƈ,<"rmnF|FER)7ӈ?܏z /.^yP7LYfOW=lu^^/IhD7FōQl\y^lv B/p ˧OS-W"Q+wlsaTO[IznJ> ħViz\./9-ǟGyUvkĸ1Q0zl Ȣ偝`jj$@$`1ieyX tBۄbТ* ѧ :v&JCV&uH1 xј?X['Rk)S6<ZS,~^['o+VG_ٚ-J|=LNۯ.\6v/>O7+7E,p׷gJoح_*RpVC?^ys.lTup25^kH6ʶ(^z^\,>߇gM5V(Xy/D.ae~l1ؽ3+z8&aurU{7'SR_XLP2u)> [fwv-|ar%g+ByH};z*~%㪛nKpζv[g;E V< A!;TMTiFl#ouʒ={?XFy]oo7'p>wVA9EzYCXPu_s,Js} kvDLgV.缺Ms+Wtݏf@rlxw-y\s.򐭿z%9]l۵=>!bQa!+/G_mBLmݟ|N75l-ᵀ-dEmKisR־N͖yyL؂d0j\?]ϋz<:o>jZv*/1mVZ=vASO/d5+^wI<{feͥΤ?.uLގu=k-"{+m 풒ZV}pU{\렿{n)-mXPx#[ı⎕5uR\ "qpO‹lxQX(Yث1eǒ*MvY;W԰toq1DE-yXC!98. X}쒱B }\*\Ve&?ggϋ7uԛgΖgqYXQ ':ڗmҐǖDŽ ^qWb[ttYSS/ vƶ}9.sa&<zm۾kYQ S,,>[1>e?~qX}\ZIa뽦{$t?.(* Ӛ(yVQ@IDATz>~Y4P6~RH1Hepֹ?4so~UXIyf+^p B]]mZU!U9bkz٬:fcb@`;*-<񂟫;:w *w}yQP7yآ-Pv 5FGpM^#ggoL)3GVP۴Rvi;l/cUʥ;DX#u=1r\?xD 2}T$z_揦nGӵN%ǛL?0jpͱTv\?(F-S#:@@@ϵJ:zhŎ;.<^gJBOsbŀT쪖]64x̱RʛgO0Ϝ޽yz?M-6+U]g5Yw59yp\aw$V(O Z.XF6 #L|^sf}JBGfS?K5Hk##+̛j.@k#*cE|-:bV|랧["֗n`}֛u\. =!kʸ?}K *PJf}>LI]Y0K-Ol%/U}|EWgQΦӤLW|`꟣q1~\x@@@@T z鎏]B /ܗ.adu\}SOɡ4Em0Z:=B?'MSVqxBCVwk`䐦#pA8nm\vX1p 0iLL33E    Z Z8.{,Yę`I Ķ(8.-}DF>9C#t]rX]o }SbjJLJ'@@@@@@@@[AV  #fS_SUy:m`P0 |#:&# 0kk_Hey>m+OG}b|{9R'սWQn5CUTz~G/}Cmv4n7KM'MQ        ^+|2be-]>է/2+> =I~It Eb*).Ӛx񍥾%yqm   OLte_WIPꞭ+*ݪkEн]kǷcMP 6[d}]3YR~^# /dP8/ۥ:$iQ|ԩs PpAZCgoq +Ju6m>4@wIwœ;ey 4#&:b{EU+ǫBa)L&q i7ұ=]8O^ZOROR'1 Rn!<« Sڣc>si4pmtHĮӓ]h#(yhw X~'vdҩ.bC;E(^ݣd_VXNۏPK0HCЈyeA٭etzYJ[^MҠx`\P@?T*ke(ymվba)Wb.=H-Cs[xD⋗{Xa܊l\0%GR{Q_@@Kح握3S1=u?r0j\M3RV&4B eDc]'vJqvk1o6g>lxgR»gd>g ]B^@u5uyK(=ۻS CݪꪉSE12 D3@%b }XIHLmٴVcɼ/WE5* ջ-}JJoU&$4\*S/^:ww5hu}huF|UiKSekkK.ԉ[僎 Ν1E,\,|'%-lNՉ{&MVI-+yՉ E|^@}`z;VB wHqtV٭u@4j\DYv@톜cEР[>mu^{cDތmbɛSŜ bڟ,^07{G^3VspPzDdvtV :@@@@@@@0(h'N1><-f]ڪPzMu]sW+,n6ef!+V Νqԥ[ y']aͱrKa]m5^XNʲ"=l: zo7Q}'V6&*4LKtzFlKMiRJVͭtr=TB0+sVfkR^.ځj(<;1FZq=iܳ(5\]+-6w{ଐ7 ^E_ޠC<1ݬ<<))Dh&[-|rpLZvLm.f홴y.i}SSU+-;uk*٢/otIgzLgEOۄE*[j%=gĸqhwyіf>9]*Bwv:x^Zv }h-rʗBAC?Pbh=%1_(Bݯ*qQT}XT؂@U:rQ4<*L .e 9/$e\YmmM};{PN~qؿn){?h{(u1ٺ5ڿ#s }lPUYwQst&u^0sRXw*ݜ;TYVYq~rRN1{Ln!GwhA(_…9=C?tdPFS%QwtCax HOzN ^eYMĖ:J[SNaL!;WÛO{.R򵶺n<%T*.K\KD _,oyIh8uֈzs2[*L=ĕ*hĸqny^lzz1Ddw,kE} O̹̚##`ގ-+YnyyW[g;WW݇7=Me;z[Qu:ݩˁ]빜,w#/@+%b  :cZ O?BTOM p3qzb'2]WL^-)U>gPUYW[CGwPIɃdZ}]-ު ;s@օ"o7A g[=+ [9G-lU(,Yzaio$ѓ¥$+ٚU{FiUvEz)uq4zڹKX^x+ |Jy>(7{9vi+bjNK9,|Oϊ](:rhĵ1.FzW'iIYViھs; ڭeLqxzIK/Y^Zi/m<=v\\OecEtk\?CJ-c)eVUlzhGKbe=o}:Ts{ugYp@@@@@@@ ǃ؃{CB:ЂE7Wt,K}}ܲP):D]Ûぁ2@c>nO='"[/R\w'џ%PYfR_M7)dž:"; `*]"QݻL/7㒌}$24FB!0, _OQ SAUB'Z 丱U#CN`M)F5{鱷GWcHV[2-Cs#4\ɝsގja n=xgȸ|iq1b0W}l =VU9!(zK9/ OզN +<e@@@@@@@@@K D- O2C֚qRXf]m kl$t{ZX"rK-lٴزAA&W\ɖo6׽wWNu-s&w4nQ9F=a7$tK)QVy.֔Nu#[Ui%AC欄v'g&WlyW"r2EK5#\qC޴ݣ+]L;=$ٖcB?EEoH_ʮf:댈m7z OL.yd*]8/ߑ.{;.F"=}^Tyo깭q<+Ww&RvlVU?{ގjtD؇,Dm]Џ贠 ֏EY:ɮ{hē1K*{I#zPQ=˾>2f抿lY,D#ňC;t>/: ws`Kts{}Q*|^#^w-/nHts\T6ؑ4Wmq1b0G\=[1e)2o|R'jd"KXwuOQv yP oX[UKX,\$]^.)/XjrIT vo;]LK/;ѹ:\CGnEۇ;8wē*^},4\r!Ӷlh{V s,^8ӽ^Do<U۷U.WKˤҶMu\]a`u5ޣ^|TVOvg7I[Lۍ51.Fb[Q52=/d}_wEW.fձVT|b\?S&\TVT!b,Rʸd:i۞_0-.2YFE~p?iNUeabnkTZd[xTBs82-e-ަqMwt!}Ғ(! GeY+SQ(<mUYWL7msʨ]Y@KV61 # d\ֆ4em8t1b\?:Wi[ړ@bע,E^^Oeʹ;.>Es=cRUbaD^)ߵ{*lr[ԺFg2dR\B)2Nw*2Fg$tOr8R@->_f9oRq?} 6bCh(UVyJ{L&eј=e~ 3v,](L& mmviy1"s'jOosQX4 n3]=KV"޾uGP^d76=3G*f^ɼkXU\(#Yْ?O~ap2_5Gc uom5V<ZLgS [1iEn(}pU +_UYS) I ޏ1~y^k0 cW+F~fyP mOR'eku9.yJV/v(/+}!FugC4Q:BE]b#]։+e_>ļ ƿlwA&WYzYuC4ls%G;       m}Ы>vٹ]ns[<Džn (88XNh̹rݝ^8ouިeUa]ciHa&N Qs!6w1)+ E'XMLc?H[=j;Jׁ*%&˓4b@Iwp`굨mi}QW9LjCq٭O$4KaLO|6,yYtA91r78CX׎kĺp2OMoSnfcaGm:QfQ˛"Y6=4ڇZ|]oenB7'\B%&rak-PXb7uw8+y:!'_X r%e=+_Pq[= @v=ɤXuq~czj}9]jB5x3Q1rlϋc-]-؇u5u:`z*,,d{Qc˼ ?o);       @VsiBJbСhАa*>H7UUUԱcG 0m".dcz1 |XçOi)/gl4+w}&[p؇n}CK.QES,[_WK6|h=T+, ǡiT^\Iѝlڶiݩ*ﲗKzӌVJ%Bhm17]*e#cD]TFȮ\r|lQ|[5dm{_Z$r8' 4{AݸF0e=e++S_.{y'muhtnL9&ejv)DMq{@[>}iVOTZ+%cTB_( }tᴵi͕BHw-%GJ% [lr:xΫ+c>BZ[pEh اQA_ $/=ÊCvCVpm_|^L>z' Ǥu [K21_7ha%/o[NKoKV=7[1a nQRk0j\?]ϋz<:o>jZvFVZP)M*J[_~!]ԉ\ƅ*YFGs 2iVr:WO3y;.FFoئ=j8ds'L1^׭ɣMJD<+cOTo6        6vIIC}cЕC=kԠ DDtҁm[eUTSX?\+ւ]㒥`mM;mr9Zqs8&,:"N[k|5GVԱ1 PVPޙK͆Au&ݻq *Pa]d Zis5šǙSgm !(ML/em:b ƀVV\Z#u=1r\?xD%vq)JE{b My]245kۚ|hѩTxxӿ婨.e0ZU7KϙLmF{;.FFa GPP0wa2=mFڴa5z é3i))7xN/;@@@@@@@(osP     `,=-HW~^ظ=GzptO;r^3TTX`Nx@D DC ^`#祺]ǦӤL `ꟃq1~\x8uxnUaӄ+ 眥\F˗,FC{       1(=F LcM{08߃4&C QS㙢ƖIk D>'kW/8W        (ݡ TTRFjkei?bLQc'i咖':M c_6;0ø?.`j([6,] &  m+DWp->@4&N Ė>?H Da*h@l#@4&N _@KTt EFEˮ<~\w2(04|uuT_~n7            M>onǎ݃=h[s.OӑCܮ#бs4 N@Ν4b           ML &`TrnVЄSiԙ.[eٷ|SjFGw!           >&V?att1:.]-SϻHaa!4nh:KYP 6(M@-]>0h]=z A=}8} { yQBݟv|M &_qnҕ+W &4@ JpG`p9T^rɜ;. 1*c7RMU9me)NP6ʊi78#MɤD>c>nu^{:MD2(}S8cNL;           @Dcܻ/%$&ֶlZvluf2*..e9bPm0Jx}ڒLnsbCbDT"m{)E9[acT~R1X_WKhEQhx$m]n$" ")؇6YIT߂ pCw%c&*S+GwPEi9(@}ڶl`P<̵ՕTI`P06MRU[K D&k aK5q0\RX)%.Kۇ+&3m8#,{N <:wb7;Lu5c_g)27Q}_j>P 15}hk}_RYa.TWRԾc8&ՑmiHCn5M.h=ZϥJ@|CXi رs45{6g\eieTMVB4eeq6ӛ7JWO*.RiQnxjãա۔1d':w P ĕ47+WB4WW{I 5 h@@@@@@J4;A br^QT!@iۿמL2LB g]3߬@@@@laze=hg{D&?Ӈ0DS(a \!1 H0Dsa<4Q>YN Mt@&i?L@>J@iM        @IDAT    MQu+ݫ)f*.ȹr4:`ͅ;ԣL0X۪d@3&@@@@@@@@@@@@@\%             Ќ @@lo.            h.1fL G&E %uԕ||[;{̩Mj، <#[SyE9QfF:8vr8V:@@A,CÓ%ھJ&N~`>-,,U@+|}ܩ6n?-zzА~Ѳ~B:ĦشMSه56I#FY^{_t~v܀ @@5q@ ȕϞ9M&ݝUz.Pvveef& h,-ZCϜ:v SoYw>2R.%ѫd6ӻ_nՆ%:>,q6Z&ߠwl+UV+9zR^%NOɵ&?{3Ʌ)<5фA@@@@@@@@@@ ]?f jI,Ɯ>YCZѐmzK<9#=vmBG4Ww_}D,_b&J-AJnl*h! L~B)V= ݦNo=mLO%y)-ڵJwe?2hq<'J;           ژzNxn\א89!wZVZ&eJ%sus]P'l|", ` 2aZj)1yŲM^Au=7HkJSهئ()ݽ~Yywo?'ǖSAN6WojEcnJ Mui0E.Dhlm w.4%'%ԹzTphTVVkAgJ4rx*T@iQmRXD{-r I96nPN+Uu{>^k&/FFϱ?J nӝ-rA, GJ+X~5k"iGj"IlT! m&'%muCH(۹y9 @#ykɬ)bafvhXXTJ%oz Vֱ>&LNw;`H=v)Q݆Q`xih/L22{[#rǥGoΫ-uѶ깈=[P ꈫ>wyxhF7 iZWM9\9Y"™*}u8zaDrqv!5+ON~=#)>)^}mn^Y_*C۔P˨rK{4<:䬎))W*eW~^qLhlSc酪?d&01l-uYma cƫavV&<SHh8Mfu}VeŢ\CZvl֜Ujj;(ESJ56>j#=83c -_sNKQL+++7VPa2!Pl4}x|: r20rgI         V$ъp)<2JޚP[5$4zx}=s6ih]HW׉=C{vn>ZU-]0O,Yfsrl" `xsս*.ѪGIw,y'Mݕu ?5f=5`FO?4NNy>u:![ ݇sgNQEyڔxQۢڮ7-kbCSٙ!b4y`fNkV-?3i.5@@6އ1/{9hNy,!((潼}膛f=egfҦ)]M D(۾exlT-Z}[y{uv4YyM =Zs㞾A4gd$?9{=ԣw}t˜{byzH }kp"A@@@@@@@@@J Z b64,hBj>pkT]DO5{.yzyQaA-;*,,PlFeoo_ޱ{}qؔJ`PY0oRϜOOB5J iyRH}|OesBq)O8BǏKI f`"i΀!eŸXJLh`yMGm-/ͣі&PVVw0ࠎFŷ=f.ϧ_~ڬ9 w=9eq4(<5 Fnq}{eZ?j[sw$޷k*ɠBZxBKO8C|<  R4dsUmb         WV|O4 S9d#Ƿ!ۼ/}Mքpx+8*k_)F[G[={Ʀ[~4F|?D..>n =vhwPZ9hӽz6]OOӨG'^ݵ# h%_ `[sĄ 2?fTvkN[h XQQSк+ `IW, M~4pʤ &C=W6rgcNla!-虇4Q]B|4OLg n p97'=Gc[?nK\i) s}Au6"`  P'vK{684rO7t+zV'w7gO}N_YY9q( j@7 !p9=M.<'Ya@4yչΆu_( $'%пqrwVZ,B3_8O,0r6apS?a?N?,Bfb5[^Xi[qu䀗~oRssҤX>@}@@50J bN0v/?YCyy9-Z◫U{yO>O#F'{{@W-@@@@@@@@@ ;fEGϞ^C\}X8Jˏ^}0pb&GM-@#3A.KnnB<8S-_?YN"uvv1          `m$P"BUN@ 9@@@@@@@@,0mo 6            '̱"4Y[ @@=s            M&`c             `{m+@%ɾ5؞Ċ   %`ggO:tt^JMInIh42*i<3kN6m ϕRnN>4^wB"(IJWC[F$h@wshorT\^fX@q t4Jm +GR+]CZ#mfz{RA unjlsIj.ھrkEM28&_̭M@+|}ܩ6n?-mxzА~Ѳ~B:ĦmƆҶM_qbCy@7_ߏ @J}wˇ^3VӞʣsמ4O?LJNJ0aւ        `SR@GnG#ZKkcNEcXD KFutt_h6bvW+ ܫ}ѽ_5N=MvvS M)++WF-)0(A|ϋLqiiy͹ٙ"ڐϩؕw_}dJQeU.GUxK)+Y@o<%ޗnCUZn:~&^?-=#Ot hI4y,rqq{p.)6LYc@@@@@@@@ן;MNzdF '@ DN",Y} \P 11]&Ow{A ؃{A9}V{zyO5KhAr¤"_~v޸Gt]!,ng(w\^^NɉG[J/k&LVy:"OuNp:sӦE$.y՟yܼ"l&5aKh#Br)(,}KEŦ f&lfC,B9Eu|K0y1 )f+ _O-”MMȍxm}z}nq\;{0?~<<=G:t+?CH;iOsx " {qcu<oa~^tbL)-!P^gIZs)-s!{1}Gz/T^ɉ3oSz>#$ov}S3BFVNs4mܡ[{#:kT*W.{66ya*>juٗ i@@@@@@@ihMwaL)Z{CMt{i6x+o~,CP@/o y֮0+;uC}_0uIvAnn4|5rarOxȍ,*.]r+0@}Sӭf"ffWETR~kFYasjߒ<59dKLkl=Β籄9       !l= t)/=ukJQ~'٪pX7.E)އr?8o|@## O1C"տ;u5 sri?S&PI'T&(;R0ƅ}pk6~\=ύ?9KϤ5n ԽS+:w!y^}Y!<DZk=b,]:J:Qa~JMϥGM{V4{z)<Չ J(!9vMɩ?sٖ%l4y{e?"۴TLZ†ka 4kBx:}3 ][ YGԾʛeI9G˯@-CBygj=0D M8uڸKR]& gP*у{lO$ z65]q\[aaǞVs)*c|ۗڶHc'L9z@%ۥ҂ߠzbRk}snP۔J=W!$.? kiSvVYW!{^Ұnrj+Vrrr"O/ojӶ7v=Qe}<)E7^9.9k&ҳX†._r^~Y.En= =RuųK*vDS) j3U:FKq^|{vZ0C-<͏RkV_>qpPLH3'Q?O3S 3&W5FŐ#qnC1{:z=,aU8(We/Pj_9>{gp[,a51@@@@@@@zV0IGB@_ղ}x@xƜ>SiA/ߵw6z?MW0f>)yQDN4`FҌ|rlw9@\?y/夤Sey=M)+˃3xcNBsBYILwMސ|&CbӔss,žVarX%_Jq!)D 2WTThܣ͐ p~pnKAa#Ӌ,vCfh 7,yG0E~Q䳯O#cOXPnבT{αI?~[򽘳rrĞkN.EA_~>d[FzQQNo8"wby*{}weJ*d&%CBT]BdŒb)v]nn4}mW;%l4ZtxByAA>gֹ\j,pQ<F*]׈:2n@()r)z׆CX3hh?7Ж>*[I/rrRmJ@4TX<~R<,,*?@'PFf> :Ҝˍ^[p=MKP6b=Y@T76LYc@@@@@@@O .qf 3ėkvYK^ԥ[Oisom!{mڰ ̌t=[o&UŪٌD(&!a^x"7`Ex u2]DK{NAV@?USLdôBxx(+B-۷&W!P\9v]];(hA&TX)Y'5dwѪ^xJ+UƱK9Dsmut;ݧ5ETr9u E|KI"sƓ3Ezb݄&t<*/C*CG5( rO.JCȳX,z4Y׆Dmuhҭ^ٻ \10OHXruu бrp?,aIr9鰪%%CswM),ٹk|)ּF'uænqZKK+׸ς೏L _64mڡzMG.!&Ʋ/ }ΑR@^ٮE+1f/ 2?7( ruX]@q\^K8_ \ oB .-^U>yz{6P 8*r*MN~>ܱ!:mAT,~}'6F"x!n1! jX) *,yu!)Y >Hۂn鶻ԉ#2/MY pQG-a>b 4{D'3A3T*ͫN7e6%U/r1K ъ~,]溊'[.g61nƒBT?])P1L.x"&ПXp5Zr=;wAoYEκקݐIɘJͧ]J}څdUnm'{:;V&i97R6l;E%LcvPO _}y4zpd/ mɳjXKжg麽泱L83 3p+hȨv-5T&^Ky ֦m/yQq멭9 Z2lJT#_^u /c3S u٤oJ=}|E%v}z_˖.}ZzI1! Ek+ ❡RQ%vx!vQ<۸ m!CyTkMRgB=>NcO`4r\ 3UV-Kf̖yv946{k{~G?`1V,aC۶ϩ{QVwmc>c]¢RZMГ NNtJ×ֵmOY_:+.;_lKذɕ_ Mola6A@@@@@@lK@ ۮoզ|\SnjuY}ڌ[G/-bԀ 祀.p4؋O{GSnijO{kK֯ [_ОBZ>p/|gO^l]' Uy>䴭r(M.v:Q5p@.3゘b3-RUp,6f|(UhQ~^Ζ{fr>#{S}GFE y~A-aC٧ϩOY\_{X kBfe"+ܟMeGʪr9JKu4-a]6WE 1]kQu1@@@@@@@[glnՍG5n)}jb^}GǬcW7>4Rzz*}:L)챯1eƀ5xjKWH V`tPB83̛B!n)o}7%ߠW=k$uU!O3/sJOK+g3X4U4c& lwEï?{qT{25wC'RT< ˜{iߍTֱ sS%_HG1M:KiEFvW—nquC6c/ g)o갻Fz{i^Uc ,]'Ϊ#dumZFm       `; nVYшiʯ ggŵr(^}꼺譎ԥ,)ArZ~K}la4 r6.|FGJik\퐜uyhֶ ;84aj z*_(F #:ZYEC"d!?U7!#=M j3d rmڶ7:?oy.Ֆkd:ʐx&"jۇCZ sp1>Mi- ճg 5"66}ᑭrŔJZYt5E3T_zżZMNՄC e#I)Jz ՘*Uq}9       'Ь=vf×+-܃;~OWdZ/t)\P@1 Q zh>e{ƅDPZXD"HgG~>>nv”0YQ.-~FGP\=cQ SE1 '77)3T9o ܵ8vXoΠz2KLn9u\[sP nJUh^w-Dzu"sÑ7;+O.@U1$Z†b_*)cmN4bx 6ʊu鮛St˴~LWbi߱ uc?WK9}.E_H T}pq{|,aC۞%!q;Yb Ys@@@@@@@Cz FQ1M|+^co_>5_Z.yZٿe:M"'suB/W+-!YLfQT?]>Ck#8-]qt'7wWPs;e}(*'RaN;k 8&~J[?#oY?*z?QLn\eEH<~D^^~hO뜛f.E3*%8[E9W?ny%fQFRhv%br$W!x3fN&'%{E oATSuw|Cq7y,ba-RƿP湘SYv&;}Y >,)?&1)U)R}!{EJ(g %FM/sڣߒn=~Py9 ڼ}DԐ! V_F닏ֳs`.ɉC>&qv*ۼ)rE1_)PC|B?*e}K]YT>-(,"JϪޣdo^m߲J#:auͪe" C}Ny=ªy\}jm_^yO#*gf}j ЩUwn⦰-~L9r]oOW鉨%+ݗ%l5^!#?~QYKبk ؞]DDJk.{gU]>X3p8:7_7{RA[}R& VWkjhó);9>1O>-‚3ىEyH<2Ew2쩤 r7" Ή'I vn$R, []=.+-!9y9`OCX| E-X|LMIV0Cs|l䐥{133,,<,#ڢɞ"6?Qgx28 og*zVgo@S=B-aCa֐甽IYkE(E-Å|sg8shp6xzm1ڿ~7 >VBv(\? ϓ(-#d$abF^#y z6Y†j v@IDATh>lCZt#dOKI[ `̄K/*cET~շ;oHO?: 8_ŸRL.cϷ/WCXbnZj8OJL!m'nhi )ߐ素 bNPL5N|Tί?}GE{:|{gwr}KDdkUi Tޣ          PϮ3?@?{4UVVm6^@*!LvvvuJJ!0{UË}Jh Wvt)0%xsd#٨Dz6$@]N7RFYL4SP:׼x+u]c:y@h㷠CROz-I `Cv6\R'_.5)ZˊK8;˼E          Pomw!!rM> w pu(+Ĕ8L4B[9ޮޣ         &цī=X6lxfO`uyI>B%H'vґK0 CQ]/{JżEpk9V|v_jO!ݛ " &ߠrrqT{2/?QC* 9KxԶX*Ϧ]+>LfQ!S?T_)ᐤ-[w΃*!Kqs 'Cmʉ+(%N[S>yvnG(۞T+7:~rNy\!A:.%E/}HnP ёn"N7 bGzpZB7m‰"o:Fg vѾ%M äу{l1Q b/ ,~=dgocI2 FKFN7 YpM{q"j[,+R+_xNmܥg          @@;4dKg`bBVz-Izim/H񰼬w`Q~Dlcuqwݴeld^ݫ>QQ7ѩ](#YBm;/3en վvǵ6\6Lk>JG L?Ee%4ͳyRvm(:~1pUz9?,<GMK!zfZ81f:~wT:H6ǂ'|HЊ F\8Ʌߟ{GG d&>{?zKQF b::2.$хǩtu;V'imv(C&޴N^ ]N]eGE;JALj qbx_1G%L5& )jDŽc.qO@ QV&B)M!rk"G92EZ.{`h,M:slÚFV~dž -W=+4އ.i 殉pvTt6hUDSժ_ݿn!EM'ɝ+≝Z\DO.9Z,EyF'g7}OH`Ϻ-7|®]~#ː^RGWoG9'Y&hJ=}&ʹ X)1%2U{Z'4Bigh,"GsxCcXeNCR+)]F+D+c!ݮ\aZ60&JIEYu,w;8:CX!M~7{XAsI[,ys+p>Գs8T錷!IdB*v5{-_x}:9wJgOJy>}6u*c @@@@@@@@@t2\ZK 뵒%lkaL ~\r/_jkP!uϯ6CXbɳ؉}V59ʚmN}}n^Ӯ.S U*+d}UKCcj(<9)h])R1&jw<ʏ&^7F~]=D8dڽS酸WhֳɿU;7ڵscvZܳ ^gy(aK^-yk,ߦ30^d&ʫ?JٳyّaM % q_܅S`t " s{-nWaBDGOIM~֣#%>7}fLNcSIA-}u)o0r^Ar.4k j(-sUקּ_hfl%ۙiۡ}(G'la}M.v>ޒ5G~j%hO{T@@@@@@@@@@8;;}8ػꉎ9$?zHmS*rOcP#nzoՕOZFŅFEnNM?F%biod{qseTMV\%fo!G'ܼh w#6ڂ2RgQ z&-05mS*TTb)3IZ7n>^6 JRTnk[_3#.v~l0{ t64._Lu\ߙג:ֽ77Gs/[mQhW=q<אmi]3(K;*7*s7K=ܡ5M~NQ(}gM"ߵg˻:+P3l+e1tOOyiicsmQn>!>!A4[e[(v'VwQT]>FHH zH J""QQł JUzFzos73;ےlI6]eowޙݍ9_R>D~X8FR?r(zhaA2XF5ԤEI)L.B!݋Vh3ӄ6gC/9}lKQGOeKӛwdžd%y󲳉c ¨xC56xļW z_}~19:j35݅XB8V2y 7Zvg?ϛN|588IJ!t.>E7\GU"muk`#V<,u?ܤWyMy[rgvvG1Y<@-YC}na9ߩ1}pllIe(OMϒ ![9_Z\1 .?&88F_0(I+/K+f~N[[rdFDTCq`ZsQ         *EKGNd5ڀL$dVu߃tRb7HXU,M$J1 er"|j ΃r>"0OX7XZMLՁrj5!EH-k~]BLXX Da(XS+c?vYf̡JF-1y⣯\('=CX#Rrt\iєG_ $D5s&\^zyق8,:,9kJ@8EƪNNccLO@@@@@@@@@*@i90so" įN,i-K[Kr%W#n1%sUڔx9U$- $O%_>;&v|r5(o˛8;&юO            Mbyz             `xo@%!t`Odئ5vv/Za V%+ܧ%27n B ބ'          @@@@@@@@@@@@n)oӍ @@,ZA@@@@@@@@@@@@":8X(chxyW^P- }{t[ Nw3.iݚk'6@@@@@@@@$<8*(W wwOk>Wb PY M 5UN[7U˕1ӳOJrrrR& +OU_l:NOFEc_5pswW{Ѡ{GޥCq        P VtWC| qzpppU>UyyyEUBh9f<֌8zλ3*ȟPrusM &7/O-дEkG]\Ļ '&QJZRuGd>2A}" H-c94,\zqX:}̈́        vChUnzl 3~7NЩNJY̡[LMC@{AM M<2A M:N7{XgoZs:s$w?X! !Z[/G t&VFci-RSʥ MիQx:6nڂ:w뉛*ʓ aiw|iZWuqTԱKwuX!HNTi$>P h[\Hu&ZJnށ THn,WܼxևMXpJMIt? 'Lijc4%m}4 QEv4}T s#j޲-n^tn [/ u6A \5_TXT;77uGwXOU@l6rrv69@@@@@@@@JGbY5Zo}xև"hdffX:@e#7o[F5k:OOorvqLJNJqNɘloxhK/$,\~~rssK ): ;|:OP.RS.0)}`.D!ᲯOu_1,T ,ηZ뒗W5#33щh?&r~S7߭L~&xCX40SJKKUw~kИjJW)K|/E_"5ˉCK9ۅ&[ic[ oiQZr,$:{$]\̥R#a5ʩNd} _RJ?M,  EqdPgPkUB\q1ҭcG\9-cu~gT~#9.6ƪ(vOONNUI)ߪdoVbAHAfPԾe,]H_.LQ:RO=E,G{]2B-8;ӝQF.Ktt4-]}n91b}.'c EOFu5 ##jjm'kʝ֡=!џЇZ5I)Y<ֵI}),؇cS$_+<\7X ф9|Kb1Riߡtl8m4ׇvm.k֬%?JA@@@@@@@JObkZuRHXytRb ffPa@Qɉ<)fumݴUf@ ͘5OϚ2ygs>զXm^OgOŅΩ\PU'6TN߻R$3GD,h ]8F }脌Bl4~bNSǟz:~ ~;! zLT&pWW7]6iN=@-K#1rmba/^CE keF(S:̯+"֧ʸ) ZgQ]{wSϼB#)ېYu~ԸuvdXWEڔIrbYuJda֦YM6vL 5"ݽ){h5m"}:\.挡MڜՉ!ŵ鿙dxP`56@ TM|H        #`0Sa:w><{ZX^\XW6Ņ)[MКB "ǂ&P SLCi,F5oACM^0+*B#[=TQ\tR%߯ Wcm۹m5Ag@ uaw;d˒O?%U?l@ku,8s vJvW˼f4+y=h漕mFJ{}h^WyXrvI>zu8W?I}uVPRUX$ƀTbrT68<Ǣ]2flkxrv ׉x|MpGNA!7wwJV,"3BJGSIخNһUͽ?b8p{gKiLo-E|?4MǕB\v+tũvx !c#rs<)T_iƜ唗oܥYٹP'丆 TD^?vRL\ -s* rٝk0g?[|2qK')[*r⸊TyJێ<@@@,"GbE짍}bĀ!:_~<Ǣn~[JMI SPQ79[Eܯ,6o(rOFj6\؇l$1(X֐]}n۲^feZD_N\>/ K"C7jRdB[)S>I- D~9X"gg9 [oeJwW77U k[Oa ߋVD$%&ʆr8k[w[\DZyUuEjR`1>͹ eKD~-!@&*W8 kBNa[|:w#Z3DPۮmq}(s)99<ڼamt>) zy{KlfuR X$R"q9=ݲ.&vd5c3hyoASHA q3\fA E W>v7j) _rԞiIuIO+nݣRW)"~,j U: 4W2_RJN5M̡$[̡eer)kE*F+*m\PbE* WIZW_>T{fNu!g#:w3]<\hYMaEVa7MG*[*Mɚ-lshEI\pYxz| j8eMмF*m|7eʖZ 1#q{E׭,al.[h^sXBz&ׇioI{Z7>6pkeY/j(s >%j S𻻛9[̡]K/L+|c մlgϳwQx:^B+@q @@,.Snna2Ak)ov0G9\lA8"TfYҒI/j.Ӻ,׻ƅpzշ(.v) !껯>͸{e|AbDDƂZ SS{$        h=3Gxzy,MR_s|t7Yޜ6u `bmq f-=;:%ӼU;%+ߵbcs8::Q=Ъm&uJ?Onnz9~'nV7jܜf̚k2fԽwYtuՠdfn=7A|]80'vd=Y+tqܣW_:w'mXJ鮾Q@W7qVWc3t긷^{Nl},AW#⯙cl>X-,i-:z)fKz^/_8Gv9uHhͲX9ÂSP7EǚV/G_JTfԦ;ϨuJS:JV_Mm1vVpRAtEϔI;SCqxr9r>AZ. @@@@@@@@ doV=Qae)3CoeU0\}spJvkqRJM 93ptJLWY7^̮{cc#OӻoN>}B-[Wӊ'+yjqQWJUfq秳a(OۓNx}Fhg?)9D[̡n3,YeiL\ َՙr@ܭ[˟ޞ:\uJǎrCkFe%kq/e-)uˉcG, kf_Z߻[y Km`vT@Y`k)tǞ|7Ѡϋ Pg5"ǨnFOo~"_~¥bu+k P}LgO),Vr!S?H3H!Ҝ҃dlw-OI7ᐓ@}6?z]|4oFϾ=; UIϾ9-U}j"4Ydfgq Bذv* x+oµ'6'XԃnwmX6Za\^|O_ٝ4!k;C;-4eP穉ؠQ3? +Uܵŵn0 G.uYh닯}+4t[tl,]Vk/\NǮPF:CiOI1!)]Z56 $GǪ긿;LׯP˶C3VZ}Ə"A>/OC(>1<=\"rM;N˾Oe's?( Ǡ B}BKFHsgNW^C媓@IDATuSܲȧ_ςiޣ7DXjظJ?-J-+O?zNEJ,<C8)8~|Xbᅨ}*!v5vnDwl1hcgO233D7%.MkB,ڲ?5ߕ˨K^k Q3Р-ԗXe&'>m< Z+Iv.oٴV]A6]qr,[Jlij׺6(KS'VF=B f.lƉjwB+M?Ln.ԺYM^kf9L&*ºg), 9Xaͥ7u[OkyTxu[֫kj%2        &`xa[ػ{ܿ"CkW~Z`1+zGi(;ԝ/oN'=Jiqиؑ4qڋ-c~U^,8ep73%'%&#s|\Ɖ1j؇+^ev 7\~f2~fTwǬ|)fcEXu=^ju8 x4I2v|giׯhjǖ6ok{07~F 啢DGAZM '/ufbC}I̬\v`fs/)9%Ӥ-0N*bzF6 }kVaunH“hJ_zmս3Oy6Ӏh T omQSԙ̛摱gcz쥦~ҍXr:>;j-۝lv"ü-&R\a쳢Vd=Xq:wIM{C:rp/b[۱'dQ" |]'[e(Z{,<~^1EskzJ+19G F9\?~jms14ȇf4^,h9nv-"RBR:D+١{GY-'(F*;w! {{Cw~:UȧTιkԤlotv(/TJU{OǬAժUiéA&rK4 cJ ##ޮ9Fd?"5TÓǏ?KOPfz:Uqprl @zx$)¢Q\lt{qC(xamMKϦ;NLKaul1GaW6?/1 pN)b-AM?Nu_?=}zpn*{ݷvc?XOU==燏e_{giJ&DLxHzDŽ=@e"VFxn`cw(]P֬VG墮\_yяB8w攊";+K͗g&L,唕I0rrsy+\z B?jn42Դe{r<={4ul1GaMʤb;LZ8<]i뮳%^sxq [a`>grv+_)OoE_|$SQԽgw2;6     r>n9ubY,#̓c  _xBCv9ܡS7e)xR3帤]]v?@ժ޷-(&0SbPk~Q)~ݯooҹS5tj*ڽܜ}`1;(o.Yc`     7?xoH:AczLu7ׯ_B')c)|M) |MOu+j/X 3r)'7Iij[REþE(EN8S'g _Sej8JK&s%Y2Y/) 6oVzu?\U {{%όևڶf-[gK/xX筬נ1p/ժ.-,L KZhG~Թ[OI'dƊzyiy?~9$'!u sio =ӲM{%rw3BEnO1W_C"qjFV"62f 2VK{^իHcIɺcZ D$ŀ^g=4RzAJlKhӟ)3+ǚmUU{;=}ZPp@5rLeSQhڼh"u6-"ypuvHv)Κi$Ze"rz쁮s淿# `9IKs,M f[Iռ(V rwu&7Z M}:].#o&.;xѹ=<#խO.$lFھ\5oJ׃\ mwb 5=jFWgjW=3'U'iᒝ*§iPUq-.[s6l׻}V;2cRn&}{nwH;Ë9"lrbMX#8}7lB Pۜeaw2☹Rۺҝ} o><#egeRlLm\V,Q.,yzzQߺm q<&9).]8O9Pؕv,noPh6w~x#j^ΠͿ@ԼT\ܤКtfNMPFJⴡ9cfo ^I|P]V4~s}U@ټL@:ڽp~ߋ#jEIt~zYKA:1GPp|hՎf|l4qۃf/?kQ@,y1>~EԼxԺqY Rޕv( K#p J_^dI`+g]i:7=4q20_)DrfvVMn:B8AO|CJ΢mԲѩUw;oV@HiEhh<){c8ulUjP'PF\Og0`_֡_=lv+,@bY9RjԬavtA[H`Q]h|,!:x~u.[f ]J~爿Zzُ=8d\YTlۼ]]]iShvri7V5iNC?@n4dh"|sVIIo6غQ \y174,BVosR, +]V]@o|Nܼ|IjNcGvzhhor&`,.^V?JQ1Rm'?E2!2Z,,͚zٍ?lmR wvkDlV+̏|vz}rps](..NkqqN|UW/+?GNF)Yw[ac1ؔ(0>K7/zu_y=CK(wItuq6#kBg?YW'![Re84ڐzUr8]g/E8nP7Pܱvx b)#9K1RVî9Y+hR  S}_",[dgKQи;!¢7E+OߦgO F="QW/ן!~]eC*ћodwZ4}|*X,~o[~q?n3wn>`Gf-WgJȡ}4kKe3*&;ߖSDYYjdu[a~^ݿRE,|*`&C^wϪ񝔕$-AMo|©]߱qѻmU      P*KxfR͈Z=<76]CvаFt :~]n(ަЫ NמJM7SRiٚvd=:w]w!  SI/}wм~7YzgwkZk~~MZktus&왯X摗Gc =e f^`&cb<-S2Mg.Ct7k2)U'0*w~bQ \$^ZOGS]!vX *Z}ss6o OM;NKl=XhL_EÙMkJ+EοjZt5ڶ^ ]բ" -X9tQw[ac1$[ww.^'/0qie ݸ4Mzm[rpwJ[>;ݕrB?욖,OדFR#ةh౫RbQND U@d׮3CC!(;|I"s)ł[As+E˫ 9[/SV;Q/]<',s⿙%v-$wvT7_SlWw~ڻk2JgOPaXW]GB\4W5ʰǂ_M,囯='F6e"~u<\=;uত$ɭCI1诹(-ѲK|AuS5gw_lx.baeT]V RVȀ؜D#5j}x^8dn+[*,XRMMĺ6 ;x77]ECH˗fp_ٴ˾5MD_imq^w.8e^>,PL&–IQwzX6$ zfO|#UI,ݣd b]m2EvhX:OQ\, ˰&VTD]YK͐#3X^Ǹf?:C$DN^&"ǕV<,Z&=CZlRS31##i rf 'D.+"tX^ Z4rsvua$ZQRaȯL/5/œ>qT ~\b CӲ%J[Sdݳ'j9G>\vIz)f9gS 8>UT{dJ:wakƽGGd˳C/?cQ@ojPb|xA=A>&HX t)*ɢKWhĂezSY u(߾kx3`X崳@3n2[/kAwQg,BHyl X.-pHfp$@31+i릵Į3Y~GIl.9~ȇqbKCs) ! N\⇁MwޞK^s4cdJOK+pW' 6gշ*XĢ`^΂ZVhI&CO"&bqDŲÂűf dA@@@@@JIb)5s;dseaQcȮJ2322K~l׌hB>+Q)9I/iRbv+6xLJrʔ23[<[1{Xq(e[Gx Tމ]2>@W b{X/!pKиxwOSZ[7zH{'b9BTVV҄% [KZkTWV*,"/ ۿ@䲓@+\:sp-9)e?p[vSV %~1p9"|| t~+]^s,H,;bw"0&&$>,iƬTOxJ_$\[{@go7=1渇坜]ub:anL\NF :>/wQwA@@@@@JAb)5-"՗6oX[TwNVV[ǜxȃb(ZF@`V+6N. ŝǤB,8ReJKyGiϋ=E Hrv1ekS}[)sElTJJ$)kxRp@5[.is)L9Faa?NC89 QS~4&eӤ*X݅+?QSpJs52z}^19E9Q|U L~=:.o D彨7[~FzԤYKx-ZN$Riߞez&%vآYU<\d'}fJlG{bJ3cVKvtYj8$"杧MW,1R**feré;8y&V FnB l*>nDb!qpRXt)ٕd+ԹX =nwXV$˹e cR+኿>7q:l#tc-)łbb6'΢YsnS /<=*XOn1aҭN٩iMl\'2Nvt.9>䔚Mm/_q3D([ːWA 3lC+i~(W,[bOyWdШ)(U43lU6= 96eg O񖸿g!m(y1Qq#sYhOY5 ӲhG+ ".⭒cVAf Ɖ]QrJ/ԸRocxɻx^LVAemqbaT;O+ bSG &"."jX-*sXzgE޵4fgCע)/L:uӪq:uT_?4jۣ 5 ~uKrB@s(35J}륤Iլ ͳp'yVqn(q;%^A@@@@@TUeO[#8)8NaMg+ḱ%p-.D֧ڑv!a=.VwZVmi1"5[+M|S՚V^^z"ꕋLQ;KQ6(H*V,Y9q[-Uv#_O̥uukLwtAIjac1Dz2EŦPc!5,t_VƉ Y(x%R ~YHdᐅŌ,kGO/j[q|<}6zo|⿕Bc"j:?.͌QqAY?[PSlIX,lYZK;ȡ4wK3/2ޜ\jUOMMkB\[5Qt=Laqvo?}d&m~zǝ}UEG?+t\ndbJU;U tn0ٺK^@ Զb=҂߶5?WNϛ>qHv`2ǘXw3?3#3Gg&&h*J:EJ=!bҀډqAbM^].鉳bbQ 2 5,<]/`aMSdׁwʻK;S26G6)!2CzL5f?Ozk;"DL3Sݯ.=̋*~5Y˶wq/K1X?Y?Gܼ_  @-۴'~o[/5ori5j7 HU\ܼ(;SW`NE|bYYG!u[L41M9YrZS@([lqS~ڻk}{c>|pO(u?Qߺ]!>Z8x,Dh]%yP-ggbEaNU:sdo(XPz鵷)$Tgiȕ|'>iit͗v@1k.I+_Ma: Wٱ 48n%F /nTxlq^!ݹcu 51n.rpɉnaN( W'nruD@6gRl1KX~}Nz;uȂ;-?~s:-'Լٲ q?N3[D:kEE0Se0oRqvwHKNɔH50MNNUEac1JTf1[v#TKCKHzuLڹT<>pD=jp{r֊ ܖ*Mtjd-f#|՗gq@5 K늽*U%Vߠ`(v3@ 6hThӮ/DJ.(ؐ^NJO-S0@Ե$'>&-uKbbf0&0-ޘ㛫 0'ԝ9u6]%oDFkH| qlL{Sq7PnΥvQsaw؅qZpq&ƻTط7i:{)KlYr1獻IyӎӔ#1!4uboJE@ /٭鏋w!-Ȉ4e\O,E#Vb|ͧg KqؕgjYvvhǓ ]26]\|yzPh..n6*|ߚXtܼY49b5/Wy,ڊ$sű;R_Hp?IROLBll)e坭SOgMzD$ߏ; *wޠ @IVӜݖwn!CitaGB|s&au^[vnD= ^_-VM]X>{woZ|I2v5ǹfq|V#zE<4+)1^h.b)(!^ ڴDp[fqמٛ'uf?_=M~!uqT]_bή4fjq IçDi/ /`9z~Xy 0vnluCym8,%q#CjRȺJ,^ϿNN~^^f{Ķ'0O[ӷ5߸dKDf([+yfRJ!gKONJnM7_cv+s^rsr*NL'[ϚJ{^̭ߕmRD4uWDiJAA좓Xa˿Ҿ×l~$X݄5W~x%~Z&媋K_7O]F*۹߬VuuybJk!iqCsX ceh{qwTPoYCs=g&ᑓF\\NNZ. 3@[6'D\).oVXB,^:vX`V5kSu_?"38'sfRbBlfkZR^p{i uue fhp!]%$ә +=!(,R+3'K&lv,x`b>!"fdP8OlȱJ;8{>̲p'\:[^JRx{Y쏭WS&V״Ş8&pPHp%*:߹\aDlmJ5ɂe.99^f>]V>>r{|՟yCƂl+Z4^!ׅ~ge`1 Y*dJ>G7Hmm "pYFYiI (u)>epwRx:rY~Er$ V ӢM,^(_7sk!N<&\4)ؽR [-g>xg:"]βًJ1[ձ vr|jΛe1įҎWk8R}/:kmKe+=GVÝ7Ux޹3lr|/_[6rvt/ &o#.#=P}{vbΞOT1FC?P]V!ʁKp'r`%@@@@@@樠* #MSP⭳+SiJvB"=@~"b .[{*V #Ӱ1& G%#+@Xq2؄GNֆCYM۷8XC Z,=8Lgn8OGi>aî@@@@@@@D: XO ''V/KUTnL2A@@@@@@@@@@@ ܧI*7c#@#V鶎 ؚD[|             P @@'[[hk@@@@@@@@@@@@*1a             `kmM@%& VⓇ @@5Q            J|u5&@@@@@@@@@@@@@XO            &D1Tb+A@@@@@@@@@@@@ ښ(?{E6pM B bC .Xa~^{~ ,JB/s&̰ld6}=3s̙}    Xb=~x @@@@@@ Z-J      c1t@@@@@@ h(!      P @@@@@@Zբ      @= XCG@@@@@@jV      Xb=~x @@@@@@ Z-J      c1t@@@@@@ h(!      P @@@@@@Zբ      @= XCG@@@@@@jV      Xb=~x @@@@@@ Z-J      c1t@@@@@@ h(!      P @@@@@@Zբ      @= XCG@@@@Dո@IDAT@@j@;?h.uuxsG3E[֝xx@@@@@@"Z5o ;a}.Ï;@@@@@@  zC[ʓyK]mXi:nl+OD@@@@@ 3'Mk[.]3nl+OD@@@@@}c@@@@@@HbE2G@@@@@@z]rdAҧ[ki!aA/{ջoʁi3&*L.?PIڵi$[ I)Y}Ųb]b}E&st\B% _rr dTuWh}WGM=1}S8$/PdʒU+e5#     N @tFV L_28&PƄK-Ez'Z6_ZZhhGhHG&rƈ2o9k'=4߃%&:Lztn!Oξ-)**6[]yqW##B$u#?\rnӾF@@@@@, h9)V$дq<|ǙzPef^=kU5\9a'g_î0KW'?.}R\\"nce MT}˳dRPX$c#ѽKO3'_lmk-.|HiY"'n+MeW )}eu@@@@@L'*Hubvrbx ̧^4QSMW|MWHѷSҲe#kfMZtӶfUQفY7gfv[>y B4m Tl3MΔJGm:C:IO-#Rf-BUTV-8c?]#$?H9ݶqK:K?Kid?f     @>@TgۣģvC+~*z}{)@ӭ|b+#4eKaau}CZ\M͛F=ݲmTXz:WSjW@ӵXRdNc!     PG>@TCuI.kL;l 2wIa\K5ImZʓ[x"-`FQ}*#1L[C1 48YxTөlyHJJJ$00@}zyy2{FQӻRZYc4     A]=$~}Jve&dUzADTDGكF`FMYݢRaz}UF|8iUvke&⼥ktJN)n+jR ;~l_b< .     P>@T}SWfZR{jNU +h^~OcOMUrgʅg[lI#EE"Z\IHi49ΑEw }ۊNhM-[v](ZsEzqV@;Jmռh]Ng     E|q^` gE"cӄVg6**WVrzWזfɋoY0E6NYVRhv2x@;߳tL>xrگXjr`;T       y79dvUkQ '@Zt FGrGM&ҡCOkࡺ^.bmUx.yrߐ[ØzHQӮRj_wG@@@@@ r2uFN/**AҮM#Ci8/&:)Fzf5kSYUsewY &\ZVx;7C i1a^TmD @@@@@bT񌲥)֓Q2O fuݖc|9a\U](WzӭURR-rτmM)Dm~ɹ'ȟ_.3?EOɚg^g={B Ox @@@@@6|&"ԃ5Xʘ}iT ۯڨʖ?mwkfکhCuIȑewuJ<*۵T6h9t4C2حoXޜ~b_ +<%T4JÂƢ8g7wPuA}k\@@@@@(pz!pCDZϜfwgg/۷a~鷵zЮg&divC 76~uIzgȑL}:Ҧ$ 4ݬՒ}<8zj<<^2FRӲi(}S5Sʽ[עj/*WL,kce9%;'8؁     X-PyW?6Gy$-=GֲԚF0++Zl\3SY>MH}ZTj֩ԍ4u4A[ {A=gG&mJdXHrJ:WUAu[5k c#D/%;v5H|+uZ5u?ڔEeEޛ\a&ק܇     %PSzѨ wI_5iVm&+}I˕VMiAwtM      KLa@@@@@@n|;@@@@@@\ @@@@@@n|;@@@@@@\ @@@@@@n|;@@@@@@\ @@@@@@n|;@@@@@@\ @@@@@@n|;@@@@@@\t5XQ&2|Xܵ4h( YrX|5d䑧^B),,4IN:"ׯ"<|Ɔ     @GN:BŒKffF.#/%==.ar|tv)p}i֢eK ~|Hydڔr؁      @ԥ6SٺyO0a&"&ޛ,.~ŕ-k)솴6o' ]&l/%Nu@?/84Rl҂L9(iG8GF$%9!@@@@@@bҝAf-#wˎm%?/O39hmW"Νc^Šò[3{Vb[ve׺I\7xk_^5`#ټ}I-?R$,4LF=G.t688ĬSA@@@@@ +E $'59$/ɖXe\pz񺹳*2n}zP|ֻUv+gO~E>EIz~QAܬteI>WRR,99)Iڇ9l@@@@@\ DIY)- b3)vrL\) MN6vê?ali %,*Vo7d "y2%< 96ٹoP75aA~M@@@@@'@}\{%//WBBB%(8XZKb*β?2RrQINͲ?X[Vqe6׭Y!+.4kk4iEܲ8uf=vv}X;ɪ>G rЩy(avN@@@@@+@ѽ(Ɯ-O:U =hwO 9xxqݫ)+w!ut9n*ETo[eExBՀw'{}]潅FT@lۮ~7Dӂ       Fa??y߀)K>;eׁmIL0*:4$8|M<2L ԃ*Xqw\1@@@@@U~caZj|G dggIm .ˮNjxTM:pPz),(%q695ANII- ]{ʴ[F˕KWiIx'-h6:S=jfn\$6Z:]e'INFr}7md>91wRA@@@@@@t kiÆWWJrQjvle/jNwrO}zz[*0p5[nnYw2v)(,-;:/˙^][-o~_T0&:LMO+dɪnVgua玭ܵ_YJNe;jڶܠiG;uz#43ozA$=i,MnziKVqr㔻vC^|Q@@@@@j.@{PYv c֮rFܴq]Ǭ8g?ZїU}iŲbB=Q6֝O׎8<|ndepZ6]K]6]s,!arUO۟?~X9%"f/o.YifUٹ}z 1Zڶӛ~*c      P-jIU mRR*}t[&]~:__PM]3 N}7s_NSWhf.C[2FroKl!3^,)VeZóU6N@@@@@@jUUEppy^aAY/[)Xٶ޲Y:MjxxpL{x =uTbvo_OKKk}0ut&mJ۞'W_>xaMӠi\pҰy;ɔ_UR7>M.Fܵ&K9|HwK@@@@@\` S׼n]XXh9 lJrOuvV<4qO?gu[y:g9teI/l/?PsWBV5Ri2n+PC ]fgyβ%÷_      {*lz>k~󢢢+ Yq ((XzRW.N㒒mǵl9K.Uι2|H')..f ˔N Ȧmkw}99AS}*k) ; x&ilL9,}];c]Sd5%08Tr2S).g}~~      Y23%77GBC$} 3ڴmv&}\F~-NڴYsslG2Qi4F R|Dy7жF26_cc^_JxLc^R&8lg%rSRH>y8ERv֙G-[M6k)A@@@@@  ZXQ{cnricޑv^/7'G~cECqmۛcHLikJdD;*/3WB|๟dƇ7I֫kטtҧ@z|[涻*kl[x|d+f/IS~xTp脻]Gn߸Mӎ6qCziڪҶ]@@@@@9u b&އ^yNJUkwe2pN卍Ce8vn7\r@9y`iP|;țϓ;;Mn4T,,uv[!wxt@ܲlVQJ3%?.1[鯊:Vȴ#{+:ߵcy]f      W}gq0 >T"99ҤisQA5+I8 u\Hښ´uruu_j|2vDe!>x9$/s!=hdښ&ۯhx'|C];㙪N*      [Sw,W,1/iPh W|yNO{<  ,oJaaL}2!kQZxDDFFT@@@@@@}d VCwJxxjVϓ=w 0r'H4-;oղP sVUJJJ)Vٝ)WYWvzq UEjRTA' iߗ'b]DTdf_Ѯ      Pc5&t,ٶ_H$;=cxs;==-DTҌ]$@@@@@U2k ӻŷN]Z2""J$_Ɣ[ZᅯbmIafF$="6w     @ @C|.]*Sԋڔo#p     xSzC`      xDOy@@@@@@ !       )=I0@@@@@@<@<      @'8@@@@@@      "@S@@@@@@@ zC`      xDOy@@@@@@ !       )=I0@@@@@@<@g$:57T@l/lG*;c     g >PM @^TH@@@@@/bzFuja֩  دywI@@@@@/7nؾf  Pa]ö#N*     xZ22K՚fv05},PwXQ~      .7h{^#WN!^@AA@@@@@|&Q=̧^-|c="`P@@@@@|A2iA9_Ҧ) eԔKW'诏^\0     xՓT^@@@@@@j SSS@@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ >I@@@@@@ @@@@@@ K4!6if֩       88+Wq{v0Q@@@@@@@B+qn f嚷܍ Dӂ       V jgvi3w:Qq{ϝ[a۵Y       ֮١mD7<}(jiFITS:lh      S*p*>t)9s.3P?튾D(       hܞ.?MU      nPq9n iXuOv<5j*_T(#"%$V]"m@@@@@@j҅wVˋ=.Q      ^#P+Sz7      @!      DWh      @!      DWh      @!      DWh      @!      DWh      @!      DWh      @!      DWh      @!      DWh      @!      DWh      @!      DWh      @!      DWh      zq{T)$s d뒄*qGO;>@@@@@@X@ s :^ ?sGAQ۞R @y<><ӓ(B&=}>Ĥd2:y.     #|!O3P:luHRv*#KP}z^=9n6NmI^;BNuk{׶8     @e=;3xsG}#{wgtN񺹳*2Z].     *`YbUw݀!2Uu+eΎ-6P٨w(wm\#E6aGNFSf u)pnB@@@@@obf-d򉧘[inׇ-?-pj Dbˣ#Ɩ;M뤨-UR#Kfjv1OGm/A@@@@@p{1<(XsΖSP wV(;ӱѩtiTIH`dɑLY}p mIG1}g-?P\Yd2hy웍eávي6z"\,M#"N %?@QvA:[[GH@CFv"*xCLs&d棇-]Abv^bvjaAr#/nv:\'$/WV88sxG@@@@@p{SGK\LCsOJI2V@gMp8epX4^*5G?y_ H҂aki:xmTNlWT\,//8d{ mNc<wk^,5Ne՚=֤5Fp@hv Nn-hњ=hGfJ4-b~N4D ˏNd '뙍a;]GsF@@@@@(M '},6ܺQ*8rCeet-;v ,vuQa}LGk~@#5%Vث<7>-Pyk _Zuc-ۘfjSwVUԨ*KQYwH>ݾnMv}3)XU`T09;؆}B5JrN<:w,ܽSF628Dӂw|7g?d2ﮎl'ٲ@C٢uQ.M"9Ƴ-2&E uºv8Q7:r@@@@@Ol|ơRv׭t^seQ:6iw-Pdw+ճe_v@@@@@@k1:4Q5VSpJnaݹhӒhBwz.yz]:l}H㌼=\4;Ri:]#*$InauU#Yf;U1GvpeeN  n:maQӗn_(Y9^q     Z"mbʈckM3PhYB`m]Cۢb2K[n-h*D_Pe&Q_KQOX\X~}._(K$8:ҾQcW <;}ZѶڜg߶8lzAnl{]ƴ@  :극f&o]@@@@@|D|͢WYvM;3o^!>hv̨Cu=C4ӗmS5;wQ&P©fSWPF% `g '\)!ZvaP@z2`eYJM~ٲANk_]KeXeqo/SF,*..w }v-PFzڟVn'ر2Q2$QINz,t]U~ ^OƆ     -ѠtLwcSv}Bs޴]ݛ466Cnx:[ocw9;jk#t{ cYx2éҏ;u[8M}.hw'?\S)).5NS1 0}i}z@@@@@J@TvizӼGqQ1,50XEF{䨳/WE% _ܹ<S[#ܪ" B*} I5U ^Wֆo}o>e}$8,HEe R@@@@@@&)Lm1{tkL:7j>swrrm3Qӊ^;`eT̾f=S0I[028TNlV8iFOe,P5ӗMZU WUZ=eun8t@z6k_W2sM-$dkhҸjSeEaIqN`rhW4kHθi~k :@@@@@@85rr YKMv-#vW~{Bٙ|ܧ;uO`Ɵ n ѡz6gna>遌tHa\xB,^[?0r@@@@@iZ[ʇ2EjRFwjz *ZQL*D5=c=      P7:i]b&ͥMLCҿlh֩ `@Q]Og~@@@@@@ZNLT;3AX&,׵s6 Zv}:B@@@@@ wJ~"+n 5׻\V     ԁW6i&qL_Z+ߺdK\$&}[@@@@@:%^wWnO{S--E-iPn@l A)wֹӱ5zkVfJ9h09ē&-%%:us~QZ[Mv>#k'K eDdy\^v!ߗN]X3Y{iە% Uz=rjg``ti"NWDDJcZJC nߺaQgm \t$Ueί?g +V|w 9iio` ҂2>;Yciiljv\;If!͚; N6B^xY6F}'_а0c֘kԸ㧍+udEEe"^}íZa]XmѲh7?;nlx1Gџs@`X]lfS@Uwt~N~' ‹~ Gנa#ytKrԘ;aۃAA X]^a[O٢h!- )h[7m&õϟ-F vu@wߊ'|wПKݸ~ȳآCQARRLXQuOzVZ;VwXaꩿA?Q}#   RPP_4BˑÇH&x ŋ.F[/IbN),,+6Fλ2QZ#nj5͙rMSuŃj.56De\˝JII]7*cpʝC5D֭^!z&N2.Z}EՆ?U*x8h_rT3Pٌ{'Lҳ/}*+lcǴ n\Z}>yٶqWUz׎*\"?~YL F{~Ig\|^f-Dev< P=nU&T I梲1UMLrQc#xzI[Qen={UߪO7^((ׇ*8~7?gٞsM֫ݷ;nnhh\wT-x?_TF/0I϶Ê1ÕRzoW<@@@@ پW:qj 旟//6iɒs6e{euM~D٧ TeU EMgv]JtL-B ڽ?~uQSQeߞݲ~JQA:|^6kϛv'3h*?~FU3}-<Rk5ԗjZ\U;il=3,ւ55il{һ~j;I&2zo?*S    ,DA-Hj;eqi*lzr*BelU&*6X[GQ`P'88TqֲU0.-wّ{Uf**XY19B_-"2R+5Ź)jm2:!*FMF;6m:ujuj(jQU4#0YQfOMe ѫJUn }]_/xڱ|ywxwخVX}s0 w=X&=@@@@ob@(SCع: ۶Sӓ֭Y!v%={}H6<~Ϋj-|yw_ A޾[ajXt4 wݘbMt    T۶]G}j="X-斮YUQ*2%˪SJEЫȨq:ZkS<ʎߞH^Err6uvZj#m ,Ƴ3f|Nh*ko yWZ{ȥW 8WY~}lVZ;V};L=7(6hS*?    @e-VYOtXPeIe6}]vGҷUBeS$}i涻UWhаQƷ3iSF^~~gtV1 ,`]vm<˚|[+L]ѫ<7E6IW߬OVr #    Poj-؛NN:MeWzqjV{Y*oo+Lr%88+AC;mFFM#KV5wC[zQYYf8J]8`@އ6OYtY7*a_M~hgetU6__JMڥ[j_pَ,RAyFl[T_*zS6q]8m9x``/f =,*}F{sGJIIdf{k"00n6ywbܺ|VZ;~UƜ+$w]#k_՚e}:V S7_&wVs@@@@Y\qՊ%RPP j;߮6ӍiMg=Jo"?Gkn]+5%I j*}u_ ӦL}O$=-UR>#uTj#;Mrbٴqm~<ţxLN /J9,L+7h*Oe^0 y{u?^g:رm37=ݡSWy/E}fҒuۀ'£bBQWӘ?a;"IJ:"9Z;XhL\-ðl|eݪ{$곦kxΈV}ۚZ;vTgq$Ŋ>ln݊qXaꩿA**˖,Rs    >)j7^.)ICUقjO!2.{ټiC|ewȶ-mԈjISj զ,٪M.[XB4QY `37}4姱CmKuz*dhy9[Lvک cO0(WQSy "Qs9Ia dn`捲^yϜ,6~sA`uLaiA_pBv&`pPHHHHHHHHH#P0N^{TV#➁11  @8kPRvz|C(+$@$@$@$@$@$@$@$@$@5H 澗(d5 -  "`%^|r翯08$@$@$@$@$@$@$@$@Gץl-WС;NB$@$le)Bϗ&ڑ EL 13[V @fئ !v8R d3            CD;HHHHHHHHHHHH2 эl !@B$@$@$@$@$@$@$@$@$@$@$@$@AČF6HHHHHHHHHHHHGj!            PCޟ-a#2@"Eiceۖ-yƌhW7d2ϔyj)5 (ɭX:u>'{eжJٲ vɦMdʤ شrHٲw^uOJח/],K/܄',=.g?/u5Ï8J-~T[F^)4EW&x#?S^c}Egk֮+KԿOwٰ~]܃ ϐh~cɾG[y/-Gk C,jKz^˲o? @/!YYY 11 jeYrULZs̴4 ^jiܤlٲIzu;K7R*2Az˻oljR맟y?q+. }Jvv>3hF-[IoMӀN_z~AUo!GݷTY[*V,K;+d ]RW(T%K O+M;U\O3?׃A ﷱyzϩ<4?< -*Y_<#1aOAcwD_}ZuI <G EQ?@ҥcᅱƵІ!^5%OmyC/eٷ֎RA P*:TQ+#}[k/$7L;S&h d܊ uǎ$\jui~L'㨑1S=O@_63pG"|B|r~7࢞]W9(Sϐ{^%ժ׌,wlߦJ6t<71QtxuLi,^4ߋ*G&oujAt~dx8wle%K ڨ:#E4Հ΍6`d?w>$u[&2>fu]z^!f&s?7^/ !`ZhCGjn/VYHHH #d1pݻv֣\T{-aKWdoF`*q]r6sW5,g2PK_f9B5 YYNri[fjNo[ߺeRQ@.W*]FUpo:B LUxvpu&^xd6 PPi0;Z*d)dΝrlނLi~2Uױd_u'Cw^-jٖmٟaI;Egi1RQRn.){h9͂v w.۶G&0Byʕ(}TٓN>UZ(/Ïh,q N}^F]PaE3yDւ+ao#Tm:Cd_d?폵_b錶Yc_'fg/}]ZVufsOd:[}<3ѓNK/oY*~Go]>b৥ C#,~1R'Azo/6ΏX%Xl_?<zy3:ps/eOY {*ZNM-]2!,pG?I ЀwQ3u]~jBypcS0gXJU;v:Suc3uahFzt?/ןjmߵ7*:(sa%w*>0*Ul5zx:,MlM~A w0P}=xgcW6 "4}­Jژziol10>犭ut|G?V2 h%*^%Q|0^q3Pk~6'"j5ɡ%ߴ1@}l&6tDӝ,SSb z>kzL^PK1И:VL]}Wb*UǞr ,+H'W~qZRq~7vE4lt6 .>VI1/+ݐFُf-& rPdW c{]7[#^=~tГj {~q~D/tgtb_3S'X_^?:- NP߉pO}ᯣZrN|#ssݵL$ '@b姱rŲP5X*$hz|V 5}lٲIas.BHe0o37c>Da#QfL5bUQ+d<>e|>jaݻq\)<{qX5:g߿OݕHW*KWWE̓`ν5CQ͎]/,uQ>̃O9"֬EKuO?PDRjN2C=xQѿ ^]t5 ohXv mh!< yx /JB`D$wʀьљx|e,SKZu{>S8NB~:~~+gTl!CHuxԪ]Os -oFX`fL^"kx85n؍#g(U+uvlS|IAg)/!<:s.~IY ahwڸGb 1KxOEaX ?oN]z;/  s9v` HIvk~kp tPJqAzo/6Ώh%Zd^?l<F\6/~0|jteA ooQ:O{]̀G^1}t;u8XЀnr ḊŬ:RE+N3fL/Ѷ=S ˖.$XK1D|-: !r +}tmz~bsg7rqϫ .+6tgT:<0-?NCW$^>k!A~4Unx¿rA8?l5\x@L#$̟wݸLJ 6#Pق`_l:'K4ɦ~x.\?dԩ/66t߾^1Ei½HSЅw8 J u6  2ހxr._bԾQ=:,:&\Lj Չ`q+5 K#CxRUh;o#BŊEۯ>j 3 ]n2PH-+s/ں/_J1q\#" 84T{.?W{sm_\ +@d5R7+ok;4Me}ThY*HݧƋ(tbjnˠ7[%{q%#υ^rneSSTظ~6S0̀Dȑɴs=}&x">&Jnl@JիWa,SPAyIҀOigXʏXe/yatˣO S.o>r2^DKU D LLlH^v.R/F)c}# Œ:s;q߾}L%1'#%rfg=THIEb|9?<2fO1^mr-ĜF=0ZD92>=q6L!T-B5nBaT?9ChbCGd7Y//^?#Lyx",¼]؊KQ\+L>3Mr%<J>2 bxxhcV]1MwWng5} a ]l؞9Hϵ({FϳA}xy7o.LD[%p&{tXDx`NPnd!a> aZs!%FĒT/~u-7p_ĬS҈[=l-x>0i yF!ѥ~1\ H9v|h jЙ!WS\MigKNgSPn^ζu@9g=뇟c T7i4ޑ73!6nF-J*eBq2Y)44u/SOU@0|MP~͉P@4b@L߶:frsAQ_dm#r?|~3C9,OQ̻u3\ }4Oc%V/Sn< Kzcd%JA%tؓ-5l뾟\x}b+> 0@d jT8|`1 9?l~!:rCwXۮwڹ yҳu$cla*|LD>bc>d {~u~{1.^?R\}.(a'=*уeA N^1v?K6䎝wLC˴sm@:xb:u[]yfL?W523^툗d$Of͘"G-7m~4oR׭@}xӮs!_7V{%R~QF83iZKǵ!6q ^}PW=5X}I?OWo:i#~IK۷RZ.bo߾mw4]27^qѩԋ?Lm%KLBrB}}{s ā hmc7EcU_&|Źop|_h>l!Ġv߶XD:Q"}{l~Vn'o~rEj +?.^ e-wgƦx??R$?6:2d</b=Ԝ݉g^l Ѫ;7 yeiUw2fPqnK %-/R͘0* s`%o4|&A/mq2/N~g<{bןIs—NQseyL'ZT-mrߠSU{zRܹ#Z5 _6(`V1džI^]:q۵TH$S6veռd׮y˔9$Vg.-޶sYD'^&ƾHP~e ڸFzC %AܟBo>0~bd҄4.{1}߭NLG R^jTRe] \}hH"^H~:ud@IDATkchh0 O?akD􋍶8_:èbs~R4>J7[${qa#-F#([ԓl6O./KbaCG+{cr ͼt1VL`I$ 1ΥJ+_Qw]$>5Lx-O` 8AyArE1`Mo6mNj^@ŷB"؞cDCKPF[Lak !:9KW)QÈTK*GjM-[79/fNu<^q[N*T1aRFhcI:n횰l6t8aj7:a ]|~ЉM: i-/BBw# w[)HKUjzNᎹr#e;z@Yu;HkHt_pa=–٥~?+/3B-#sƒjP %.<ܨH?EQr-fTTE}Lj3P~/VR4H7X+G~IEAy. BDQ\%Od6Æd/簕]T47b,4~#M|A#6̟s5Bt,<8+/XpjDz5 .۟z*۴q:駤8cگ:?z$Ka< {΃->3~x/@+j3zq(%􋩛vryDgډ~ lMOoCw8,Tɍ%/F~ \'Q pC(&GQs]ޤ=+_Aλ`dٲziS97۶ntpJμɮ^WML=SnMeKd钜.]]&\p.>2+/W]sMiq->L(<㜮EHS͎n_>W\mmF[U&`.}8UX-z M3 J d!͟~涍FR,h'N+# <7.wT|hZyJ 8|I?,*ʫ=)s u #/y+ku0j߱s!k֮ c#g^l<`jZhCGMxWPl#D/tٗ#(υA~}*/|ԗ̺~qٷdgoJ7z:nwo)LC%'P$|[N&xyp{6,y_;n K;W}fG7G ˏxyu dhX詣B }M3z2ᣤ c,r×~&5s&ZYyOMOxA*WٲBUgsCF/ '~UFa`I3Tݺe|k*ĆS9SɿS / EFϻ*xX&埳g Я`rrK*2d"soyWrσC"{u 7m>ca"~za~ CB#"Ͻi j +ő`Uē͏y)#yU]zedry*@ e!lc~w1:Y?jY\ׯ]`B+VݻQo(6hcu*ϗM#ܶqC}hS~ Olf#U2 {~225,>!F y~o5@x0:Q΃H1/xfx!ׇyM^7P&^r' H/?Vs!>fY_loc!᳏/՚f2痤o1 Fl?M< JҳO+xs6iJXVqZh&H~c<ć0D 闩ߜ}-Ǜ 6]2U,/{Q ^svyE;G8V|0C{u=5'ʀmBLۡ~;j9DSxfes|sK`0DSӎd~xN㓛əϤ\hA|UYfJm b}#c32om /{ܣ>TDsYUQSb2{ObIVX]MoO[lK4F^a00€UR}~ jd6>ر]VZM ,UJڛG]|KDo.|>oV #i]ɨ * &kV\k&0p^f܎0qgx&3ҿHA=O#̶-3Uy3-^!toпk{x`n u}EqE?}xcd@ zr;9,\MƻiǬZ5RIt-9{x#hʕ0 &\jAy S}E_l saP_z>baC_6ej.E0M^^fDL'?UW;F2mKFϫ`dR]b#-x/8%WU C/u_AmPoF)?YWz͹ghdcnx4O4 y8K_uF>*US/;`oFH yYd2M7b_ϫ_n^MO AS#gH[=65_lER# d"3WڦiS~ѡ0( ~GtO?.BCA헂dd #0ޟ:KEƱO'Aw:%O;K0n:X<'VYK^|YQsa>dJ}wOdś:HBXTԓ9}E~A1\ ;ݹ05bT_0RFu 2|U_<7& 4 ՘ yrbry?4LQmd-/Ͱ%073֓́NICQ }[re)cĻh ;c\s&S7*~dj)5 @&ȪSLlD$]z,_D鞾 aI:ŊS: Tx%?kU2qXႹc($λYz\B}7B*    Ӄ&O$@4 :`pH V!%KR q($@$`@DS d ߿wx HHHH&`Ɠ \%            ?$~             ' @ !\!             @@WH deÏh,u7~5Wf@؄L#PPiz MS\qN2QΙ/ۜ^еI_lݲul"5kՑyseKyj B$@$@$@$@$@$@$@$@$` aN Uwev4iv.Vn½1:lrvG۴iL4uh<-Uض8qXٶmkL0uykl,b!e޽{׉?iJח/],K/tm3o=`@m߱k~;* ~yD(Rt>|{Y.UF )=[.֯,m,+W,K6t$u@f&        1PAUs ހxڙJoPZRBE\,YJ$޳[6oڨ?gggG+EevnܰN֭Y-;vlUrfGxsx=O7;n9JbCGcp? &LQ xwYğE拷Rr57ja= k׬ZSϐ'V̸c6wۯhbLܮR\~U?i}/$#hlq!0RF-(9bgnygR@nvT4ؑ28\(-zW^3l@ #2+۷KVB,u<`X}49s9炋x~f"m#0 ` n]\yvpAƙu؋z?d_׬űrw+c!o:ļgd,W]J;(C:}'[(+VMٷo\T~jK3Εjj}wܨx8[0@-\fXƃd#4hXvznWu#NTCRhk"ǰɣ Y ٵs3ژnb[Sjש/t2/aCGcF        HS5Tr}fB}F@xAC;_jmdɌi3#W>}oN>UZs~DsÙr鼼5xÞUG>5a ]z66;lu"C&^$[nǀȶ4l;T,RT͋N< YdaZQw4]}| !!aTky28I6<-F91 ޽K\(*$VcAMS At<໯s+͏9N{{bC !@D?=e+9]GsڋryNg#U%n,R|T^psSu>|ٹcR搲弋VzRDI)>nXN:UM@ơukW D-<9J;':ʽ(~isb{[ nEGeätx9q:&0P_yMz/>geKkCv~l㛥&_- 4K2 Wר3XnToL [z-t*p0K?&siBlR2g,mH@D$@$@$@$@$@$@$@$@ Ѐhf[n.SF:u~X|X\6( MT20 aA8I8;M`\r3r9;oo3{Ж& XC'#}ovt:ӵǵ>)Ǭ>kzBD K/0E.ֲ͏h(n,aͷR4y-'e˖)ꘖ(*W˵6)'w7XzA{mF2.V k{]J3+G5m!<8Dp>9KVTEbǿ͛6:u!$[8^wۯ>qKFyѢE0exzyqXYf߿Oyiښ_~++W, /SwJdj3k|zekD|x]12JSLޚfPڜtyFd>h!y ­Nh=wʀ?y;~hշX?krj. :?s02.m$`E՗_*0Z " ;vW«Lܼ%ר2N2Q>SFyx.ٽ[7mdRU{<#RS;8{Y ;0]aa!K,F3tաculPx=D .+66tatD.o"'mCG"a        ED[$9~?Fʻ,T^C}/+?09 *Uc@4l.K۷'͛G5[`S˯9WFX!$,\ d7KwAlj@A?К,q`<2>Kޡ0XeeeP-Լ˰?U?/ p^s+/ѻ)ïp{fߎ\Ch&VB0SիW!l(HTPR$׀[A 0^wR )aH5^hnk"EѧIf"K:"y V|!t7PcE~]U-Q"|+މbP%}ظБJbAJιx9 ^2$@$@$@$@$@$@$@$@$=K8!7!_&/Kؾu2pHBc޷o>d!5AHK|紁 !'xT^W6ohvM7J:%n57Ss?Tc>L2r:ũ7ޅ0{5RzRHypva|1YAOFDh}\w@zNżĆHɞlĄ>4%Ք`"އ_]1.]:OukW봺g^F:zs.;Ҝ3UVsIj}:?BIְQԲZ+[b͵p;̜`e<+#խwXdR!ejkF sl"yGu̷Xzذ#Ri)0bC^"ubvnɁM;p` SaL!ڪM;]c lYl\ m^C{ը߱6tx9.ː W4 z%DBrb%kHTeKĝ;wB+_s p';wu`AGN%uTKs]p{kuRd)%&Ʌ05ãp=]$>5LbWg vg}$"|k'S"FR.[b*bY } [mE9+n[:~4yr0vBw&?FW]J%Kx 4:}7xXuq-#V^v:n~곡#c2? %, \Ziql0 ?Rn㿡)^LY*9="˔)TwJZZ'Ҷ[svr+i7\"2:< h$n}ɜ?Q+I{9C]λ< ˖-Ksj{5s< F[>}"۶mٳrfsgfN, x㲫۷<1w9_N;K^|6<0y!ΞevŊ;p/^\/3Y,R6_Sj֮873ߪ&8]7s^*bԊIéɞ( Xh{8_/r9I:O:p!|mNlVzWfLՑ ヤ=ǎ z֯h8Gq9ƵІT0@ pL߿?БAYHHHHHHHH,T#&ϝ#%Jj^1Cz'O 7+o6y!ƓFOx"eKd˨"qɴ(P=?DZ͙j0!1Dc"y0q˵({.8xswhn./8TW{˖M2K ZHW9qP'A{m,+66t䍁 7rW::N]:=& -Yu4?`K@W kVk{]wVQK*(B -.^ %-`؁ ;o6oި<6hODH &a*7XKJ*%г7{ kVaX `|DCX":l!ڃPY J%Ѷ̬Un(&#&?Or*s46ZhSlc0QP: 3?)I;r2P-Ua`LVNVa(Nӓ-n5jNKs_z QkKo~]`pOF}lBw`*66t!۝r OXHHHHHHHHl`S(9`ZdfCOnZ-Y@cEH:xa%Ϗe׮YړU rR|{~Ćs|?e_UJ_oȘQ_jaYșSs:Z.Դ"ʸUԭ2c`6k ^NhA0gԜ/-k-mw '        T45~H E7L.9R|Iv>$Ǭ$@PӬBkVXJ.ʍ;0t9JƵ!y2%SB9a?ӆ`w#?SKzC:ҟ$[@$@$@$@$@$@$@$@$4=4j rD)$T}"k%M6['0?_˧#o\~2t -8oC{Cϯ9}$]T2-9)Di F$@$@$@$@$@$@$@$@$PPXPyܔشqCJS9 )]_A ` Ѐ $N]u˖Ȃs/׭^ XX         C!LptՒ$@$p0Z,UJܪ$@$@$@$@$@$@$@$@$@$.聘ž:pK@ @$@$^VZ^fmIHHHHHHHHH $P l3L$@$@$@$@$@$@$@$@$@$@$@$@$ Q0HHHHHHHHHHHHF4 6 @4 Fd            8 Ѐx0:L$@$@$@$@$@$@$@$@$@$@$@$@QЀ IHHHHHHHHHHHH`$P`l4L$@#,)^nd]Ic ӮXa F3ϓn ÀeyI)p13 %0MncIH 9.>So_~UQGBHHHHHHHHH2 эl D'T2mV7u7^~V/?(9) HHHHHHHHH2 ӕl %PrU9 }]oۦΙ-?A_:)T(:re d2_3l $_G+&k׬_|_Q+5kՑS;w ܠ' @ȪSo&[H$@G+/EU+WȦnDPVl߾Mߐ|յ]vukGuba" d,3k0 t&u 52/dOJvvv(+ldyaA$@$@$@$@$@$@$@$@$@$@I`$`1+ 2Θ:Y0_!3ϕ;\/VyDz9Wtn 4 z2$@$B tBy஛嶛GC쑣[{zRJ*dee={v%#          ?h@CeIH J,_6}{oݻwIfϼ"v.^{$8_Vy)$@$@$@$@$@$@$@$@$@$@$@Cz_%,O$@$`@vYh̘6YV,_Rz 9M[TӲѸ?e\!          i~ka# +/mw +Ν;d2Ѳo߾ H$@$@$@$@$@$@$@$@$@$qh@̸.eHHHHHHHHHHHHH;΁K @1㺔 "            h@Ύ%IHHHHHHHHHHHH Ѐq] w4 zgǒ$@$@$@$@$@$@y@IDAT$@$@$@$@$@$@$qh@̸.eHHHHHHHHHHHHH;cI            84 f\A$@$@$@$@$@$@$@$@$@$@$@$@$ ٱ$ d3K             NDXHHHHHHHHHHHH2 ץl x'@wv,I$@$@$@$@$@$@$@$@$@$@$@$@GČR6HHHHHHHHHHHH;;$            #@bu)D$@$@$@$@$@$@$@$@$@$@$@$@ ЀK @(q-bH I%JckKڵKO;كR;           t%`ՀXHQ)R8p@vʤpRh1/;{۷5_+. wܷd*+_A=H\b-[B $@$@$@$@$@$@$@$@$@$@$@L9\,˗.WÇ{Nl\6m \K6nX'_P}I78f&r)wAIPQPqIHHHHHHHHHHA {z^r5xx~}6Zl6UmB5ܶmkh=WRn7G$@$@$@$@$@$|OH/Rz#(HS(ςo*TPPT"Kw@zMf2Mn6{{wfg9sp-z V]{ҍ.?~۽.ն~ afu3ۆ'%2\mzzZ)*zTL            b@ Gxhm`\XJdgeR vG? \{8nHwvھuàJOMqִq=) ZED7:/R`P0%%%Х/0L"7L"_bhX=򦜜lvJKLl3___꥿BN3n_3r 5oنΟ=CS.4kފFrOߣt 01JϷD`Ϋfc ^㩅x %oo4qsJLlVnCe^}RݕM*ϯ"+Zb@ё5zP 8܀8y]ԮC***^}R+UMoj@a6f1CI`Qa!7C:u-K~ZBH1x}zW}Sq[EbF%@ ^zzS``0ŵhEîM/>0?zج*av*nP=yYG#           5C l$pw_}B#f<4/?}C{wmbث@E7Dˉ7I}v1׮Z& } ?yؐh,;vCa7XeJrY+\e v3077ti| I=zޞu遙OnT[t*쐞VPyLGc{ez@@@@@@@@@@@&0"{t hyEjӞʾa5p ۽x5v7hI↵+Q ԥضIB};p,:ҰNvȍn0.?K/b !K3ӨK^@lΔ|1zym)7H=JNŠ <-zT4wumssr           C|6Y0ߤ'҅G9+KINޙmE{wy>Ӳ%Q߁%KoDօhR[vy%vF$s '@M#P JJJ/=I>3"*z~6)aJM͝ GDjbZzܧE=zה[5`5휘pYj!e 19yNsRϞ8lmucA_8܀ =|@x&uiնxz;sJQvM5vg«>JU"5qK99EΞ:!ڶ}'ar=z __?vtIuz}+styCsk           xN1 2ֈ|\ 8i4۳kOȍ85ngЯE4{߳2+)x1:+lhzǽr-תs nfn`ԃu17wWo)V|5z1N]{͔fffЁ^jX5Z.Tј7_|LbRi0 ^pN 1̤[nN1ҜTJII"6҅/ mZTXH[𥳐![zrkVT߸Oį5quUhԬy+9%KD//oUoPU+PE8<y-/מgO˥q(Vm˲€Go/0(b7SΟ_ܧly/OTڻ}kjoef%HODxL+..cŋA@@@@@@@@@@H-&U'gT#(^}!K3K.5QNh 4n54*H)IsQd1.3 85- KreQ]W[Ԟy]q߫W g:GzpjSɝo0rs#C>YbZDX-:X*p€8`py7]iu~< $r5ހݐZk{*?j8jŲ@@@@@@@@@@@އΟ'Ìe;1G           &᪳'u|GYp3\αZrL%jG,@@@@@@@@@@@@t!PCB @-!b-9X&XBDK(0 ֒e%<,>  UK q3oI%vmBG0نJp4^}Rl8,Y3elC% v#=\~ꑁ^   X0 :/F]fi&̰ȀXqŶVΣ},P:R1 <ӝ (d}xTsA 09†5+O`r\Ntl;71FXI^ܳJnJ & uWUoPF@I@ڎԯݬP5{±ppu֔.ʀ@5 0#oSSZZ*,W-BFuȧt"ΦLqRGݽrDիŖK?nEET\lz0eAl]f NadGz<`LA%?/|NQ(%9HqwwsW:իW.vjdzlgV}:f$0g?0f[#ox[@lec5X/ Hxji@Tǻ#I\ 6M6M@@j[V(,1!] u0_.ċPxqaOQ7\rI} FAj͕zxxPhz4r􍔕) F~JA/=+ {@? JnuJ,^ci?{b>tqԢUjݶ=\dcc/S'M4sbP5܂ >T[xxU|\ȃKiv%ІvzgU~L:dou wЩ=ܪ&,^8\D=(D n+,G18Aѱ1:TD@Wb02\<6oX[n a׎@kxI8So?;{VXJ74""!1EnE+gHO,Y?h7|JɦKhײCthrGI|v{pOq~0)It1PRO At:tˇ}]=]pNn8+|mߺQTwQMm٤=DթAENҙTkr"]E"GpA݌T Ҿd$SⅣtt_tz:Aq]KɗNґ^Ȧ)/'Sno]15lٍoE)W8[}(,2+ c^Ca CGA%zfSVZ]:O]L@X=spX5YڵJK@]Gܩ~ Ocq\xv4Iꖖpv.KA;)(X /|o5`@@@v0d'm o2s*y%~ɸI7S)$,Lze .CPɁ]uއp(ےDjY*LԸٳ휍Qt k69Sh7ـ7!!x8CBˏQAAESul-D'>`QrU˗a㋌]/ȗnoVUh@dvcMό1٨NQud[z͎B=/;` -Ƹ+؀/2흛(EʎCᱡahKڷ({jZM_5kުB٨bEaէ?WS+&LCx5hS/֯Yaݝz ڽA=oxzKcԆ_7>y|ɢrݸ5׎)WCaUwF{e6.{HmGo|g2⤱s5у,73k֨/˦מܧ`0pٝ ȊGqb@d϶땩 ՞enz+[{ QԨuOm׏~|Dp)QKm,{x D6Pxbߴ싧DjbՌADq\\'[yerdo}R;]wBϒ>z~wz=ymHk͖QmW*TYGHҸ)uâQ_ʟ6.|G(c sܡ-w4),:NY|/;a@@@F0tqRcd4oܳT$HezJS6(A#iT ?k>~/isU㡵c8iez} ltἶe=͊D a4-a;K!{.%C#ig@{BJ xh%$*zPmsA<9]a133#eQ~p 3C_ 0t匇Y󰰰`Sn jGg ȡqtprnv4"j3nJ9!{7+4M1aSDu]1x>{bJ\CS}UwE#FXֶ}rC>&Husg4*6W=V WJ;A@0r&{ [ז[ꀉ7kr~nu*&+ik;j<7&f4rC ů&yj(tL㭛xxG*\c8eSQ+\ A@@j-4᝺t)=dIPM e.Pb_VM_ZDS a:jx)uX<]O4pXg8%enK #܇z0-7tŀġnIx>X{>rTkd5#E@W4EMRfF<+GccZq|5evޫn 5hH6ı#p@ _j\PN.(ձKOO]p~Xߠq%##JM6Z-o"¿n"m:㑧=s͆*KT7G8I c!ODbԍc5 6.}@ıҘީ+5 )ދŹ"z6A@"^tշ/SNf8נyuƒk؂l[" {DPE8\_6{cޣύ^[;.uϪ]wO/_wr*rslOmSrmap(;K=yܬtE2%9go~/ʝ 6:c^)muI|\?i 캉Пmި1.1ި5n?@rDHZˆb mF{Y\ZVO$7N718 W=Xk=̅^պ]O-ҲFO=:Z:V]Pr3=C[n'`5u4#=M M!ڔlRgK k^fjϕ au?ӸrٟNw-K?q-Z?012̏nTOp(qX*؈?H=ؠǎ0"vY+$:Ƞ7m<ֱOk@<4JLbG6w_돫= qゝTq*}xȵMK%:%],]"v8.l^ڻdCe/D5لpc eEνi'"7s¬+Ϫ}BC~j= w,R豎Haoʣۖ OVq, ZtG:xg5R3.JCoض(6h z!XY_HAEe'4K@@@@$EIS9URNv6anY /EņX QoTQ}9ӏ~ElO批'ً[IK~M d&CV_ez2(k`<}CM>` z1egdtcݳs%ywJ7\JJa/h8sKxD5h(F\H1t ca8)|#ș9SJRX+o 4dC}DN5jסmݸ먭ga%]HU|<ղ^!CqCSacDSj^7_|DboVA@A#bm{w\[ QnҊN[euStEqZN8J{*{ZÚ&_pZ,r} l@d/K~gbRCb,P;o[_CBdEdeџ9ڲQ7lKPiU[QV=tvJ^]͵/f 7&ɾ\CpF l doA4h%WN+r?ba 5%WTVP`[7TWU궶pbJD=0^%Z* 2kp?.|4'%QrI?hD2U%<ꔸ]qB!Bj:1E,7*c< <} A6ZP`N)Eթkid05= *Ǐ9Ā'Ӫ^rJ0xPisƻ^Lo,غJyÈ_<6 o+̰9uBߌإ}B4-hlϾ_IXd|'&s;5C(63L‚ܨ&;X~K dCnMO4W_xƎB- 5xV/-C5%E Lo e@C_zyyKZGU鍷D 59̜V\~) x " aARoKK#!lYƔ]OTh>(+ח4TER~pv0w(%,)x][ sM={/2${ *ORTVXXևZ¡Roc"M@}o}ӛjǨL4%/;C5,<.ϭ0ZV(6IS`ax`CO}MTOq VQx}mJ;*[lLn߷GD^iҰd#ߘU`#h6b kY kM? h, wփ)'Ow/E_6 t:t`p,5:`gL e{FvFr`ODlKV7Y6%`oDW[$'𦃳]Ua^O~Jk&4`?uhxTpM2^z*c(p5b<^vző}`Z\T)cc8򸰇,@@@,!biI24#dPQ=؀TWOPpI{fFO_nq̕9Ru=Vz- V&r:Zχƌae˥jO G1UBvܕZj#U]b\z:A+f^4ruR]uaKaR82S?k.ᅠ"(v5m[) (Tw8Trq,c^|6IUkZ` E]}?WZ-?w9m@dtMQks"A@fr~@mQԲHv"R}.o'@&'@4tu2gZj0~ERIJBQ PxH{Xg)WScqސ1Wz1uhg#3IØXa&!dcڇ}{,^{ joX37b`6Wz[bg4,6\5) ʞP5uHXY:t㺕VR4)uxyxugϜ*HR5Kʭ o8(ga,ЃC~:Ӂ]4bjCBJSWY?[68x>nZZ{2#twS6JxA)Iޓ!ÚK޲a{m`@ẕYNˡˆbFjMk֍DizD>ؤV֢Bi_]cs%733O(E␥II"kXqTڶeZV&%Ò?Wh@ֈ-c8b-j}9Vnm:9uBP4kN׍lJߕv-Dn=KC_,DdCqRC%އTNQɤz [j{#]9}@P+E/g|ZO+g S}%]>t5 *O1m P2o$47u37#zxxS)$,L>AH9s[?އo۳K9+ kּ 2$Ya<;#BC cKk6elj3oYu9^ENBnf_`\.uA6Ҏ%?gt>#匍Dc?%fG6կKe!v' ,F},P]U!ٵc+H@@]Hw!Ciǀ[sxQV1^K9ŪQ%Rǵh-n޲ }O҈ȿuN kW\ϟ9|b1_@zݒ Յ@Nzi/Ung 5jӋ񉍋yKa KJb38i6OBiDLyy:""Ƕ/3&+w6s#ۖXeS_.C9%«6^=t9o+sYw\YkLRJ) l"ua#RVz"eg>Wzqph~T> 7ȠKqt`j۾#q- <GD F6o kG22:ΆQenԉ#nz2uVRz3eֱ7M&Ź~GƸYx=o9סb SoٸCAww{>oP=1ԧ`_F<{2K3@)kt/i{ߕ'Ŷ/ dX4Zyq;kKјqՅhV-sⅳ,~[t2W6RbQh/h1vl{v#3_a~q5\As:B&WaK7>=eʚI`޷DžC>/>=Sk+YsyWT0mw6Ӵym,hx<]V`56νߗL5yGSڍ"fc轖rU |iqߔgZb9,M3 x4lB~*(6/|, xʼn{Iъ5itKaL~;߱$i8l) ]0VQ$DQ**͖|^z^qeFSSNN^Iiuk%Z9y?e:W1jqQ@@@zp n /24|oZ暾ሕp/%: lG̯瘮T5UX2̵yzz:{sC +!M#39].B /qzuOzR?ͧl.\jJ}ԧ`ihmҬRbAPa7mQPpg_T=KIbݤyNNUJqth@\^E78Ā@m[jN233%%&֢U[vhKORR++Bd^隑cI\ S=(/7\$0.7^5G;dr@@]vxҭC~~r:w4|:t`O,ѻ?uWv߹m3mٴ]m׼EkK_ jۄVX*mh/;K_}<ïK7Y9s}tYC7oنF -{PHەEWiޝy.ܗ+=<<π!ab^uӃE.Ǵd'rGCsVV6kފFrOߣt 01 :HyȑQ &R(4yyy._];Ғ?H8ѣS!C>uOu,B26UJrXEǹZ[=q3Ū6UAJo/i>תܫ@@ىnߺQ;@-">AԮCgi2#hаt͵cʵ+F6m٠uCeShƵ!ïG ̪vRDdZge)|hڕ},ҽ7=+.lپm%݇,gϜh]{Rn5n|4mg;}ـ5}9Ǘ؈t#qzJ`÷)\W$T>o7=lqq_WV۔^e<{`u̞5}`נ̌t_n&o @@@@@@@@@6?zDw 甛S| n},/}_!r-wɮ.?CgOqc5A׆8)vLxȞq<_;%0G1c]ZljumwYϾ*/>)3Y ce?~[m9d(iՏ=2sڻɓ{#=N+dϸ)`6j׏$d/:f̧i}S/*j<ܵ} -9٣4?/O[ =__?'V+7"w`i>#/?}#vJOO ޞQuwgmr{u`||Dԙ[WX"/_ ػrܤ[W渉SM]˔Yqq ee :r/[9J          `9] R7d'66qIziDu؃J1 [bo {ZOw_NM>T"$gS*<㚷Tހa$9"˶iKOx32"2JK r 3/)=>@y1~3=mbqHfa.dac_@|Y`8pcX9u3=~ڱi\sj 5Y Fl_WOs]=YEXu` 庞S @@@@@@@@@pQs~IzcĸT?_R.T[)"79)*P9>==>|g6͞ +s艇#b\VK!1RGSF,8Ed6NVmS]CǮRc7߯>}ZBDsWdtc\wVn0M/kqGNeU҃ױEUAu] 1x@@@@@@@@@&pz3n իgرM_O1ѿ4yrsK5 ,zwE^Hz?o=v?d[==;D>`3UJs)/y6f7]I߉<~,~l_G4(74kNnnօ0={\-4g{~XF\w}zo*i9fXm+ǒ?mu4Ũw>^}$3=u=0Uyl`<@au|5sW\ՠz)w'?sh~ a!         =#b0 6]pH(? .?` ㍫W)3#]V7xxxxj7rrR,!#z.4hKO>7*3,]8Ν-7V}$V i>"9oݙeGS5=_s"m:K%bі<9lZ'hsֻbc͔}233=_ 7ֻ#__m3q:ZaT36FB_3MK* hV=++S5+ǹZ(WՃ1fǜ]rsw0>SXxL[C5A]h3왓`>          ߦ7Rdظ' zZ\WmPϿ[DIWdN@Icc-TPP@VO尦KF_n_mŗ{}myS JMIB3GW-Ӧ<~ڣ4鑹loZRtdnMoĆ53Y3}׬Kۭg_iDe s+CX,e#0{"*F?ܚ bb0W6[rX4gc"ceRYKq7La\i9fjq롇Ymlck/Sc6Ã>>0N<{tje!#5ZXOhClqnZZ %2x^H gspJ~<1*%'&HoH3ӻt5\iXeEy!49Zy>QэA6Dxz\}-s%FF7C,jzY33׏;!KZ\Cx±1VqbZ N0]CW) (}Cf=EDF> iLT+PpIcM!Tu;"~Ij;YC@@@@@@@@@l!kS[>UGԉc}x gkV@j7% h}J`         x ꆲ*r8Qk(;%kv         z{@W} (^w@ǰڳX@@@@@@@@@ԩ16]@;p*kѵѩ16]@;p*kp xUvMr؅2)-56-e׊⚇E3ňy?S        `Hp[6bڼmzJOKUMG(55oh͕+uBuٲAOt 3l^Bhʭw h_l;K۸5oنhN^MGkњ7sgRMU`Zm&~V)<<U/*V. ؀ZԼuǠEQ{ZsWeAӤkWЩܹ3 -RdF9r榴iNhatGFU:tajˡA )>oMuo5gSzUOaʔ))B%:t:F8liB0V;f%!5`a+g WrE9s@i)yt };͛~L|}ZX'K~ݻŀ68 0&`L \$YOBAX+\&&`L $>$k@ ޸~ݵiı#k6 WW %0ؕ ^'Om[6Ui&&u Y/]_K7m*9HF F}eŜ/ %u{)*v!3gB԰IsDg7𰐐LEhlZgLkH[O@b] !K!ׯ]z˶1Pm:tzPӄ:`c~$&qT%8)_,FReCFa>HϟއdB=w"Jԩi>H!z`A!z\,&`L 0? $yshOSH^ɖ\%J.ǥaݸ󋰤uk".7;,Fs^ជ%4kNm +2߷>m:lԧtOfڣ/a$5{xr2{{8m޸V!ʓ9K6&ȣT^x!4(:t05밻,Yr&jMsx]&9|=.z? w.(?bv]d†+i-kT`CAreȐz*2 _!\9\4&`L 0&@ZHBx=o/jre T587Q9JkPl٥Qa?ٕK:Pm^춅%s?!>x[r=STYt%wذsfM Vsg~6aPbfCG<;L "H|&;”BSt;҃~N TR00 H/+_| "x8G?*$XvY‡@TROL 0&HF*Y>e2=sr[01o +]4sl:y6mXCysP२XE/@֭^& g7_)#u s!b)E&E 2F/7KKvy=zE oF׮^sdȘp 4iu(t#:S(,;ʀbΡX{bgWU ?{,:tuYW[:~Ӳ]˕+TwR$9"͢빯RkLw~ӯU.hlELx"s֬>.;~찌‚wlsZe P>N8ϧvE,Ū-{\F>!9L'}(߽kܔh'iG?~PV}oOPUb7B)wA)>w:ow兀>֣=ݦ3y*͛ 9`L 0&^l@`K(}#'ts#kD^~Av^VjҏutИvoٲ&cڶ=fV1ƿ3z8Pet6>z=߹+M-8}6`>!#!(h[cUzS:€^blP;`L 0H ^jutQWxXk=!Ay50ںi=EE]沓Ugh'X@o00tFxgl޸l%> ֯YS K>bg:2vx F~=\Gzb:sܦׯ+tbh^R =y;-t{ʔ)wKj!Ve:eK/hiȃ` _ʔ}0 2M"xOó:cLw[ƌj̉!]̖aFn/B/ hOS=]j>Lc0'O?Fx`XN| : 5 kn0ޯK!3GI̅pg܇0b?KvgaL 0&.$o@$B&fɚ#%͓!=fH Ȃ 6+Y0oG83{Xc7 1ft#͎!hNxgvTO>1cDV*7RP[p[j~Q/䕝siYǹXvR/JL\O`ـ({l@V+FoT|X0b~>x6 !`}~V4-^8WF0;uG)>OL o&m%exӖOAjpE6\&&`L +$o@DgP$o9BUr ԩr"ur‡kV%LoTcQBcD#xa'O?|Cf0c!j/\d^`˽?f8͘ PWLyש[/9 xz:̺e{\.;끶va'wܼRstʾ#C]ʋvҨ  %۷xT:uҮ;e{?,7x ?vD2Fgo1a@TOEȞ8J\t!u]FD +samrO1߳z[4gB]Dya9f7`8w6 {D8eD@x?a&h0(/I=H N'|-Wӥ `L 0&Iހ0:=9)5=c٢oʰx96 0Aϝ`@_CxX{̈́9^zYb78N'rU˹ q۷o<\ q s59W""= '"5k9ĜJʡnsĘ da@IDATE9*Nǰ"ܫdcԺC:Cc^FoXxp:Ɏ x(':⬼u>;Lqr퇓c}N ryQ1*9,wdE.^ Zƶ'6]:y{k oשG) 9@0t*TVA$DLx *Qs 鹨2&ȋ^Obܠ(ϜYs߬םz'𻔓iY;*@ 9 DOwd7 6S/WN'h B؟D2T]*?Z<[ 7"kt iUf\z@< v(}ϑsW N} `L 0 l}77_AT'|Nܹ3qݪ-w? DA#N_n ^FLȣwj0!"O f\sR/0d;7*%1UDtWTҼI9t흘;ϐwK+iI@[XDbc\a7sw㥁CHB̉N%Nueul~m(3O%NHtsyu:BZC̛EuֽͫAJ7So攩hW  +O̱gD4i=x@0Unu}܌ mBܾ[uKy;m`i-m:Rw9tr1-yd˗%[`߾C{1_n:=z핞tUOpKOz9/aH{5Nj[ +jtwn2zy[ʞ=rLubuw2"=kț\~]nVļ mϐkQչmoıՑɣ 򵫱Y{#O,Ex< V9>܄Nr/[ԋs1z1p,;DKHMUc\861ݱm3Mxw2-Z$ xmkoĪtޟ8vDa7dSO)+ A,VJeoswUd)d}p{BcVoU&NsF@w|`P"l]'YLy> Z n":K@rԯg'Rm+o}@M)nnKf5?}>\,0zx$uy=uϾ:c>9}}6a,i3k`L 0'؀^{yYV|iKz{$z}x 4슕`PMxA1+Ý8k@l-}.Vs b΄O'#߹]gՇdԋsQ^]K͓uҨIsJ6փb%u|B^̿ P2zUuYƇדJ5[Q_W'&,Q֛qgNeӡè SʄA*t1g17a9Oy:ĽyS<p̋Z\RigcޫU5o3NFdPN- G{!P2l:uq?vTF P$yU]:EWYtL#`It}0X9z_b>e*Y|ƻ5|yYu`Kwi4,KՌy_-B'bO>,J _cP?xҗپEXX 7u͇wn%"gBjm Mg0G7NMtgW4rG9L1I=m%e2s>&`L 0PslE*>՟q5BtZypҘ˩S'Oo<̲eZ9ҟx#ڶcwS:zBi֣ W[fy@Eۼq^B%{tTn=</>";>Cud֮$ߕ B^Tٜ:/'t=ostwTz׽8Vz]3E?"ѴE\o`,ٮ굑ʁ G 0pgXC({\kaw.cϿ$=sdΒh+G#ѣd~J~0yϡ,AREӆ()R4j]VQ&Ա^Rɷy.:%Q_w qKG> mH;TXx"7ƛ3{L4Xv Zv&M[91£9DX|@A1J8unNEHte]xNu%D^G'A]zĮtˏrnBxuI2e*2 ?>~ɵA,|I{&9RAF/KQ4cdrY S*U(DrU TAG_'{vwBC#uN.ːp=^_} 10@hpӪ}h[cx?N~Uˏ):.TDnA-7)_QϚ㭇u~$`pj^\r^w"1ڗF]?Wׁ,sg(ezvt֋q%s\~iԜW纎v q]JG9ՁvA#kѯ9t?:CB<6`.{UKD$ QdzaάUגj"?ޙ(|{N ucS5+o@2&`L %D/~ፌ۱ \֋Y?@o4o}C/[-4ꝏޯg !*cT#ZF!:sڴ~lW6}R筛Ӵヤ~u.Lus_RDd'Q\y\ot-cèCEY0PINRg^zPNc8V0[\F{r^7p[=uZL@t6~, G?fD+\Y x*:e,ʀ(71Rv~u>~k@9/>@(<֨*O?|Z&_cH&`L 0&2Cظ~!v iG TK2x^sK“wDf%k&oN!حG?iA@H1[~cgEJ:b;1OԼuǀ76 LWx9rFNKʵ`L 0&6 &: 3&Y5X/GxXb/M zIL:LuPdضV.׬C7m)L1˗hժ/_n8[<+>͖#1}xe&_cN L 0&`L $46 &4q>`> Ԍ>>w?æ|4-<{8Px FN֭ۮeBtMWR2iܧ^]ls`L 0&`L 0&D p:B+ij<_ѡc%t,Yc} }x>`L IȞ#=܋x}&MsZo:s3km߲mЄx5Nz}BnE{܏70&`L 0&`L 0`"_Lǎw۳uF+:t{Ѥ(] eӺ>4 D=7_w-BHJu۲mJ6EGGӐ=WaW r7 ,BmH ͯ9`L 0&`L 0&K wo3H,ytb&@^uc]!D mDD2RI?m<4V_NyUDD#^fL 0&`L 0&$2`L 0&Od"1,G$m+`L 0&`L 0& j|C S(s,tayoH+S^KWD)jx3v*M82dDM S4i)YdtYhhzBxV=JO앃zP2UY.EE?Ǜ>-\b]IK~l\kUTWZ(ԋ'CW5mVr1ks - |wbФO޷OP^diYS' ) օ];U1&`L 0&`L ؀J~bzRUa;IO$;qLe2Ӡch;#Nj]GϜ% =•`h4U)SEKukB{v۷o~>P50MJ"4_r/OCGOiҦuێeI^^# ҐIJh؏)WvL29׬C5/Y6`>PUќ.]zʓթߘݻ{-~']jA:~oр^t\_@;()SfK񒥤j<_\e;{ fPѨ͓Jcյrc7Cz)Te AfnșV%EJO nO/o݊OHÇ;V /EE1 L 0&`L 0&1u_gL‹cNct޽2x$u:zAiə+dw/VퟕaŌDe͚]惞VJ|Q<Z* /%J|~$x y]e5Kq}e oH,v2 7!S"fP{2 Cca\5l:F8»C7kE# 4xef Tt墣^t\<̿/};m?&3u4o Wc QQ^| )NE,AO@ǎcO)AhccxciT٦BM>t:i^F-X&Vh?tu0"/YYW._F_`\xgAϺҡtaL 0&`L 0&; Ǵ;KLL8oa axCg<$0[ԧ{[ݭ 3V7ٲlaIvs3wgvV`l  sxQvP̵inCQ+-~,bj>NJJ5v0V.ˎ=m% Fmuԋ|_tIs~ # VPܷ*qR[eqK˕'w4? /!vD8iݞ\HlxC^VDȲ`s`\ic׮\1.uC X`L 0&`L 0&p` W52ո˗/؎A9gqBXBx!J/yoM^.#-SӍa 1ԝ;wDQBWzqbީ6[O:O OE ):Ea>g;Yu2^A`:~찜;Tʋ??Qz𯴾s9uk69b1?С>3w_}*F+!g%@7P2>}5)L 0&`L 0&`I\7V,96l?fzQ-/iU=Zf2$&Fh?t0`pd<Ե'eϑJ)'-.}Ju޻( b!۷!Jdn 5 `L 0&`L 0&F hI~3Fx{{h&r3 $-1ȝ>C =0$o\}JyG`TbNHHt6@5iMTλʔ ,Cz<m`zu}s{:m?THZI&j`sF@$Tڏ@-zQ1›!ԗ$fh?tȟ/Іu+r՚H бȃ!ZW=M2xZ\oyy`L 0&`L 0&M \^D,2}&MxF[vڨ14fDthK}1?Syo#ҙ'*O\mʋ"_7S;e}n^Ow\2d_hV&O@1^t\s{u\ˠ'Bèmծ~XMGZ^R[QĮF&va Fͺ4ܿBGUsÔ{,ؠtp6&`L 0&`L 0C NKȕ˗ˎLsDž4{Zǜ[cF ? 4t{+=ڵvqW/?Kdb/fȐvvaBpGx2mۼA@(op*Z^j9sίtr::Ea>;YGbJ_dܡ~:]zT]OTznz0EGC]&oڕ%UhTS/l\뀛`0W$$[ةd͚]#L 0&`L 0&`O: A Y\S<>HNx[sʓ{ xpׇzXБhx*Rr1or{Vu9B';R<2m_+_} 4=Kı'O. yu0}E[VQ>d:._Ё0O@MsеWq6ak^xׅ/Yj=*/^x׬]9"6JL 0&`L 0&`K [w\r&CV0< .rWoQy]G:4& O6o+žq+yU+`L 0&`L 0&'<'2W~a:gΣwEʒ44+E0SLYc@վ=:RNIY7|'sg?܇W\b&`L 0&`L $6 &y;|`%>fI(pf)k O?I̛- 3 NgL 0&`L 0&J ^F]._;.y>@LC^ 3ϔ5?_Hq߰0&`L 0&`L 0xDx?&`66.L 0&`L 0&`L $(6 &(n>`L """?`L 0&`L 0&`!HC`p`L sᏅ 0&`L 0&`L 0&j\L 0&`L 0&`L 0&`L $<6 &"`L 0&`L 0&`L 0&Yl@ ٪1&`L 0&`L 0&`L 0'ĄgGdL 0&`L 0&`L 0&`!K =z=dK$OZB.GEQ wP]98A<>~v,8W>Fy]$;RmC,JKx՗ ޗ6KA SNC~11 5WA,i@̑+7q,vvS2)*"unXR9ikL4j@ˏ5zR_bCayDzag>ָ^gGETuz*'PD)p4gOtƉ bP/ѡ1a]J'pǸ't7cYxQTpQ޲vղߗbvȝ?&h up+TyOsQ`L 00$d ޸~ݵiı#k6 WWt%0ؕ xO8F۶lLE3PM^ЗoUrxl_8} *B_+V!lˊ9M_|5JRT$](B gΒҥ@ 4'x8Ot{: X& ˦uvt$uDf]0ovX=Kh֠z-Ve<ܿo7}t`-B?#,ц iӥa7O?~#mgӊ5t[\BƵh%n۽حo:=m+[AiݚnCcukmB 2dٽy{_|^ӕRHAΞ1:tSjH{d2TOlV2U*آUxs-[V}b6;ySJT'~CAG>՛7޶n?r:S:Bp|U/PC'uOLдE[fLΈobOu;1wh'[ kS9 $el@RԬI0y@f}#'tse˞nF4dg1eaFz+XG9]m-[zj2?fy˪m}^z WpŜlN4X0^r-sgOw7eXHU&b'q.V CFuw0PmcGg,OAw$;vG*\LzǶ1|xd;z(XkqAWuq ca~c>q/@ *;wDgvZ6_H%~V":O~It;PNڢz,۷nj@t~8vP<*Gz1/, 9r梑c?\c j_ tWj1ޥ5/Y6~up)BϾ/J2<%KI#Ɉ/Y0A{3фڸz"[im@LX~;.SԪ@W 0?ղM.t3 b^Vz=߹+M-h̫Y/E@Lѿg'*_᫜jw: 6SUp Vo/tڦp^{:Bp|U/B8m?p:t8[8TDx_XT w'Ȭ"ʵnYxNdIT0Ҝ8~ԕu~ε ɨMhSTEiCL'NV힑4Y.e 7[͍hr}qiH|yw7_MDF޼qCK_t#tGï3ȹ@  _ ;$ic%^t~}K~y8[/0nt42^) GJ3^/cI;ƛ0qѯkK c=A˪[Uty]iܽk{Cmk^|*|DFvޫuoTvjԸ}^V!t0^4zZ֡JwLus^V깲TXØfGvc ]Ƽ1ȭgyyB͕;0.O`PEԩ]}P.H#Vb%[~D|A0f]r֯'MUT?qr9iם0Ny-c1} -sLCqr\sQeهUjݡ4 ژ`CZGI4U*%ַ{Aƒ7%UQK*~u>un˺_B]N})s2M"~2E~MgɈ 2kr~;9NB C׹b0ը'<~V\hq7"Y`9^#{ETQD).|'#oyr~.L<* :Er_<4i\f;s~\t<_֋S,'Vh?tu0bLӄg1fSЅo8 J4K(]:$yb%%k6uy=cD,:f% ͖;f}an:q~i-|H#?w[:)UTr1S+QB-s- \y Y&ݽx:<\GeF1(# b%{&7;=-ʧ.XLU7Ttԋê_tIs~\vjbiԨˉU/::< ʾ>+"!ϗIj׺x :$o@DgP oʡ*Q rqq:%bJ3  Yd0 9oTJCI^I…B'7W^yA':Hvvά#yQ}|# Œs;۲>V(Ǯ8Hc9DHn1oMGƈA}myحs*wo m!cˉf jʯtS^r>$:tu4X\vm?üOyKxy)uQs8g*/ڱKj+T>swpV?tXADl(>A`bÊ?b{CQD"R;,?ٝK6ے{gI2LfLrscs-aA: 2e:{ X,/[q@4KEȜlI ucK\5sSE{[}@ޥyTk5UzU Dc +؃g-\رM,߯.lw pw.A[lbSmYVIF9|1x@h^!0> AWw~>6R4a 6'? AǏT&{.,~p_LyAOM9f~1~(07ު'9!\]^md @W bF2̭̇hyӑ^ah[E[~:U) ߏH%LUاձn{חKR/%iswAeힱ q{DNe," +pxpNKó x&7m>{*z[F>ܦc:$ /8^-eefK?PCg^;_{^ÌjSPD`l/+ge^RЮu62L+ n f_vaVk֭s"//<^YD#N]{o y.uj*."W*f-(lWPKQ g4EX R}HU˖+f25g,t-uU,1m%#oww. N,-/S\v&lmcJfΙ'å5b@iyװv1y_cc-}jKzc_PƢX ̛><crgDҥ] w\xu?h[╟_ǻ>vgro6zW~awUwRk&CΏV!S&Oȭ<.` ki|+ gC2dTXn~ls!8  qh}bcd;& Aab :~ﹰA_L9QOM9X_l6pIrZSi֢M." ڵ6QH ?( ^uz2L+ eZzb;GZ2{hΣ1{%5e'Jhz6$o23b*/1~jO /' 59UďO:MF'Pz͛7O^7$D / ȲmHj+DֽVm۶x펙#_xK` ()#S~׃ W±ĸ-Z4Wa߷'ۚ.D( 9-vcQxrO2]`V0-gYWCiyŢf+mRZGug/{w7s]*{0ݿ_.JaɑXr1 QqKGlV:}z5qrṮa|cA?ƍwʕO [Ls~XY?&Ƅ -9ẘ[AѲtglmwe@+}L~N2kCYw6PBZ"l~\{mmW!͸4s~^ӂHŻU\EO.>:0GųΑxK{ҵkWU,۪-u?ljGKȶ[嵗|XɈw/6R4aϛ 6?tml;,㇭*i_l6a00qXiա|Y^1ۜGKDپ-KQt􋍶S%,C`uRLmKT("~O椻_JM~Mb,o,RnD6U!k׬ΑF0yϡN`\cK1.j19Uv\ }H[5-/\B#VNO']&Nzrz31fe`~V콳[:4j%t?Ypa=ܖ٩˖j&NhZh.ݴvn_KF:Q3fӹdd钅zt>ujt17}escmqyvU &U)ջ4V? {|Ck;tn\z]릕SL:}HIP+aS m1 / [b<ܬ7Ƚ`<^夺o{WTyJ&7_~W`y#R |8~G",&bTxA۵3.AC*J: fA.]?e =fXUN)^sq8 ݕXuE;񲥋e⬉.;t.be=po|Z`V_6㲯]ǵmSOEPwS'mݺE5,3RP]xFa)?<@jծk%LizgYY]^_pi(V,wn-*&9Sr ֢wp$8܏T2{fx6;m6]*:;JF|O)q8otb&-`"&~|Ey:ua>#y?{oϑv M9o#?fǫSaQN]Be ok %O eٗ~<4뭳ݗ蚘V.OM$p;`@ Aݮ#1] c 41raNz{Ԓ#@.?:  _lŴ-H2l.G;WtQ⣖}{|MN9CmqX^~G/?@&%c^J-JU|H@AB@0u}B?(3Tݲy|"F Lms>~#- rEZ]Dwju9sTL8W;#\D wn;y_@=1 ba5.MmHO? rNܓ-h6~`J>V !/ TJXSd(1lɬRUk9~tPR?bPT``>搜J^ ~}z?Y.9 e"\Aax8j9,2 b-A% 2T^}i. m)%}^IՎR'vV慥_p/܀HR̾w*wi{FR'Eb`6!''b@Ye̠Lms}k2}Z5m _n}|$EԿTS3 Vn^Vv`bGpNJfh}Ss?wodzO=*ÔXB &Fy ajm̜dL< з&C:5Hv)T>y)MLͥجO¤-sgqbblx%"|;wQY1}6^Mؽ{swnauJ1V =w aUBxNj'r}cgL}~z  !,x ?1GNLmKw~ z}Dxٟs0,G*/~}cbcQnoG|y0i= 4s0 7"u6ka$hǀpY5W?hX_wzO[xYlۆcՇ*(` U+F(!3wg*e% Ci~O:,L\(!0 &.>o߾MV\M dds-p{ eds&0/ڇM6(˞ YKfUĖ-d }unmkԪ&\,vs;(SuaY$PЅi>5W)+,K|VuA̳[jGHMQt+lXCm ^$lK,N\B7Kꗮ k04=>[m~9 vkKV9UO( EDu/[3APJU\;{*grs5GP-aNuMf/dΑWy R[l2+0<֫Q¢ ) ]*xs%n+9o(s{eK_^|a_ӅN k>_ 2~y2}?M\㇍2/A<' gT guG"X-Fꆵ_:*]ΚW(- kDnH&|M'ztvm9,wP,‹[xۆ;sZt!?zG3O@@yN}+Eט-,sΐ$S{,m~I3,2ϔ% @A$@bAՐiߵ+(|7J8_/?ͅP:{(N&AMA x͙TRnv)_~pۼҢ:#=uwbZ}.Ik#Js(w&9ފ5u ^cO=Wdj/6(,Ls 7*05hb c RFG֥}cnLH$ӐtDiS1?M.h.\¢bf+f3ǒi7zIɉNIC p@[J)coh %oƼ05>:L?b>SH$@@u/ cH/W^'teKKW߆$@@ɒVikC([V)Ȯ];eͪ2n(1ȯ]IVYjte~`,HHH 5暈&O$@T :`pH U)e22-k gP ) \۷/p @&`PdIHA D @a'p Oa' HHHHHHHHHHHHH"@ HHHHHHHHHHHHH"@ @"EёHKj5U9ֆHHHB@J[u AmX      (8%WSNoYc=Vm.4w¸1yv^/^B\pZuIٌrRxqٳ'SztB֯[Ցc;AzyTQK{U+'A˫?edO\}UD )mw edϞfsoJ͛7y*W*?+Hq(F[!k DDYF^xlҰё[>i=~$\QfLg#;vo8ߋOKuҥ$u}{%so3Մ#4ŷ{oY3d.IHHHHHH|Dپ}L8.G5kK G QΞ5]c?ٱmQ<6˳VRM6<zY~Y2fmj3;bokFj?:b%9Kdܯdy*m _'A&+D$M|s#-[9/vy0='Kb>&I^=Ο3~GIҲͅrѥTz"Нݯў$cXg      *S +-3:v^P, ͉صsgd=/Wj+;vlw8坮2e2$33S_wόmPjȶm[#Y3ݑnl.m\6ʰҦlϰ)M2T2ʖLe>9 -ta%5=~jR1lzOԭ/E$@$@$@$@$@$PPXດ JܔBLP8X9!<(p];%#8:ӓ*f6'fXM#?4Oayxт\D? :I{uرc{o1G$@$@$@$@$@$@!@bjZ/#]&C*.;vl+W 5r:_lRo e˕=Mj[?!t>7^͛6F-+Ȏn%%{#fp MN3{gRnP2a:Yr|[2MǩS~dWG59jeú2WI9փ7WX.KFYc]Reٲ9BѤ{-O9n{oH*WŊ];wU+dw:٩ *vn ͖B9!&5jɅtidRVoThey:H%Ju+.~)e2Ŕcm.Dݿ;XT^.lwVoeRWsC.mB~#>NS)J/&=`2}PhzRT]{'{d3n)7e%f-ZWe$KWeӆ1w,jڼ_l$'*5k86n%W ӧz%SNo*'z:i2_9,<|',2&cBSΐrg$z%6/ro k(u        P tfk"غ[pJ| ҰёZ=?t >:ʌC7{ܥ?N>b{L<κ`ʔXJPwh} d)rσuj?>B,a9Cu/^8?1h䨔0Jŋw6ƒm(fY{~;vf3"<)%ks/NۻwodA^uu퍷*u5kkE; Çc8#t2&I/ǰR媂-Z{{VȤ6Q#5Sh 'h[L]V۴njysȳz}GߟݢܡZAt UJk$?MJ?RR)t]o܇BQVjԪYާgYPJjȓK5s/<팦_1GD6N-wl7eK=/Rg߾}Rv]sW5CipQZ Mm2lyDjVz: ɊQ ʎhpuz p *JEO6 \d)"Y ip_oc~WieǝQ56R[:@CYAy,}?mkDټy.3VB!/ tsՊez׶OF|I͟O` mqWLN?Vb}QFݷozn};ޟO(и0C0֭ZuX֮sNKݹsr+/I%#i]@.dq#]c57ܪWVO>z_ڸ_P{F2 +{d7 cr'Z1j w?pҿ=)u~VO*UZ{M/^*P5&bK(ͻIHHHHHHH qT &*-9 =?T&|XܬT^]ʽE/5?W/\u]4k˳CYMzptVRb$sB13I}$qƪ={UwVsPPnݺ%G+Rg/ֱZ3=cFE]!"3YYdB㲪8~r6"*VOpd U֑IeP)[@tOs\:pa?e=P}{H [>] ֘6OXlwY'9V D45@=ꇄ)la-f %-,0{okyl 2~3SWuE >fZ*d[^OIDATbmVm۲oۖs̋ߤ?L^ºO>vܱ#R%K/Њ e%6)p ]Θ0w~)phY LhKx,z ?]h٭󹟅}{N)&1]cr*/y2V&K       P8/>C9%?XlW(%,Xyl.bz*;ДN.tIo(TľvUr)M K>bc;Om(-nŖ{?*7tһ`%s/Ѭ-M"mpxV M'XA 1@;B)cyUTZrPC]w[;~p? Bab"ď}pl x-{{DれΫ%KԧڷwPN*)9|>zYj pϚGi+jHYQ >~xJv2@Oטiy2V&K       P84fdhĖ/[?JWWׇpC T3$:+B]M?_d>;eipe XM#,_[EA`SXd׮^oZV+WZ.(KV+ b(k 1Um[t)AٳfXڀeKR "d'-Y +7?u0'X?x[H1` 6qp2Jfw1e=>X)ŋH{kњhy/}kA}wwrtɩzEF ?T gGҦb^'߸a(i]&##}󦍑uNm۵uWf>c/HZ~:޽{trQ?/ˮJ;Ru}pX g_{y2kPr/>59އ",,^S.*e>xí4,X Cch( Ktػ:.ýͷR):V.\^#k^HLذ~am@;!+˥=xɖA[uzۨC*Hǘ 8-WvO1aSɘe @~#@b{ 1kmqWRŷ3eE4A<$ڒ/^uE b$,Is)-\7abfšV=\J5@/aw%K%J2j֪+7_?c+O-oV*OgeIh, z7]y*]3e bҥZ`nšQ$=ex._N#<_'뿫X)594M:vf؄4lr>bF IJF6ꐪ2R6&;\{=s.HHHHHHH 1T &)md[S0mQ1،*X&o>Aū&>e^ mȈpW7_ڮÕI.]]`ӊ=<+}ASrʤz=kV;RіJ!tnm/]L$|8̛3Kj+P,E\~lȴʼUtkDG}Di||9B;< y)hc<ݛm1e,ܼy˰_s{ A(G8V.?[ǽ;a#19 DXݣ܂ؒ/~BojԌbc<5KUnVyiSdG{^Gܕбr z{˖M'·; JA=ٮMlHST^+9KynE!      G D)5}Hrop)RT+yR]kCk(xlc#fe zOpSdaܯ\YnݲYqq+8)^Dm enWS{,.j/ KS "\wB U+;w{c,_Iee k+.u7g:}HIp_l˖.Vw"]Qzw@5pHDIc$ r>yJ5~*馯lnc RN׮sI\6dM:&'t& @hd^|Yqa#rOɈ/?ɑɿN+v>г7mZ]9?*RY|.L++2߁ 7 {upudO "PJy#O>#y?{oϑ nˊK,UXd{q"#o*V?5˶]6ueDpRg>Gxƽ`>d蟔-S+ޠLKhLZʊ}18t~[?'HgK/?ڋ1՗»o>Я~]rig*+A|#?5 +_<'a!ˈZL#jHHHHHHH?"u6p಴F:!czՊ'Ѻj euqnle[V2/|r oik(eg|!%87t*T]% ״u0UT\JtEZIhqyIW|GZ>JO]l|?sb, <(6HHHHHH$@bcys+U"]/?PeD(:_Z>_7mpM?)$@$fF}a3MFҾc1RS*c/3^{RIHHHHH 1* AO6E~Ѵ\rf߿OV_1\]p#X  R*YQqG ӧ&]dcL&}@pRHHHHHHH` DLCYbɒVikC&([V)Ȯ];eͪ2n(uP֟"           T P D1 !pIt5J$@$@$@$@$@$@$@$@$@$@$@ 1aTH$@$@$@$@$@$@$@$@$@$@$@$@B            H bF            (@,} @@L3 @'@bcHHHHHHHHHHHH&@b¨HHHHHHHHHHHH >* ~$@$@$@$@$@$@$@$@$@$@$@$@$0*FŌ$@$@$@$@$@$@$@$@$@$@$@$@$P PX-$            P0*f$            O Ăl! $L ĄQ1# |V9ZdmIENDB`structlog-25.5.0/docs/_static/custom.css0000644000000000000000000000116615077624341015176 0ustar00@import url(https://fonts.bunny.net/css?family=atkinson-hyperlegible:400,400i,700,700i); @import url('https://assets.hynek.me/css/bm.css'); :root { --pst-font-family-base: system-ui, sans-serif; --pst-font-family-heading: Bahnschrift, 'DIN Alternate', 'Franklin Gothic Medium', 'Nimbus Sans Narrow', sans-serif-condensed, sans-serif; --pst-font-family-monospace: BerkeleyMono, ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono', monospace; } /* Override theme's font-size-base with higher specificity */ html[data-theme=light], html[data-theme=dark] { --pst-font-size-base: 1.15rem; } structlog-25.5.0/docs/_static/docset-icon.png0000644000000000000000000000217115077624341016064 0ustar00PNG  IHDRa[iCCPiccxڕ3ot}lFl ðv")6mcgzAsK5~ֶ|_ޣgZ/nvַ*[<t6OW98L45 F`̖ VwTL%vO*yU><ʶ|u-( Gm]:/W7?}-{{]oDK*QRJޛUIȊk1|O@x'rv\xz~xv}jr>OwW7G| @DCKyOo^)1H` yPDj#l$kN* %y4 qxLMY.nO355'-Xb.>R*!j q.8*f沇OjQ)_YY^&"(es9TUex|mY&N\LÛ!3| '2+knk7_~(@{ygQ:RUUFe+.k+ sרWLvks3>~49 e,q):< P>cLO;;&_҂IENDB`structlog-25.5.0/docs/_static/docset-icon@2x.png0000644000000000000000000000417415077624341016443 0ustar00PNG  IHDR [iCCPiccxڕ3ot}lFl ðv")6mcgzAsK5~ֶ|_ޣgZ/nvַ*[<t6OW98L45 F`̖ VwTL%vO*yU><ʶ|u-( Gm]:/W7?}-{{]oDK*QRJޛUIȊk1|O@x'rv\xz~xv}jr>OwW7G| @DCKyOo^Y~kOA1aaTX? c"0މ-JS;-`艸DhJLQce {x"z4j-4M%JU4ERH0דvhEP|aܵ% QHP(mvqm\pߥa%1ы 8˖5U bBt"Qr%50rI%uS"y_N4^ۻ"F.w~4;ŴCMov@'iUGvZu(3?faЋbLz:#c;;?Y?PeOī$X׭w.<6cدo5_qqJNd *p!4m1e"d0|܈(&9?ЭAvMd-ɗϞ[kZ!V!|hd0PAB13fbhАEʱEZDH$qEA "!V(ɴ۝eA:#3 IVx&W*YUp VldipcRqBQBd"q%rQ{D ~8B0Y=6 c{`j* \љvZ=>[pD,Яu1gs#Oz*u @OS NzAr=ׅɁ"΀!@s"!~acm\O|Nw t9Z˄RKKRL>Jgz "8EBd;Tщ>/Ri<0:)°GWϲkkzNOL mvc"Qa4BNsn}?pTڧ?O~l@A#@ʃF(7 BArDIBKfzS ^2/|W&&rTt/"aqMoI]o;vy#&w] t La[Z"j'@M 'rL0E8h c3ʗ:(+6npz٪8K{~3vo+<]x˄ Y%9j[7C@zthOSL+BjL;E>p)S/1,o {HWR4|g4nŇ/*ܐ01l\`xn6|& S-??F_ 3!pBevjx.;/u1ȅ w"j {]R{STLE <@xc`'v½:cn4i\cJMLHD)ѳMKi!%vxVS-]GWT7jy֑怊iK xkGm ~Gj݆1{Y V'Q-!~#JMC҆Q Lj4iT(r]/V6׎\]-LC_x8?rkx D}D ˣVHr;. w⚘?l9ttwl6.W'{ xn"! la!%ZL%ϸsy 쌴wqt5c99dʖO@D[&GK8ⵖrGh^ !zI#YJ6:ZǠj /zh.a#ΝS]ge5Y!V֝J ղ.+:8mӦ%+RQ``@C8%0vԁP\ȕ&/@`V)kV ;bcS!'d Χ' jw`˦^a&OMl;#po&{c>0Ul-̭2g+yKSR}`恀ah8n۲L(TOj0]'t8ƀa ~ėy;lRsyJz Ҙ:@2;MrDs&э/ӈmt$j kplS,`(wM87w;Qw3. Fͩæ[eSpPH-uBm:G,&Lq$Tw?o\UZIa!uD Fw~>#FOq$?8$`(>+Y'r%Z Aq8 lI`Dok|I} A) :r9:!AIhT( ,*kЬtwKиe۝`c8;Q$: rtZ3e};<;)(@Bb܋H6 oqJB60W+:c){u3$v- +#$ץwHpK?mۏqylv*$*- < Muѫ)R{>G$j4UR;AQ.t(S3ovfYAAy+5P guSB%QCTCWu:O,b-}Uw;?J6^5!"A%&vQhu9ٔ8>۠?'҅q"hP(d#f!ѝN h&wCc6g+z,"w xа nÃF? Yg6tYw]PS(E2 ) cn#dAim^ͺIOrF[Y~o3g _60xKrG F< K!_CƾT@5L70@;QzD#i]>9Wn`Zo=c =ZX {M1/mEO":+'FVIG~e wjrqSq>Ƙw 0s%'!u3w7fr~TQmr7F-~>0tG=niUGhC6AkNpuly?;BJw| /؝dukZI5qV3(*mƹ%Qgzz:K_4PtYGPn'6BF١wk ^6G~L@A :H e؛HᨇYWokzc:'ǟThi d)Ӂ"&98n*F\}kUm'ys>ѭ?Ht fi!4iv*+juٗҜ"і$gsYcI 7N&s 3~}nd 1sʓq,YM-/hPkkVUu7bYՋAw=&[+924q7 a͖qy/wޮE|ݭyz!G *%aCWv*XDAA|vrEiyDM\ux-?X?tKr2.f LUv@qJ}7#|:`E3uvrvbL+(ju}pZ,gM ZAK}KR_6b7j9;V6 <פJBMBE}#n.e O ;̽q?[~ Q6 Rћ5 :ɉ]0:5[Pܶ%,w[ռ (a CB4cHw(\%uEAttmݢ`dGL腝܅GvdTm7kg7}{ t1^fQ>gtjrwr"oT97 =[sc&}^26㝁!zwI61_0e7o;qo0=;E>~/02007)MySU`!f9`(x=z l #Ւp zapX T.o#gJ۰>1l' 1qFiz Um-Zh#HsGb41;7?%@t[8-X ڍ_ OLSA('i턜+O)nÔCY,BbeNAYSGTYb\'*bÅ[GYgAK$IzbU6"!2kY~^K:nY>Qϼ$xǽjOH7㚓%µA[ [ Ts۪U{'ՆbG|*C hrcd9hz^+ZWZV<d۳,>a/b0reZBޞXBŸ!vOw "ڣj1/?as.admYA}Rv2Kbm^b`?-׸Fp0 7*e\5b-$U-!b۳?񃬦7 qJp=t㺰?.ϭZqm [-~2U6;v­~-0}=[墂lP*nLkfG;"gzxJ`onjޝ۔I<^G$~J|ûy ð!z"~  ǸڢP) mPo쀖bE!>fE=}L AC9MVc a[F"0O?\DNvYҝDVdCRS(}pn0`H8i^wXF4ޮRTѶS:ߴ*gif)M`@8wAV`$w("=b7E'HFKNR'[ R@;nЈwqaJ.R@4BM+gy0Pp;X?^{jq~).`Vr.!  ;߁ʎv'qD~7a?# ď}t )?^r@٘{.~%ƘvjL@ts[C12jXbn oF|ԇ% "@q8䅌wy.H$`ck 51|~G9P-rǷn/_ጤ_{VŬYבK0- a e ;;+%PZAZJ18Ӑ%F7\g UM4|rJehvH2R$EF(vAZ"]We?P Zo^Z9L9sFNⅬv]G$g%?(Ɂ{D|gEL 2 u'|:Dy- /p)\6[BEԧgA ?sS(0CgqF4t B<ߋyMǁ6z.k{4v Q𶽏B 7 x\U-neHA^BWq`i Dg2(.歟`)NA*&q&uckZ>AзAjnea>oTnM?Oa"V n$>ECjӆJ9jtJ儸a}''3Uj#(ӡJ!| 9TrvI@ ;P&}"3zR mȞJst2`Ha ];vhN¢?&_$C 02; ޖXyaqbb<ދ/Ǥ:?ڱm mW  H7,.9a?TEfqWkۭtbȉw!w#q_Y<z>A`Sx1!nڂoIknqZokKq$=y= a ;ktpσp yB -f9-jڒ?yy|eE~HݪKЋm;{1.8v?It{LpMn­'qѹ^6,.^K今lA]`pW*W9҂+Vý7 ;Roj6u.y|?T݃yUΤwE*`OK[]%Uwߐ1&D%Sf 0A ]鬹F$-.<WT4PKOb5Hr>l܉q(ι7}—9Lo:S=G${Kwٗc뱠!??#}Ç* $6%>a} \{﮾1-4(z:uZ{LAۉuex?q5 9>(??:Be4N0J8vN93ew^/|!yR Lʈt/+ = ߍ:.a 6E:a6.gjƪȫ/ͯV ZFc>p 1aPJguPqP wۼ,av5s ,tz cB+-x "DMT2wOOz1.^Wau>ZF܄Z(E<̹b,b#\/&:paC]9UcXBWuĎA(2= e'=&Cig1#OqĜGgt !|CʍMǍ<@@&DU.m-dmdˑۮ!dͤl0?9exx2&"#Q3d,䭛W\fc94;8Ü{,/e|_j,lE}7_#f\DME;KoaХ;ն0C8R-p6>O`ЛZt5uL\5K㈬ z=h }.傦;]LJj4v̟N6j[#$&,۶W}Qq[4G呼]Aކ홉[T$n*7t;q#r1 r3|%!"F.1p c 8*_/z8+#dQtQGҥoA>x! !Ub jy6f|+7JCގ iNnU3Kz1\L{+P_$/4J3+sa;mޛFa(o˫DN" #P&?k/%{ǗM,Q854gﷁ^{$U)mwj#ۄ91 &1/`2`9 6}pi =(jyv%6QV9 WnbtcSx]7R$bީUS,-ÚnDYD{e(hW7R=20qyp?=P,[;8UH;L 11PHZԓnij qiX=iuXcEOOQL-Wdr sda@أAa&3ǿV4,94C~8٨qF4oN q vAY Qr+ ~~ΥuSmt1hFbnƱX;-v\v*[g^"y[qI {hU3.A忭ώ6V -@C `wI`&vW\# /&pjc~ܚ6`]SNj[g7O,% 7C;KRydvPq=7͆ۤ &gwPyZ1L$,CطpX&=Y>U4Klku@?Vxoo&ʶYH3l!BJuTiY&4]ĺqc[k2TmhKor ~)@ pLTMbkor!e )(0'C2Zd2я)+~Қ ])fa_Tynk ӭMRb'Ojy"#/5vNNA[\6?6 0 =س:XpMUpǒS?ϤU |7p㳯D1AP]Nh?`bD/.Ts=):SGhu&p,ﭰo}A0pQQ/&m d7NdnCpUG(yvEN~Co cg}J ^͕gHT_mLhlOx XA^ sK?%;}LjȖ~HLv EEhx/k6R;Wia#%_1?623G0D/]!;- c/#'1: 4bEWikN92%deԺ` e,fA?9`U `luH ie5mqU PDZv<^071Gaj3\d3E=j!7v~G-mW; LvL\y! 0˱Y~zdpM0-wj뙘}뽢8O|;K]@ 7F}^(|BMkĉїճ_')8dw6&P/Xwg2`cm1u\OJ:ĽK( P)r%t { i`g\?-=D X8*=x/ɓX:*YO\spr͓MaHGpS(qۣ(Y#P( U|/Sni@nd]9|yzHÐBuWTzk#(E.Blv$oJ޷YAo]y4h5[C@2MpjMAK<R4o+pwSDq)<κz5J(PWߋv 3 7^5`( ~^ZĆk ȼ%lUs2w6L1URo3g'/J:'klu#e`vw7x8dü+sz9ϞǙl`[{+50Tre*z.Kk:Ӹn%a08 |<,U8Ȝ`ޑX̮q._A@\+( Ԗ5}ș&}m|6e9nk<+:'lkS$tS;IZT1# AAOE 'NʛpY"Ѷ2,жMƴ@ 7bXR]BMPyїArYW-3W\=1q@2MzIƒK&mA}-BWH@Д뺺AUp:Hp!NV0RY G!"JYGdrˌ>=ې3Rߢ]xc&D^1a.@Y(Ɇa4ߓK?hɹQݑGp"kT{Vm{"ffgŌ3 /Gt'eG[cQoXnB@ׁ.3D )=)08^!b9[mʘG|ISw/ FtԌa\;%.M&Le az;ݽMckZ\seֲts/}㎧Z@E?8;$׀>>*0XD<'{j_7zV !1 KJHNt0,Lp5* U^,@5G#:epM1`|aNc ayfS8IjV0[!CT'I4!7rӓ =0YY]Jxp~lĚ+ }Z|˟a2Qў?׻̟8ۉK\r4W|C`LsY8({^0<>!*_ݖRv{8觭PnW"iw1\I蚽m߹q]0eq%?ˎ3eWZDEEU/jᒬ ^R-K! yA~B{>W H|@1 L[INM󍖧^ޓلkG xopD %;! ý-i^o~)[\6m[2/vGg+ 62=';_mPxbN)ٰ~iP-mM$W̝Ԣ8m}K R+PfDbСzõe%[zHXjްLK #)tQbtxfqM ,OZu%R Ѫ1u#)[ҌrSF; 4 |C$΄2a?qs`͒SG†7"38ڣwI,Bv,2oY^ty޻1(Vr՜?)|=)P +fV/B&RfK: B8_{X'-,6) [`alma(^`4sl`t[:W`-\FdfaP&<_+m/\`[&SX}1F[@ jg1l6%RnG#U88&UV U)7+Gi(Vndq8ҼdG%Ζ"&yc(PD@ue+wt9EakA6 [:gPI,# u 17\ڰmZR}e-5N1iUXH]1,t?ρrD()lÑJ(ojI) a\ɮ\%ߒ-t֎E xiGWg$c& hxa]dcnU-Ԇ1Pn@g+/!aڠ J*JDLO)*h3Fn9W9ƲEE%NwwA[P~fQDAD CQ+11!2HƉu|_"h=?{vBNvâ#+.P8Ai3&hVP 'yY !#!HfsJGOORzXrPĿGCmUĹUa5NBr]D0WුBNe7tNjxL/Sgy i>'!l{Tv/Vu-yS9tKǑ(Y_edm DU1Uմ3WASprzQp^dcDpiˏh>Cxfx, Njba"M_Gנx{3&+2L|fݸu#}Jd}˯J_Ci|$ n.%C8hN"0>E_tzB>y{C4n"$kտynoQT̙dW-{f;0Ob7$ķ ,oq̭q\*ӫ&9٣|X5/".+Up:=&*.#gCb-o.'"r!8"weCnGN2' gaKg Q)$qu|Mmh];!%wE;u:yw&(9%WˉX&i̧xye'$uF<"|aL21 p'>OPrYh%9vbswqjfzZ9cx8ÌkF 93 )\B. 9KQSoLho!E)v9)+`h6tE4&2`gZ}pZ/n!Q<ᶤ9SQL,3KDr1Ho kD)ȔboI7Ň^o3Ют*1gDyH*< xj[Q/1K@zm:Y@Wˊ`4ɥ: CjԌ% 6>eq/l+'mF=ߴKAiL3ͣ+j +ln7dSOY.a&\KfzD/<IYX:ʀ68|([8pF@`W୹NKW۷ Ym9[j{V]K| U( ߂cr k=sπ ? qՁ4q]G\\%u Y`$W'sV*k r Vsˤ<5̐bi xC,Rqc95ZJE2![`yɠg 3eBZƀsUѸsMI [XSGNfHP̓Y?!L T4WOBcl T}c-꧅< ,ϹӅf:sOOqxnBL[P;PvA^.k]졙@ƿ;At ]2uTCaU²qg)pߙ';<p#q蚻9UzrJ9\G;(x*0?kp(ىع ݓ7I;" Rh`/(+ ?gg+0̛Xsra7D<ګbυcKMD-nc Qj/[ kVؗr,۳g;eUahϱʪKy$bRyt.^G)5xx\Xq6$׋ؠ"bF7rxާJ>M. XxZqA}@LKAWDPղ!TE"HH_#bƋ*^^Pn5:8-HA̗hKCߍ/"䗂N8wރ|@`6 f`dk ^rPwqKLknYcGс|vq0dA"2I#+1\[@Tx)%Gͱa u;/IL)FK/?ߵ@<4/׳F\JM*qY "ÓY:>_K;Eq[@,q qK1⛱&o/`y]qMQx ̊6ܲe4v-֐lG,OI6;K"bVHQ`S(=Q1gMjgF[掛!y=hMm@|Jkőbb ]U^ذny(qeRFR%mY $ 2(:e7o\žR5ͤ+^,6[SZa {Fqv흘D}bHs >Đ=aXb{"d+JʄBx&zT$ӡmWPyZ-M!YaTp'Vqg8~ [,I}ks }{%{pTm\bz?`c+Za,v^l+ PqU8Dv vn5^rW_1u Ĕ^N7KClrgp), ܩQ-lop g^-Xc7Y7Ev}my=ah\ܷ]1FۨђR GB=O,Q$A"*0KXy[\/1.#XA<_ (%1A*cN,G 4]@,*g $jѯM?hY?*aG8V jQuq׹g A .8AT"Y{,"7aqE ~1 Ps#]2K`0F)\9¶I[p31v5q? RDteД戨 k096C k7GT=ӐKD~CG*!&Q $[e*E(w +S6pc:{iD1fUWX/%\ER 8d[`EnViXHGp4N. w;ʟ'[Ҋm2̇K'h盕Q_̴Qqd-!W/ZA[a?,|0GI 0yF)c7#W$ZmGKr2\cs 5  +K*H4fܟDn(z ;rjQ<@jp3J"7F% Є<qR )i\&.qJ=/Ť5| \zI7Sk٧x?n@= ш')0%şDbMhU>Sڭ*2H":vE=g)k)F:*W%P.cJ"+ Bjy)_+!1jMmVH9OhvSDVk|LdgYVo {Y)0>_A1Awoʶ6QrUEKwiȶrV`a|aK ~, $2 .q|RH$RCf%ac.Y*ycm|Y1-Q3x < q[. Q&Rkl(= D2m9 (X `S=! 1{${Ӥ=(Iir6H'!í>" W.c;K! ̣x Da'D\ZޖC,b# ]:e[/*3Q\o< H[!$a7޿cnmuf]R*ǴȽd~ b 0W((ld"v b6drKVR)ϰr.~K$ 1 H};yMC1&bKK-b@OPK"Qܖv=#km n˜%p@=$ǫϊ3V2L`uY(ܨ"^ !x8įӢ`PĞГ{ {D!~)Ԡ0N ҠJd`dVjHPj56)j1K%=t/^ |LWZB7kNKTCfMoba]@fYʭk̭ƛ?(4 p_F(.%IE2ɫ@Sܲwd3#rpzI6v>p/#ĵ$1Hܑ[b@Q=Lg0Tf&8;O qW]\ Y}uZۣqXﰓRwL)@M v+`!Eu!`;B~&@7{*^VWwd`()~u5yfl+lG:0bD /"+VFxy`,X˨<ؽ3=#``+1ÎkOJu}j `;牃:I{O,v)vESRvmJCk_8Zm&oE!t&Q߀^{'b1G,Sp/S^j5/>30BaDSvgtEC=v{Udpq6mJNa[]SF c+74v3c.)2xV,3A6ӽy3gW!G5"k3b>m&>>̐  #~ WYF$0o"qT_R8Z`W!FOn@ P'RPͼKJ9@:,b(Be[„;:+.R_uؓ/%NLBҖzD0 ۯ Iwb>M3oùyڡ@[U? >@ 5vO Uo*B 8]Mm h3-O܊\ºCN?ŏ];\HsL-E@Wmyӈ::JuL\Knm4X!ހ0Fxe3پ`mLXӸc]e&3lJhƑ,d8EQl~ }kפwfd }I'/7n7e#%ɲy"ChJ3rI83eiOѐ aSlF "lѥf^u%-nzِDS\$h\3-o_$ lywS ÛFKa7іU> u}88Dj]|Qr"S ΰKZ엞ۀͼPr3jZHZ!#<`NZ=TCbfu_!pHnX )'$ewBvY^{cpO|F19!>bR-ve˶5lӈ<y'98M ~v:^M|+ [F)t SL٘b`}%gː,<+DM J';2.R!#0b#D@&?Xp!'\l n8]/\x~hŀcT`I40c\t1 CrF HA4:aEMD !>JZjA^ # ? ~U]K<=9 1Իmٕ*i裳Zv.G/aa`0/{n19Pf7֥jn:E+|ES\W\MֹC I2g.Ք sS ~]\[Z1x`!b奪R@eaJOS[S黴V@LDqI"DBx 3!iLS% 7E%lj$#EtʇL6ifb_ [ [$ "j & Dw lnEӾI4h ;3iK2mdc* L08_\u bLX(?+U8C OQz@dM*$.+A'"a;reDH"X =_aFQ"sl<]8 ~qmo,:h5ީEicڈxhXH8(wgF6&fOCd챵:, J{\魴:kžz[ūSRQPONML0n(@q$ 0FHAQ"C9BB'LH}=hNc& Dld!AfĀD`!· F8,mH( ](MJFB>:62.SЕA2  "@Bߓ Mؙ73wedcĸ! Q.D2 +QJ)R8?1PBy|褰%g%$vHҎpy*2DgH!beBaF]RY^UjQ~MIEAFF4cޢ"b$b!B0'  ,|vz"t*ze :yI,{&lĵF9yC|b$o5w\]ľ *6FNZrK1sep}3lcqIÂVLٓ2&dJƎ K׌\[ZYXWVUT~S}R(9Idn3J^$/(l4.Ca qH  `$9b!nA#ph%`q ^(0` "L`t`aAH9@DSeei$b [:c *Lߗ^tq-X-{Y^`rÕ)+*}T4\fc^TA'2#!=q3@(At0`D S%NZz}k1}`nQj@?%w~ۇb^ @7[Zb^%>Y٢N>xe½ɝe@` ɒ(05(?M@L >M@LW:ܧԿnÏ,>;_ ӇToRWno'x96wN٘܄MU79 =[ V ݪpo xnw;?jkIZuc+WPF6Ij3v m' jlQq@?=nOlV&7;9S'b?g ?mDgXq_x T]QY9_*偓cã#?yQ'% Ju#$T#EI@䴕NwQx8)-*eeuځ\ /Jk¡“@෵i?8IU~L5E 5D( !rz!):1EG@tM"GHUKP#U*,]oFqb ƒp^ުSE'7w O.j1j,"&(גZ?Y78R_?I}߳a G)Z%er5FQu{OlVXPֶPoֶTҒ!Pג!FW_lrN?OpUEm/jTFm/*5}\S-xu`u:!4 @Vz+xbxia lݱYs>PrtU[+../a}Pjjj a@+ lQ?UU X襸k. %]`O$sP?%VZLi8ʰ3%OiQ)bfg5@?mC?ɧTKC{eI}:ޔWPܩu|F*vBž>6wR YX'8`؉8 yAbZcb=> ɽ3OBe?Q́r~QT;:HyA!Iܙ춏!~ żBUv:1 ~pVUxSE4DI k ex$gYMH~A  A,"1h}9%aU;p|cod6@qjV3xym#@D8fP%XQy Ē +ހ-x˨!>T[NPZI]ܧv&j͂5ndц6NYu֯?[Gdqd Z2sIEX1*l]!B0;``L21K|y ~& :J L_Tz//.=QgGݨ²a`'?de'N"1X9 29=<>!$M^C\kȕqov;hqr?<&q.^RFp @+8l1bdf!wy nCsjt:ƜTQ7dœ4CNOƼoT V܈Ə<pةb|l[NOx]iLdenܨN./Cwl8hMڰ}x+]}Si1K \V b.u^V;4./E_Yob;#z1+?6r:¬xg W>8 V؄T)]b)8iB#rc4+z 27YE$anDĀ;lLSa(fBquܘQ7U~czu|[Іb ky%^)-3#zh!MCV31 d."WrhlG]+E\#cxtyza ;r(GL⨭ܗ j$t)4]CRP\;F{󋟓rx26Ìk?C V@:eLqح= ƘI v*s߫Y[CpܴE@X a!5!#;@s)k<~N gxpэQܺ`0uhZ=OdgB ܪF(2n~"zSj34uhoM Li0? \ uB-O1RB )=hZ88Udt_"m1ocPTQ4X|9!',W6Tx@vm^I.v7ƻք-#3Ľ%Or ,om1yW՗xd ʘZҽE ގn-Ӧ.oxզ`SӃõ8jBZʆ†޸ Ak 3 Z掽mG͈ٕ&)aq0; &moy$8~0#AS#/kF Bʧ. 6Ζ%l#'8)%ǩ#eG1Fn)a}Lf͈;rk-';@.ū =]H%`!|pGct!6 A^5HGz+C˲:Օinnd)]]BBњ|]Gm~'zȕ.hnHrϾ:RB=8C`Qu9w('́3qt3#:VJ qx(b)=O08DZׯ;F >K2@ {V%h܊BiyCQگ *L2!0(ebe/F"8DH˅Fmb؃W${.v D]VNEZCr)J@gS Qo5!z۠$Ki8r-{QXN1#3c F;mV\*v/]P(o]em^g`I| "pȬq#:U=/uQzӣ{OԹ<})"/R.i-hɢvvi'0;= r2|>8% 6Y%-[]JZ\ug̸'-S`'r,}J`򻚤19q?F*̓l4U;h7_J< ބ5x `3$ 77vb3'xQ50k^nțy67~#o̠P ,a84 Qȝ {(G_3C3 |o X~;q:l#E.7UڶI<*Hk8בBe!k?Ck`K4}  e>gYek^w4(=lyS(8R1[!魧g>gݯ UEe ؤw"?qA3Ή tG\5WE"Ib *EhguvN2_RwQ It66]b=GM788ujȳYVAӉXk,盭n>mZ񪖷`\n[kVٰx@*RbByF]loyn 8ցo;Q{[q^H"ys9T @ŐqTzkM>{{*KYw:¹ \BG #9j1 K'tE'¶YKsߘ9/^$&yH=.#t;v^iJUV747iml\ ϋ RsJc)^@I t^>ƒ$a ["׸KB2 |P&3l, 3G9 i|O"X}-0V05d.֑ăD!̵/ӁA6 b즊ŘIE @ dJB#>QGyơcs t ;q%61%kcNe,EN'oXʎy̖wCjNq 1mzk [ɸ \NGO \DR^5B8ˁtiq4ո'|d %3P(d)cÕf*s'pC1hw&>@,9ǩS+)s#y9n]M^<&йZ,=a2nMyiL.<.)>z \CR>Eu/q{9m?ыc6MZ$lzrEVZ|<`79 G_G ^Dc˄|d>PaƀśEU8":ґON0v7CoQQ7[%o8 wݳeP OX eX֥O } Pf4}$߻@S\a ?Gm/PBya"3@Tߒo9F1,_?`qp#>fΰO]F}p3k1ī`>}@'LM'$ >ɏ#,AI:ñ8~0&x^ty"\cܳ1۶qLTDۀAS594h!B^@e&dށf:tb˕la-W~5X`fV[gRpӨ1nA~1;gr:j>HUtƟU / q.\Z+6Mz-sFa 0W@]FX@{FsSvժȽ"64uTA!Á Gtr~Pu ڨ[{X+|Ll@sB@ `RX:+h ARMUubKۋv . dk뒇aDޜv -uYH!?~&(W'Ґ;߫Y ō}~5Ui>ڭwmq2-,sQ 6ykci/G7Hh|8e\ya1S  ?eRowo%{I<n*UMrDrхlՠ܂cvg P2EÅ^`p7"sW=-&"nI6ybu 1oD9=zZJ-v)%\+j%ޒ#Έ< EAyIKXyt\ `Ä mbėR`0TQl4+ ô\ ~w@6,p< *_ PD/{+".E($V2߾3lVAKlbJ#p=uF'<:^Vo?CdE6s$,]ʗ*켸沉\O<1PrZu+kc3EC21 fFXsf0k85+?;n} @Vz=w"dVvF\02YN &Oc̀O +(!}45E·%kFD eTvQcD~)gkm38 =Hl:U)Mds |E^y]*?VZ9 isexjG,sZ3|E-Tc5^һ;~}(ʌU]U1C@bs,dgvgiEHHx+?|X_' pb!E f"!.dR8k ]vevk%:Ey΅~,`@ϲI}ό2p+oP̰ԇx#$ LPL+a%@ޗ&{}|S2}@k9' Ӥ>9@"*3{΃jsuB,{$Ѭu;q' xH/r!%9+yf1jip,ADt2M`pp_.PM04EC~7 r(NbO]f[Orv ZR8761ȦM18>Rg0MVQHH toi-Oܵ2 ƺʔގи(QH>}Ʃmc)☜W2NI>Y:+C*] Xg"ZcI4#yM(L% b m6`\焐p:0:.qS &B.U8 KS&rx' !fH;l#a^u]bz"wYx9r|n{תlA `>c{&I!-m'ӊ!(cR 5C^-S,kZ$3~1ph]R^;]OR50g{hcȣbyCzn ŠǐFOr.n/׊/Qb`#34'R]Fd 0E 6f֋Aw^$TDs7dȤ#=玞Day_Sc]9p2|t:r \)zTl =ĺ?0}h8 K@iܑDkr)ϴ|L6;;ў vO xP"_cx: $Y<U UB6eF`'u Jyh@.k;,dnf[A 6n*,®L "@]g0_y7= Unpn&Wn& xE/ 81g'H2+y{2 wm7нTh&{#mb "aj/Ui*$64 Z y6z + >(R\wt(%vt>$Qqxs˶=C߄ ss p|?XRg( ȃp*]CEIFĢvWNÖI78#A9I7Ljʚ; LgF u]'Z 0FH- a`f*1t 㑑.a(92qmCo&7EyC !,YT홑y&Rw~}-i!/zc|W"PI8B/#+3Af65z^!fsət{2o|䑅Oz0 {ieA34 {B{ۆ5kon/d lk8yi+}ܣ. +[4A!5Zxg~dl`nbݣ\x\̣t-\]2{!'৥Z8<2:! rȤ,wđyHrmwWa8k" @Y=y6dO, eɝYbY8dc%Fs Dum@1;xL8Ïk 1Nb_݈#Q_yjh'yBCmr& P`WzlqhCer_ Qp>A1['<:Ȃٝ@nQ]Ig"h_6qgY$_9Q53LM]$^24 HY1~QfQgCZ$=&=Kbk (IIH=o7MsΒ鴏)6#_r.[oFlA%\d])4_:u *ܫdNKyGF J7/x^'y Y # 7J D+$lZ(  Ӄ}e0l9OҨ}x @Gs|)&̐`dyL6221&NY3i*T$Mi`du}d+2ֈ{ H_jeet鬒@ف7ޑ<#|,<@!qch(2S4|@ê v#L3T"sKn=! w=3)lqt]|=C,dMa" :|OK7'd kZ4Z#SdcAWq*h1&[g؆6."ca$VN#q,\̭)U˭c俶v YCCg9EMlc'|Y~ XK_s!p= t_sin/J/΂-\|aCG^Y0+IA&^W{8.ܖt:7ѼB @<[+FʌXn]b|AOxWQxޖͺ# 5\q4B-bIƓ-#vٺyIT'mO|\%[y2-KХw% ($//j< 1<;RFxFHl:] pdLQip6/OSIRxE&0DwgXIޗxt93˅BXj/XKe7L \{#a e3{]HnЄI&!G0?/{jIH_Ys!ZxwΗQް!|8 }Uc D0'\FU.0v2DPp_%4x ǝ | %8』 EIa7W p "Á ``0_g߃bu80 ?`DZ-p0#( R (fe Y2ƛAFIM1ٿ^)YJ){v="Mcs#1S8:@;T+9bפ..J:^B\N#uA8`jrq܉B8XO75otl >Ϗ}àȭ1ېx`\âZ0h 4fw4֭ϓ##z͆ȍ<(C!D  \?%2 tzPqaK{Vھ::E/Z*CJg k5k_+xum#qq1g7܉bhj[_ju$0S#2XF6(b|J(/nY[bѢٌv ZAjEIu?V $.5,MfَâgwRį !\TƄ*Bq)~4uk#cuAY) cСwxtMX8m7 [a6lD5 _y3e3MZ oi ̓몲;y.xеe'5d.&Jh8Mm:,H k2OAOaf% Px (h֮^(k9HQpQCo;>d]W5T䜎 A}xo]@6)gI_! )t&xڼjt>&4Ly㬼CoE;$ֻKraIm^@x4~jqqf\*-rqr}- c4H D+iEBVM?vMR͖&WSVR  Iȝv&f8 x2Pcf[1`S O,mfOh-H̕ KN7V&e#!aHA Ԃ5 ]_Oа5̐}#ANn@k޾@ٽ6)][Y4+ -;dy֨@AJ^U!Q9QXop)qnjQPlO>7{P(1ݖXxeN;:%Rb|E7fBۉ;a3JgPݤ@kLApq'j8W;CX*x+v`28&4Bs$&& ;Q#ڲOp⯰P$)+6ǑU``9ڱC9괇t[a,ha#[pQ8yz`$ pɥYT@ 43 Lh3%4Y%֘t`٦+RhTGD 0Ю=ZAl+ Q<9Lxܦ8 MjR EWŁ[3VZRC1<kb|9 fR kM[Ͽ20~BEv9i1J60`n. B툫n%m>r_)ln <|xwo Mi!gvf!mnIe6jm ZsN sM6׸9F/ܨqdW?ָ޺ghiel12g6gܛ5#vGyhuw!x7Z`Q9R}`9a 5Rs:ua@v" tMx3g`uùM ]JðvGs+A;n(s: F+12Y|]&W&,Ź|)눓0Q;AׂVM|#|||ſLN6>|9 *cFe!cl|InF.?c4-_PAI&W1z2G9_X]홱]Ē^M݂z)s7(*Q&web/ՄA+#!Xr9jW?PŅldԼբ~.vEq]oHU9#  ,n,CDVJ4_RR\)54`!V%|!+ Y0^rtAM\HKPeM Ǧ-C@s C!M&c1!MT~әR4ZRGˠLcbd/=~ R!0f"F[]4 yM;.KQΊn3s~ǎvQ\'h'ZUZ3TՃ !׹4]&*⫍VpU ]N!I^? >P1k;=<: vRnC*:/M訴{~sVI\h L-5? wo<hH8Eoh8+(ֶq$SJi ntҵ%oMZki'y,Q *%14@ xnY]K fYekLTiJ`^\-${/3N ȶMʵɑsp=S{֝{|B\ukxQFsДt iQP:d֭mױD׍xķN/'*G|PrwVxɌok.CE["Lb터B8IpR9 悁b1[,aj#nR /QSiqVWRt /L@g#O6`;ܻ:)9.K!7GD~+s\hCh8̍8ًpA|p2eo9|1>o72deIX$H :,X&:VIq6JFlBm m#}oY+U!@zSULdU(;U>?=uV+~hu:NQ "VMi6-055ȬOM5.Bx\Ōp]O ba;7*4qAx ]@lSnlzJblU'xAvmK`+BPJB~}~))[Pz{U#:e!dZA\]vYicxtWS"٭Ujl|Cй'zFDyyuBV@ZOqvbMmw"jHP3}@ ײ=; gDΖ%\!YQYٗ1[8??1Z{7 A-n[1N7m%<_:(Vw2 _]T.{ArA8 6tLCm "))JU-43JB憬bx%[NVD {%V=}S5)TVg4-# }j Mh!x76fchڱn.Yr=E 8 Vɺ)n~7TZ}>[=&Ҳ%@'Dݔ2aQD݁llc>RM7h!\a~<;/MW-?.W^[ +ABQn_U{w#mj@R9W O;;v8A(;omona@*Qyi N Cc|NzŠ.u>3 B_9x.kK֤K~_.I`A'&=VW@e!' { kC.z9 ?~ (pDjev;w~b\>@ί"5cu!uBk,]A{ ̱tzaҟNgIJƍMʭ 4$[tXm/MZ LJW'A$3 AG< H0M,f5(bS["V50;=0$vuAsry߻v`hbΊ̠ܯ$1H:fWlbcDG`RS \pk;)qwGt)_4©qYQA"eu)t՝yFRB}~Dž`?RFy=Yk3yz e A\p,B"UWNGb, b1#8;)%q .8#tr~hl3d<1Aaژ'`͜0[No>5Oؗa;Wխ^e7F-_*?Gıƫ'?MXW9ɳ zuPI\hcVZprf@J>? &cf>Ě,i.A:czsX']N>?ǹH WK,: (,zY/hi"hdq&<@&X.j, X뽲 ߧU1u{ վ@WL M p5mZ=4fo}'VS@\AIv餸x/ a¹>ߠ!ˣs`6b9o '8@pYM1MCƭ̭0:BDD=)K ԜZAo<~rYT]3swnBOH]#s{y@?e뾂ؖݸ6g1Br_z %F||颟-X^g @MߗzwͩJB36)HD?DŽ;S^&.D SWl @Z_V`1|8K8F~>$p{~Bwsf;L*Ķ ߀,OA $$N?s`PZlXc<V8( E="!0=@&[U"KBS Q> A1Id"쨔Uҡ0;I3U_ EOZ U{gs"F\736>t5 1_ u \ˎ4 %5a4lK5OgD,su?ducV"йV+Ne=Ԡޥ:zWϦ*i+fY LH<伹@8EWC V ^ἪDneU~hUg&cؠj.׻׃M5.ZǞZot?AOhx0vFy:GeվM9ږ:R%!WKeS?$^|eT~ko -O5ľR _k?d`L[ P; 12+wFtQY9;APwsk zX5JP,c25QsXBwҝaNDkX}\Xq~FABFHמ1I@ 64^>_f#an.HœS ڤݦ.ƅ*];1 3F)5_;gvcC{ۋ }.L`a%UK,^V-HDC{~7#CrJtͷ9*6f[ZԬAYX_ڬ gL18~mΡ ;J^H$TEN C1Wqɐ `&ggӬLU.ݥmJŘDk\E!>pk UTs_aGHt²Nȃ)N knNy`;kRU(u&,Ol5h&')G3 sJ\[㜂':6kN0ax|kruEfz+{A+y,h3OzW{%IZ1FA˱wÏyis6xm?AiIZ`QO yz xzezHJsbaE_ Xy{c…tʄ|2^^S_o*>=p ~;u6źϕ#?%Vc[/&b[PnQY1*?S|~T{]5殸T'vcSnѻzԳLJ Q~о1J\1`ii. M)yA5 ]@ ^>|u$X\ޅd6n8+>}Ќ5A)vq 6.(܂E!a2I316 w 6=k*އ)5;/\ߘ|sk ?V I6; $[9; ?_`@ J+ Qƶ QPpWJAQCA`lZrG:ofon,M.m̌HPyfs ??qy(x?덜w;A8o椻`n-ڵ #H\ YOxEY(禂=Zz=ͣ&wH*ؘpL!ȃDJU87 ߚ$صwgS:!UcAoR4Wg9V?? :gș<t'8 q;eeɶf?"kR_߭$.BL|wxKfJ ;,APoW"Vkr3G&1y`e0[6zdf4Iy.ze.^o:~(te MlsFDM;&o?ydb]~eiL鬠 mA1WHo=ˤCp?#2nC,w8s9Ձhɮ7]xLWp?Ylho,ڨtRaІ@]-Zz+KFer®ϱ pTE?_.M@M;,g%9I6sӳUYj΍Gt#x 8r[Z|&w:u~EWKhdX\V0y귺ë hh`w FB8%\B?_tIJqZXmX8uy`l/lN, gykUނ>#edv>Z;0Y&L48kEVȎͮ.ڇW /16?c/O>#Ǘm_9>a?dw⟡O0,K\w*˰2` 2 ` q_*[E3M r#.XS!Ň-f ,xň#Ԍx㲜.Z0㫟nM>(CIBbPzWyUT g8 5 l8̪n9kQV S1PУ\2fi13Y]> ?|hZ( :)lNY Dw]4$ u^P:Fcȑ:s$i O#X"?Jou(l+\ =$fbXqL~ۜ'tj5 qSUm7 JtfcRK Q]A@T0}GBz/}4eSr"җ KjL%4LZ;ջb+1V R"QN"pwPpPU/,3) ڗ/M<K,/jCޯ O04uW4ᢨư'0]1b-u"ҘI+S1!"vAcJH8YnI>+rdq!&9H1r! rزRfOF|ElKr)cप|"ćhE!>SqcD?S4ucJŶe^97>M^?!I:6τ2~ԃp ,[R\]YHG|3fg!%^NbmIE4"!;2\bxQ6@Mqg|CHwKE01{B[刬ۈz䖸f^«]5_θ*`/Q]Jr[D -x˄uJa]`7Pw4/P !CB56a@Ted%}UjfyJӤt_AQ^Iet@ ޳YKkR3RWt`jR2vd']ã6߲V [x];%wuPR3 ,늃aI II⥶̒eՅf`ap81u* bw#$Ώ,6( ͊C y/./dMm6bsEc{#sl]ţnxӇs<6oQ< ;fx%.qSEϙjD'b@Tw=_,tEh$9(pCo`Z(=Lu^N [ 0= :^.\&ju5MɈ.FM)⍘Ke }u m\ٺ |~bۤ"t. i4n".amIWZΎKS}b7kJ*OO4BHt{wReccS<\wKuZ+T#s6RQ*ǁ݀o"rݞncn9E1C>yQby }b"vU-ءhg20Z)mM/QE1y<-KCD wMfrvĊndsbj-fQoq)[80`;_/^'$/ì\TYNE(M*V袍MqZhoNJuTZB5r ̸n8 U̙7̛3]RYs]*PhU58w'h悪c/gDLblvDT׮DOhkO:p@ Sr&.ݎ9oYqSll7-=-o -d\")V(OP) 9T5 wKgt'ck1ஈHN-RQ=* j.=41͉LR bma쁜 ;Yd暪HT;Q\}H-FJ1;V!fs#r ~}mY24ãy/*%b╘:[  Est}iټ،!qhK"8}M uY!(hbʝa2mCxBuӀD<9W5%qRc#Rv} =1 hj^,UE3\&P4$BT !kV䚃&*i!µV3^[ϭ l i%R+UjVn-,um&AnYWGט 4V֊Xp:"IHZ+CH%6W!.mPn;%楖eKueE$3*On݃,nSpekG4΢YQvz[C$~~LLIXa#U26׀싎U#Nʻmh (u΂zMPI3E*űWG ̬eŭv-S*O~}*l\/K@/&*(5'ZRW8;*# ۶lݰ>xϭl"wY xm,qbL4dnĒ+,6YCh»Νݰ 'L9xcA`KdHah thKDo}XQiUf`0ٔ>0;׵*-.A;EiBq{'! k;vI5s3Utm(]X88>jM`b_.U6ghThDdU< rD,jTδQ*pU1eْc88_buFkvYvW[rGrN:ٹru3:c\VS"LjtonKOmm1cb[ðNVg+]pAv)C<9g}OƶF)?kQBRTb;A]JYPङo:OWj>4]92ak܍[L{Uh2ܧu%{iU+8gNw^ĐOU4rϘg ]yhW_i(NՒQŗ0e>5tdlM/$SX>K_'iHk&' ƙmei55l(>W%8Z\ P~r eۥȺErRƾ1/!<7 L䎹QRC6OuHk f^}1 #5ݦXXw*8ձi"(Qf;rUy۾pNoҮX9׮5!Ò *18k&;gGj% S'%"NFmMkcduSeΨv0?DჸM:WE 3q&nlrI/M_`A9U9I0gTz&J X9"Gh{pn* `kuw!_@VG%Q-ƮZ \ /';]uڀr[ |-o][׺"Tuu{9*;2~1cpBXfbl l5ϛuMFfKqB+w7suWJ D2}FZfÖn')‘ qz29aeE?ՄeQ s:L]w48Z>xP6s'k<kZA=4`75D%Gwz"ь,rxAٌKyX r)Seek~ArlC-(|qQq!Kg=yօ{/t&R-r~Cߣ:-k'\RՋ7769Âk3ҷt"wU L ۲iI 'aY4p1`d33it 35aǫSf{=mTcTCMj(|3yy .S8\v!<\!toyhok$UW[b ǀl[- k9Z U@,W-+E<"ztxA!:8-lU/ M7%`r}/Mp$QlQQZeb--Ubg5eF(N [ތ5c%ki^RQQԚw]09԰rd;3W6<Α0Lc0$gJ)H<^jf(u.ԈXsmYҡnȟvHD kJ{nmXe3 E/(Cwokat8irZp2d>0/ wy[l)>.TUc+׆Hh?Y0qhHH$r5rYIL]LuVTAm64$-Ir@K] +kј7&jbjMNITI\n7.^3Gx]tMolmefEPr\XUQH{؊HO| iE7RY aTwH޵E@E:@%&vs2)^y0L$FKn(֬j1iQNfʔHmbR7 ( P@Sq:]caǽMY@N[3g0ưKJZo0TBHYPOc1t]KG4ٶ^mn)\pdG>Z9wTDŽlӜN@.זƭŔkA$;Y?ٶN;(D KN&:NotV\k֋x G}M-9$k8&pPd3l &ȎunLٜ!֞(2s`YtSlZ; W4)sTe#28 `^܂ )7V7sʤx6䞤lcaDzGgRbAIg~V]e1]P:CH9VΟxdvȕm6!8#yƜ\zX^ #,sI: B1El( ;?)vr C!-GܕDvhb*V9I_E򱛎h (fKQ,KʆDa礳 .6Ch||%W5NQ"Y3}; }C@CLn5}ZMw$HW4JW d>m^tػ4~w}+QnaVj5 "k [%gO fd;ʳd 4M[GXg0ּ 24|<X"mkPnT8mWAeM)gw5'f[f=cS2f-5bg}pT.=2W7+5/4nfw 5 'gaG:CX6mACv~Δn!D<1Ŗ˱&znd3ws5UZ 'PL#y 'X2&S2eF'eK!xpFq1b-<&}c\^_B4YsC fD X̙_"j# )m ]2DH\2K- Z.v+//(2p^:Gn2oU^װ&]OzѹLox:=\ gn,npiՅk*aOdዤb n/0A_vzGյ=$}K+Zq'DT7DէKA \iR!֨?އ7-^yҏ݇=sQFMe}:}%Us,4R6MS7vB\Ej7 tl~1ոM5!F9#"3vv87!6"f@.@6Lwe;32N(n'v;J}mXh!KwAΈ'~Y1 E1l_nh<(!f%wtB+];AͶ Liv!.U<έ l歋F;Ij#YLZ'Yf*6iR<村w k 5M钚1%FJWk+j5'2@&54ZG! +NS;LJJTG&Dۙgv])с8pSYf$T1vy''Z]'Dn,F \:׍R&M{mÌZ Ε'4'Ba*5){|^QI9#$S3F 5%~шb<#lǞ"ceqxckxEn73@4q/J ~k#  Y/1 b I\) N1e 8j ? WOBFʝYd!+c-.@ں;̰0t\/q|SB47)_ynbr#:Aa}9P1j$򷄏BCׁQ.3ԇV(IMROqbufyŇݜr[9:-^;T+`zh+LVz6naAetuhM%4`v5=0EEʵxǾ2 isia"(>a %Pi?hz{FFdĩP1yO[c /F˴3"6(s{qCތz䲱VIь:ɯRñ0YG`0OX9|͔4oxתoKol@ Z|7mwCKzE\>MN)9Nbvo|"nj8AZsFVr5#:l9O|^f)0pȝ Xd9YNxnLh۰zmd\s8ogi3WKY~Y`63 -ڝ8ߙ19JXn؂Ҙp9>Ptem|=q|HI>(j7![</=k5ŞYƄ_oKG~>.l̥{.ߞ36 Y x-ቷSn5Ўڞ,Ɉ[EuDUT,&"MDE,Stb?r_ݚ юFSxp<#Nj\ /]yGxjgy'%)ڤDCn,Q]65#V7׭㮬]zjFO/>r "[cDWaܶ|FZ0mX@VԵF*nz}/Lx;2\ jB7~tlER*mbڝ#jjүXJT |[*+{4tm܊riMMKd3\)j>3@vհ$誱Xy1`bKUǞ+Ҋzcb ~F|(qځfSƒjTMOh-%wiyQuXx^,r]uG̭ƞ^9v5V&ޑ]1$]%RxhNvK`N\Uofӡ"Jde0w N'ge攠e X̚eܲ lHFM`F-CƅN pLJg֝ӽe߶ĕ-~mZ(F n)Y ˶Pv^޲Dl\ih9-Chby\_F/lTЫc`f`aZ[%C&Mvp#UW1|t]K^:X3Ûې|d)Jeь\x :+JGF\Vj츜n<$wNqZof -0L5qyŪ{MA+`!$TNf&rBP1 WoC0bpzХqO`s"m-D2 71IݲgB"ʀv S'F t\µy904 2TȠLys vXR >h`w9jHW t?1Vg m9馚èlR`+ኖd̦;PkGLwmʽ_puj'\ $ieP:b@Pt7썥ARF aU~# Rb!۠4PՄ9B#/Ŋbci&$PUz8Y0BhE(Y|ZKZf`~𡀸0IJ n[5xO X%Lagh%lhaRI4s:`kPLg;,eۀ0vdM `+bE+ꜻ_]{#C[xBTH4"b|)?xR`C I^2$W^QU tZU X4C Pdӎ1b) b@ BS|hIuRM@vڂ0;;%lc; seV3ygr=6hiRݙ`gိ&q!{<_k:wX2(C.GQnOUdl4:I$XeY z˧~\MHkqʫ#8FU^5Jy] "ܡĻ\NX1)w3ɸ[o {vܣ\cᳩ B&:@Ў*A&XjfԳ s{Y;g?sEg0BNxJ/uW$6+^Ha P`%Nouk .:muoX8{bU;?GdW,#M*ab[:e!gƊD TvkY j6Q$iŽ,QG>1P_H.@:aEڃMs"HQɎ%n npgᄊ]<4];j5`z1{ }f +#ni&#b]f d?+s9Yn"SC"^}M6@'=*xqq:C\, yA!WGlu/V~>PwG։c1rǏ,h b,ˡP0 'ɾѴj @Nu6縟5O т,_q9A`t! Fq5Ճqvڎ fݙ18Oµ,-A;\;D9bP&2suMNhD룹($0F8 u.`2iˌe unBtT\<8Lă/"kK]$]a<_\6A0tO]:{D} ^*3v)D;4!:\p ~i_k vq)K!3+ &+iک.9[(ңHlvj%xk(G;ve:-jiV@qrFћ}jP Ьyt̕oE5˴UX||;!,oO]aͻvK{H0w߲ ]݁|&>!1saxyi/|L75}|+ؿe'[, 2%ͧD%SeQ& d_~n _) x-C0 UDD"*CPwb9g?07`#*tp0h{y](C#br{.T\a.y]1S0x7mo#hQ|be3te1.,:wĜcnFA@gɋjZu%Lኙ(>.. в>aΌ\v?>W [%r'je 5}l,!Z%Y1sY7!.6W0"뉴8^yt Mg&&jv-7*rថWH3]{:/u93zH+C覙\>hi@mTM2v*X:²ػ"9o>JJEBn:ʽcZ:؞#=ul||Iu}x˚Zxt py< ѫ.nͲBv3(a6<] Wk6z]e5V]`&ntte lJ|P',zuuD|H ~Y}YI} ma ] 7&a$t프F86nŻ`xs%WzP{}E57}}ֽ`QA 2ͻ&"}P [us"M"T+2C"12I} 2'@-r@ un:h AI[l L[O#b%07G`PY.SluL yKY( h{:]m'3`BNtHO=\d7>b\m( K S@޳A5TXrIY+MgEDIk7u,C IR_lw* >fuxq!5r;wF "%"ukmY $ʥzJ}SoPeDXkݰ)jn}zEF]m ěEKsWpK7' D-(SP.Y"Wǜ8-1k n>Zk,ZyUab&0ҋ>Jπ܍ |t45p<9(|YFvwJ0o,JXfvk|%EW8 \:ԺpT$m9Og# cU;MtȬB OnL !/@ 7#טr΁XF|g ! bfn3jAN)W%0B⚬#1uN1AjQ%x]ft``t8 On%/#jtuW¡%K\`>hF+Ƭ# G-"v29 sNʶD֪(mL4* %UVGIw$s^l![ږD٤O9 ǎtrWK;n iΫEggV}ڰ~Lj;gMchˎ9[D㈦ђ~Cwc=ząSnNmmqQ/ M̹A&':.a-|m(ha֪~9a$~oS>2u OHڎWỶ+|l& b8+$P],اŔF`S8FfUjnd>9a7A(m+I%t *1e }^ܠV-|2J|0q4 ʓ<meZAhgAET#g0jȒAQgz뺽5\sMnߍ!+RizK#X\ͅ,iFPb놗b<^־!)6:yiƣ[v.v5{lx' u/K:O=4C3Ս@b0ln :!z7Ħz5DZFImW׃ֹG\aonel P3S+fumXwա]G{B9uXY xNmyal}Hs ]EJ5(o]; AA\&> v"awBjmQP;nQ]KEVeۜ`#pM-)]n֢+ Rċ,ֱv~![`ZcCاC{he\|PHXYBnNK8N\Y\^ >qwzRRhd t3iR`-`m\AgoT%f+G`9jC%Km-o,h-=m\4 j4=#g:u<:Pq5+n9&q]K\Z̤>Ic{9H6zjxsL].sUwn-+ǽ }v(]75 ,Yl 㔗Ցذc̲3Fs^Vr[P(& QG$:^焄[o{3(_g g MKg ixC7G(@l\ocGj`P? Q-eCmhhnBuA_h%i26 ?02ގFQ>7z2#¶+DijN63Jd*ߛh8(eN[3lD]%vQ۴g<{wg.'. !U)+0n)(g80aWnӈ.c/#A1 ȇ#eٞ1h*m2169dn;,(WS#`f FK9RRWJ*`ӅEiq M|L8/WLa<5kxv~7H-ۺN& Ɩ%v|b P-y0V#5凉-!㕵吮u **-Cdph ari<`tN-=}p A(`\wUt77bZc"}Ko;@^3:H,vS,Ι%.\G}:ARS]?dqjQ|,@(&xt]PqHLx%MA[M:ϊajI{;?9t$ޔDi;D%2n 3֚>JžZݽRU22ҮxZ6q.[-ВfFCfWkB΋T ~Bn/)wG&؛8GFWmZ"lX*ʬN }ly#t- 3Բn\2o -EØk؝h%F7{^*&]NIQ&?;soQ_;B4:7l뇮ed>Z+ҷG'@C`ҶqB?36@A(!V &3- `(X'r;3/C<^vNͶQp!ĭuix*D |r&і$sC-MfBiH q]jKM)'$m"~R/Cg طutA5 %'>rn14rIM5q.Hq5(`@8b Kuk%+ pk5+F'vx0 Lm!xqخ=B+PZ8f56(*0ĔW-2/A >mG,'\PLD1!`7$ EiR. bAGR;()gw#L^71ɜx`}"nKk*9ykw0[/,лfnE55d)A"MxV1[jzZ[K228w[ܭZZ\19Kb3~vg(LC鑗 )y2^Sܷ=E\, " 1ن9Px副Rs A`7Nak8h6C G A%ks7y'MOg;Sp˼le6`4*Epk-@Tv.^:On3Xh>0GG = y2SxCN۰b[V >&Obyɕ+}y[qf '΁LmB4oa;:t6;󮬑4"Zn$'̛$x&˟9m@)OՂKz2 xGR lalgF'+.S:q{VtΎHu?f,1J~&2P#g6Wݺɏ|FTւ%I첬{|ۤ5^[CO˷&k梤Y}ڛR?FCXѨpءADmsYW8O~l[kWp QCj+o ^mM1 /xE{ JfD.0352|=]/sj0&^C~0vaF琏uYcr| ]WyWR6=)>EÛg{7`1HFT lvY]gm6@ig6,sK8:'yf(N^\[6szzzDfEY|mR#YжuL%cxOi!sŀu$p s t$e}[xl4]C7ywd +xqse~|)e*oFn&V,oe@O~ЯKŌA:`{PUl< SvJ^wJwZYuޜ69(H+J%+6;!)Z0514~@Y C|$xS_3T^J d^pn vMB Dyodٲ)LQ"r' Oc": 8v75sR cf V!c.Q`<6Z˞SrKXnRq9%pX!x\t~"TE~uz]7̜E%q6{J@O#WYM. HQY`O̚di`z`$2 NN VkȿxyɈ ea9@@Y0$MW;bE9ϿI7;?c]pɠ q]Dۈc-wCyfy(yeKx <;Ä}+h-/7zϮ+M5zI\ Ab7Ni]q^R@z~j?çkf ^ 7bC"HLd:GZ oKK潆jxTkΣlEWu s&v !%GY |mˢx 6Gy60g^f0Ag 8Cҗqg!E|yf˯|Т5It&UIರV F,s-kRo?'7J>Y G<3[`xE55\1y*~]"J$Z!pZ1 7=j[!D~JxI8ڝ7otd @^a XYFU$j" {w8SHgˮ7-\geމw}4T^(ZUK@F& T6/f/;L(J wlsGZIՄWL-f&yUzA9FK['IFq`y"6qI׻psZkGΠ,DBǸw'n..A7vz,"b;1auK GL8&ӭaA^Z”s_mZ¥iIlc uץ6یj8Vd]#00+EXp[%C;#yl$;+\Cjo&b@ PõSɦw,WѮٰw3}= Sim [bRz bvuN.x; \7:[4=q,ցMG Aͩ(Qx#^9 K1vSz)*ϥᅾnm|oبLl/c@8AJ%:tĬ+y%S\G󎭙πPvSk1=J]>F P0>v"E*G1VϧJ}UzrGXxX][Om(aOGhtpcC1@d\*>c*m X%?6+<#*9N1"ڥ p\ݧ"mf AنsTEPΛ~>A 6}fEV)[CU|BM",f//@9nzIyQ] ZfVI YղB#}B!M-=$2﮿@"z]<) Y/˳Y[CUW}h**bCk% (WQޖȶ)``7`8KAWu(Dt<.Bvt75x(V<آnSy\"}~GPh'(UuJAšyYNj^X4|bSgDkBMճE$* lRŃȰmRCk;ٹl#و[dE> O 2$5{txxeG_,˫؇MA&Ď=G"[?0OJB\39sȮhlb=]Rf#Az!,pQSպ@ZOGPC#u$lL1\Thmr5ʕ0&Wiv`^0Y=WVոgvW_='D{>H{ t}P^Z_?֣3]*UԫE8-F0f?m?}DRb/{#uD$=ӉidKU>j}Y߅Շ`g! K=]ISpM#x2x/g<9SD 3GSfˠ+G M "d{} ~  Ќ eqXe\<.]mTFRߨZ4pKm4i4,1  |p!ǎ4Ј&v:}}u 9l^v]GRZ9"x L&b܃*,\̩bQa &fƐV@>t õoWf2 /ً{*qc}.LXEzuB[6Sw9IIޏ.!w8790 p^(P9c7>r/`ô3aK%X o$ώ"~7Cņ)؞w[y/< vZ540 )LҒ s5bG:X ,)3!h{}/A[JU5BM%S:cwyn>bKUiw`pUj(5}(_|H'0(܂1x/@3s%dXIyKNb֖crwXgW/}ֲ*zjPa`ZabQ>"(e.qP4IA& {<}}^o/]f%XI8LN‡4<93,3˼ ڷ9zNAl?,pM(F'y&Tvj!BjrH&'< Jaa:p{0_5tc%3MǏ\v8_~P1pnN\Xo~Lg2Lx)Q$E-8j^D3Ѯ%YXI=ց};qq$Vg^-vÅP8q{vd)8Mh6bdAJJ{ Ih  |z6~l/D]mbXa&<~Iu.AJjX j7wz?΅ǔlY&Bkn>Io 1#7[PDT^TƂ`g(azLM{&R;I/|7Ƅ~wnT1WuʱrnlYQ29Bt N!*L~QoDL{ 'о ɝI7Ec7Rx7y^RxokY(Òqlᩁ@)9:ۋb T~h68z&='0"1nr%r5rMryYn[aj-6s*u,}w.Qr vGdkZN2%"4DžkoLUD- }oOsN1e1%22ƙ5+u F5m41TpG]ݎG d\5R2񲂣Xё4x)%?@=s@"R(R2S:S`TݠP1eCz&Ȝ_\y]5J:+uFt>cdR> uĸW DnT*m*l,UdD~8S&Zȴ'tkqmSpUtXx[/YR온8i^HhS^ T9op} 5+i4bfn ;"dSfUSQve,K\#ږ7C5.l~l^oascwf~/y!2qUIP K~Q_$ Di1철ըrƃ+dO訄aKeFb`0ļAiK4+&vlo ۭZٻ~N3e{"3okCӷ` F[pi1!H\2'qVªIK \/2o`e4̴IYS$CFhƌ^>;OEz&8j_t}͕) Y{'=im}ܖp[Ȋik%lʛ/-o]`ތxqeŗL3kbhҜe~g}\RY~Z3A{#5kKdiQٞi@H/Ѓ ֻq;ҶeI϶,_Xڪtyi5MHW^gXxQB&Q/3z0=c)\TgI1k_b{="Y?[E(kZeF+̚2k?ΨB+ ZW6 ֛1db Ygsz,2[/z#7lpQ{TӾ]ϠK5._SԶxeQE5&[fzpImEW՛M~e`] 7ZU^른o=T7z̢M״P+0 >fkxo#"ݗ1qBx~烴Px:&8'6C Z=^SDE?r{) \DK!P`{t#Mpk/rz#FGaS3ՀO#UK~P[PoT|a/dcoa?4=/2GE>iH+ M'z(jOYod`%zM}[‰џLៀh!\쉬{oDMHR>)/=y{'k+nZE$֏|z8ORPv4}^h!?`HБ),G0i&/4J^ihhgggtI {R3c4Vf ɫVhTm19c lAo`' `L´& /Mj)8EvyJ3ue Ŋ̼%K컅 eLKN ՚2-sK_ZEY[?R[5J[NlsPb  Z#߀ @_`g/¤Jn甜(TrQXBz?jS?R㟄iI=ybBWUekIbB6UTqȸl{= , XQB*dElAtсձɍmQĕ z+RB3} Cb.RߑbAɡ (jlOCO = = D \221°z2ȬkhR -89j;628ݮ䩱# "H6Z@ ?A%(l`GH`wpSl5xA2fZwٖrUUWTZp^bjЕp XpD"0@Kd2$5~^@pWCatH71;rKv)T)~Tuʵ*DQ0RBʂIF E:zA* p*$Q@{}[ޝ;OO-G>cEp@+- y~Rtv{jp_jRcD[,XDŸQFC/4Ҡ {@7B A7<)1+Q8ê{]b3Z `kSdE\)`LyC#S$DcWS 8:`{wp_dyml\ѽ}l̖dz˭?(!a"X[ced瑗& A0@pr!)ӊ0';zew@{jyo~5v ۯٺ ѭ0r-Q җeF``G<\{wd<py).$|7:lWrjВ ΰ*qFd%#,|h!i#^`߀'A7pVu~qɯ}~zNρ_}3BI8(fܠ'Z bBx*TEVŃRb@7yJ}[H}ÁpxS+Wjފ:; ټ #Y8&O!%2F$&\5bRא$S z=/[HV`o W;>AګLDk"C"{ǝo,N3y57mU{cDbqZt3)H Z1-D]cz{C_HC: tm$j..rט.b'&BA֤Ka~[1y1tj;#j󎏤UMR<}ozN{=#xDOcw#v#t#se$qp1ȝp3Ɣq59_B;[>kTU2>Y٥hD$A\>Ǫއp} q!$I.IRyhht⅄3EKcRARY+l[2KW\ ڻ_"B]~jޏx}!p'2%qa&o/Ll|Qc❡椛b S6JWeG/e=Ŋ1zxё{+tT='78h ܊7^'m6O|p)"9½qve KR-ՑMKЍ/˝5ޘ6zw:KE`'H J^WO`lBY}D,V ,XhYiZk\./v{raud3gjXq)to3e90#lFa\qv vIY+2-fZ)ζP+ѸLպDټ ÉϑaCߝ1J{ǚPk|DFʽMո'U?vwUgZj&V'eSIȲTsm(24"<}L󂗨n-ۘ7y\5u0NlKܗ{N~ҴOSaקNA#2rd sڄʔ .RO.Q2/ʳ/wh`k#gF=ll׃i}^zHg/;@e (}~)hHeOPȨHi1IY Y:41sjcȴ̨Ҕ-^Qq> ۛ7XݾCODQׯt3x}?ct]_#H7j=fXDO:(\z1 @~N~J@C ?R#B1=Q/BGG('U{%"h4D7 O'(g=XoT_7O}O}8zp쑮z$+zTQP{r$TzSNt}[k%^F^V$4x$,^,DF K'hX<4fI, S=MX^ʽTVZ+׺뻪z0) WS努(TAz=@~$_`?R/On`DZ3SF&su-Uv55E6F̫-͙Y604sP\h(pO8!q{)SLa e?! % ^e),TwZZ[]^ܲ^ؤ^輠yq ;B%JT,S䅏C D%OcG= +l)URSSSt)[S/XWU\57)uehS^bSV[TLTU']c"| r ӌ >̄ -$[GFF"`w2lYk-j=Www .-MKlɹjIE(!PNig 0E$$ {ܽ'Xs^7Α6Ά6uec4pF[{C{́'8 ɛ3gDQ'%'+D1 4ؠ6taYIB콢s ]¬]Wvw+xޫх(õx q%dRGH'Kj(Z@ OԴҠd@OPqx0DDKJJo7\ E/blf+$S:sotZc6vhL!ɈDA JӀw3 ߛ>69o'z~(afܜ[)L"E_O.@IajG${G}!zw_ف5v' yy_xons6-S@qu2yPU&Q,’$8FI'dw@ -?@p߇ at1{^/'̋괣Yr* 9;doA"8ARArrj boD{H- @c>\>\ߍmφFo3%;ycM4՛EJ1QwGEJlҖ"+4>Q}_(?!=G/GuFYM䛬2zx{Ɇ9r/a阓ڇ 6R3b5vũ>ʰ"o{J|wD/ uX9TE{EtҎkb p?:v\ _16+Ye_ e AT}_bc0jSSĽ=Qɽx{4RSܭKKp RgXd8Jn(ovs+fnv.\h~c{-_׋`1{A߾o#8}G9q9abcn\K:oGCty~Eew`g4uoAR뙺~gޏp= o_@DǣqWn3 8pB)⌴%咨똜O[%kbr]0jt3al7}{zS׏xn_@||k27-$nG 7&oMԓqNЋsPʄv<_t7jf]c1lv3cp> M?F\jޓrPA\m!s-CfXy#DIzYJyQ.ʸNJJw.e[H.tZ6h ܺZPey5Q^?Dl{"r0Ed@y Ҷ} Es$\bސnϮʖr}SHX/tݮ)o(w}Tװ5Gp0iKb)i{)s?8 o;I|!!Q{2֡9&EM 6%FV{ge+8wg*]OЍbڲ[3ePpG}XB6T~CӶp"]DmqO 5) )kSlTmUoVw+A9,׾:-h;e\Ӷ͞)z9q?fQ{]ަ78e'}>!{#$pt퓌= jT$허z&"i(jNS&z>GҡJ4g#z&; ;}~lB@  RչgMSiCIa(j/*ZBP`G]=U$-,!D> (!$;46G$F%N#VN3WY\~K‚1 gc玤ziStz"O E@/Sp}^TX\dhqpaxMˀݙ;S&w릲)T-RJBB20p@{'Y^'Z]B([\?(\[(^Z-%*2VV)Zaan L Y L 5a*\""<0AɾE2f2c.r/r0|IẌ́KvbBIZ?ٮwSoUҬTȬxϺE9a#:ʢXh,-Xx6hIP#Qb $v$&$KÏH$l᫒8.k+T7nIޖU&()&\)J,a5}Aͼ!FŎ9=/Y=9A54P(Oa!jz@h6TyzM0@kVx;{/,y*qϩ+# XuOOr`(RR)vV3[A#j}[6Bklʶ>K8e]3ּƵ^5,k۱0D3!y눍(%D6(xD-4t0g2'j=!cbb"ܡߢ]a^40xCzdՒ]jq:\0 )+HTB,ըbS cA#q|D~REAAEh" !>/dwq >RJ?o]=Br-텊-5xٹE$M2%"{D="‰֣sZ*bV"[e:cQ$*&,N,HRb %&[j+zӋ-# tڑnLDywz{o 3 `Z8OSTcYJK S TI2DNb_a_o#Yd'؂nd17^z pE=^$ۗW [XmMSlvIJk #((  ^[[5vFGb!xϊ~59~_c\qsG&|I^d՘(*7d0 z DOtSt/zz=*\D.! q>{/܅Xw*%BGdMQX.1QW}Խz*s{K/0ŖPEPv1ۈ4$R }3*k ]c̥V>y[[1+Chf*+Q'{'>Ӽ@ؤ{-F6I<|jޱ*-s1ƍl{MtS3LSe/UBRT_)@"ư.ƨBƠVƐrvF[RJ].NeZk:yluH_7bRw{X 쯸+ߑ"⽅"tW eP# eL/ gDS ΀|awC0. $G] dZ$Mn(-5nnLZ> w달zz/[(:lRRFTE` 5nʠY/N1JX=g^&f @_ʔZa}W_oT{OB9 h v6n҇Y7yGdjd.8E_Mk0L%UgF^d-ze}ռ-{% >w.pA׃{%]G;#zxGI}%y։ݍ.T]3)u%Nmw{\#ҽrDaƞ:qE^3WIwdh$/. 0Ge_(`6,"Ǡ{4y/5#}{w?"[("J{=t79HĝhDŽ]$ ubOV)_J"]1iu4_n 'uObJW(J*rDNTijCMa()k/5=]_4_9YLkYևmfCU=S?OGlz3 =@"@ 53)qp*0oo}[\w}\և]eTd}ZWtE ދӛ=,~`Qz`.`Ž\`dh`ھaضaְbԨcҠdʔASCUm}wUYf>"DaBHFo`*5 ״mk%-_W^,bw,cvV-et-grv]⼸C+#7ezsP>xD) "d+\osm?󦟌qPȶ(f'+E:F2W G5&lJsfC1 T!@A!*lA B_ SKC);Ic%j6>KuǵR}Yօma9=ɳB dBbM 0 \|q "gC; (ˣ8ǁA!mlpȜNlDm{rMua9Ye{H(KfM:0a +Fdr:}fg}9ttw{? v) 6{rvE]I J!+0>8a 6+hЉ/ t47k 6P$&h!gum{DS Xu]؍:{vD~$P-IUͥ)0x`Ȓ;1?0N tEON6Ej/_%h_a cH3z6ލXn<#pcM+,'1AЍ*8@dJE%:q2 21/sQi֔9\_9u?@KQY6FSkɎUK81zg(zI :0:BRn9:!dotϥu] B/\G<gd>hLu2I,DHd*{"ƾƨT~Xꥦpj)hhj#ۈ_Fbndj#TK5ަ"X%MF 4:~(iʺ1eɓ &!$HI;$:rԬs13~ZL¿e0< ,; ׉].][! PKͅ3?t8‚a*Jŗ%&˾cؗP Ԍ13ęzLHz8.3S֏j<űk8h..|ۊji2xɈ 昦HBq,:zf>gb͚ps&"<}צԛA@Bpݮ=|S5w/q)vPBBW疫)2PO=bobɰaL/5L5[rT\/rʽw}|A˷5SI* m Z'0.ZUg&>q;$-7KMs<3ig6. ||@ȼ:7ۄc]9tF2]^!ۢeOE5(N=S_!I7ؙc=13S\0!ŀA?!^qݖ,43vp tNR 'ܘ.1^W=bu؟xs*9y>pߥ&SKx9[&'yGzyr1,L;p_r^;dHe̱KWc,ފ*g2셢({o#9A&WrXȕ,Z>F_9Fk *F%b3Sx&l+LS`#eL-)9HJNld$v3qL2g}9XD};d~q:챰 ~hי;*))**,"-/3 6;b~k}3!G8+QA/pdeʾ {--*'uYGN$dӺqTFcc!6c. ;p&^.ʴKY 0~K싮yEoW\'WX1YT;YPc4vFo0VJq,6Rs ^uny}Bj̓ţWqKs6syPZ~}ߩP-*=/=779'9:<>] }=6Ȼ^ryUs-tlv1if}i(.HT(}x?Lt!}j#O"&T=2qʣCPoĮ%揼%h~ }SɵԽèx{/{oEy=λ8q!CK<RwAk><F^h(a.U b7'FGeK#/s@.a.a w=+| BB}.Fާ@AD[#o03>Bη#cm΃ᦇ&' wԹB"ҳm K$\ݢ"?C4( 'SjH DI,d'455678wu(=ۃ7u َ`M׫)r:-eH\Ntk"A*G(]MSd(!5C ^~.{ ~`?H䔣|LQWIŽҮo2OE4rXnUfg89z6 ^"/%iXNf\I9ad Q ,FB*̜y{( @e$&&&ݦbf&`]fԒY,>~;Éi)<?u\E.ۏ̹#1ƼJI'$O0JE=Qct,ݭ**PHf[r{i K-όZMxaU_=7CcEZabH`iz E 3MHcT0L+(,& ,,{p&zL;{KAh v' >ʂ8 ˪Q , d1 5L} ʽp Igd`׳0˂/PTDo!& vE\JyKQ$DienZd0Ǿ J_2샢&<gH܅cY[L11FI2P#NALMˌRREf*MUco%/E'TT+(uH1gdY\n|'%zb\CqcpdH0mYfĪ{$E؟k &NJdX$g(^$?rϔylMu|".+ChMd-sjXf*Ǣ:왦T}}_ Kp`g%Ťb;0|G޼Ctc(9}?W";%(3E+0.p\eȲ,+þJk8-**\NI.gDo`؅#2o]cQ A( ɓ&A>VݹyɘiYW}U`/dדpLIZMEAJ9bМK2!Q: Rn3sUke NWЕ'd:n^n'7/Z} Hz"\PaCoShZŠy [[_?UI8 &RS]Y}阅qQ9ؕ!7wa^,лr#m\_0i`i'~(k'O0Q5/5YwIB. ȿoF;CqNaFLp#n| nSƇ_/ڢ$,(90(-3(!3&4 578 tȻ_RΣluS\=)92(+3&!3$4"[}t~a㏍gny{KTy ̚/1ʫq{N~Ն f 6#3f7 M̙144n`c=VW?LIXgx" $p 2긊5\el26Z!nEW̠V,gxS,iwxY趴ͽu{KbF&ZuՋQ7 C`@@Hz'3A M&jஓ [FIQ]ҍma-Q_zI$B g&+4⢀@TLV YsD<k$.=2w"+KHoNZOˊxN͈^NO$d--4XdO0F@o}p|iqvx^G|w!}OTvD7$_~8c2O;1f+M$Q-,@`pcCW<3$sotkvxeAq i {ڗ͑Swtw>m!dW5W / 1U:\VE[x.x-2!Fo[~CuDp. 0")693MPdcdg23}yU\or-Ϝ_ʱ ̸u>x~8bxcRc iRNt²*byP L+NBKlg2"nMj3Kfy2bݨm?!7 (J9rBC-I, Odl"Dd$%%=L}$Յ$s>eѦv-śL%81ۂ~8!hn-3"k 2-#MȞn\CBLe-#bVU#6[M9V!Tgv5"*.hPvخ!Gd[֎)kO/-qBAK-1HM24 5\|_q 5czOf1 *\ll7֌+pQ+Ġ5a^LTbD8hi0m$ 53ilw2"HLTbRAWp}ӁV#S˜4_N=Sb54N2z|g|kz~n@=3_ nXm|\RjJ9a^h,邹bRŎ 3 [0ƾ ޣ^`q(75d}?cEo"%cpI}B9ͳIsJVK3 U嗶)3aQ}UaBJMq ǡλA߫a듑7蹶#(BZ3UFi)4?[WW^ܨ͈M)VRT`gc84~AzqDn=GJKƮd"5%fJ(ݮI+,.oVeƨ{)^ qXޛHP$@DT'k;z(U6-ɀ+c>G!ymIdO_UZ\ɐU!WTR_g avDmKew)W9C1fY=`<{. #G% n _-N{\1hon=CqJhY1,t^b̶~)3 -$[KȭN+,R=7hymnFïjWļ&E[6l0Y`/i)+&)VqD1 *mpsXțc{ȱE"ui둻 \~kER皸ςԭj2 P!(:`isht~u}v|;;X^#J]}T?;!\8l yKeCưJDK:|q/kPD4 cz8Q5gX 4_x8Ĝ269G吡%BtB蒃SكYݝvgURy^},:[pyOE \hqKnc BM`,TćP *B9 >Obj3l-fz{yVSbcZMHG#gc]%d|$΄`+,*+ܕεe8Eri,g5ӖbζdHɨlO yAY!'%ǖP%X&dl 왊{!}~OBO. dO[)+Ε˶X3^M1whTOIUr(2#UV*읒!&>DaȕQʈ?~c!/Ғf${2Գr9J[ `4W!QuZRͩ' /dGi"{(&~iq°lkڐ3zk3sdRܦlz. ո#C$HK<'Y:ɠ컖(aQձ^H7e.ȓmDEJ?:-iAvxÌ0ZR}RǤFi2Wc/eq7y!;eE6 8kih\쳻A# HkǓ *(Y.9AzR쥢 !'>Pagq+ ,<u6ΉbGl%E ;:m9f4!_Ni^FQ~ir<OMCO}~]O㐞Qܻ.1N>Bbj+7)bA_{aB9ҊtJҋT0)6{ WX(2/Vbr%W4^5k uc"Dt򽢬26h&lOQ @d?~EHfEZ#ЭIK ʑb0ÞJz C :wޏBuwz"8REpE8䖑Ƣ1aܣdW$^qJq Ub*Þ*GbObayqyCw)"bhu ! 쫶/e8r3e z9a S;Y+T-8p9ʲB)ܟ /A1d]}W_/׃(a&jZۄ-m6ib}OR(q[(sY9)v6*ٕ>%4A`  | qo9W\;%RSWࡣG:%Vލɖ;KRC&9'աgEŅKz` |oϏysH\ӺvG~qO}hwA"HOVSp)*ίxEg!v 3v]0Ԅv4}h9* {K 8W*DXqaz&{^B}Dc7ϙdͲs'*܏VDye6r(%w7W^tX"dF>Z]%ŞIzM^qd0[9ǘLBQN(Fcp1ӤŜ`=;t S54+@q6$c 9<@K=ϓfJqgsBbNϓnBBEL(Br왚 {} wcq<xqd0G]'X)*}MG]5*(О@8N2fGbT!9ދaű 4#ٱ- )QS^bB7l-"nX+UY=r)S d398B18qVD(3i1ʐ~ ӑqͅfW(!LK3hQrd F1 ]~h{:ʊqF2'?yOlKQó޵; ǒs'.h䗵'F4]Q=a3t Bޅd h52GyoГdbRJB;Uɰeȼ '_*'H/lPg 'g'G."DQ}edɽe4!4[!bDBZ^%ny24%(dLhМHdO _P5_S0x3AvxŚuUX+k08q yrG|]y ǜ|S" btq'Xð}n̅u-̗VA1zHQ{A lѥ$T!<\dTz97oƔPg#0gLUӽZu9̋eFy}F8wPr2s2 gď$PFW"9)VN=ΈDx"яFّ0\LIybpJ=$FGM(,5=~IBLct )@Άk{rGy1.C 詝ܼČ<[N!oKLTO^Ҥi {~ lף|QV,qf\!|l̼tRHQRS]Ԫ{%l `8Rs[Bs:R.RF$c .G#))b5B4UP=bׇDx(]Hi۸Ҏ1$"{:reM'~I}BҀ<Ѓ7І2wDOh"ɷ D2{[FWԫC.kXh {"^OzI?'~^OzI?'~^'a5 IF@V@b@9[;Sh=10&3wm))_%~Ra *쁊OzI?'~^OzI?'~^Oz($ =Xym)}l!NLMČ&˜|@$Cܱh|I:䲆5**B?'~^OzI?'~^OzI?{v_4`$` ! 8363c> qޖU' ^OzI?'~^OzI?'~I}BҀ<Ѓ7І2wDOh"ɷ D2{[FWԫC.kXh {"^OzI?'~^OzI?'~^'a5 IF@V@b@9[;Sh=10&3wm))_%~Ra *쁊OzI?'~^OzI?'~^Oz($ =Xym)}l!NLMČ&˜|@$Cܱh|I:䲆5**B?'~^OzI?'~^OzI?{v_4`$` ! 8363c> qޖU' ^OzI?'~^OzI?'~I}BҀ<Ѓ7І2wDOh"ɷ D2{[FWԫC.kXh {"ľO 3g̘)C̘1bĄ /^te-Y`rŊ*TL"3J(O8i҄ɒ%J$Ar䈑"E "$>|CG8nبAc0^lSE LJJGGFĻ! _.3Y nin.m-lL ̫kK 몊j* ɩiI EґQѐЉ @@?>=<;;:9877%ljjH!"D>xЁÆ 2d *P0A zء#G7lԠ1CF /\x`ѩ"f&e%d#bݐPЏNl,{X4W7ֶvV6&յuUE5Դ" ȨhHD 655$F"@y %J 9b!B:ra2bx³*Rpnjfb^ZVRNJFB>:62.*&" =\mZFeẊ_ofWk:Fxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJ(N)aSCb >xa .X@a8h`(@`~4R)@ IH@r$a!@A!e9uDk/{[k6֊N̞ ,КagNĞX XϘNXT՟VV=suB5*Xpͮ"Ke&R4b\B%>0(F͔)t(E!ˁ<<4<9QƘ QㅧC7eBm4 `XO:G>||\V}ē<}rMI0AqՠHx*pGs08s Ai&!0L ӵ´bj-FV_Cd#[~wB.$ӻ& aZm%z=jj![ цbWM􍽗ڌ0PFH۞:pi(:u"TAb屬h Qֈ VbDuDۑXl4(`\ &Rni*p@E2 (aPB(#(AQD!j/Fg_2cF.fH%pJ%mGWdYo; ʄ3*È4Q= ?;ŕ ߻>ӐD{E=4 G2G9B}^<%hZHĊ { H@%8\>l/r-P{lR(Gk: zEHv(H7@%V?닦d{L6o*]W? >lW60/҆H{> 7C63 n}cUqHL a:CÃIL#`"S 4&NtiN.hz6ݭ.FU]veݧʺکks.H\ # mȃGgh@c&G'9[GwngݭMqt#vnIVj"2]ժA]^QQY]եCll7a [2BwEn"t;ֆ[Bw[Bn]6݂ -Jݭ!]mՆ1]bM]fI!mWqu}K3ѽsyֹ>k/?L@#bT&!s}&gﷸz}.qf3 ;g >7Q @ D߄`$Ock))x|3 <|xng|E( vݦe݅AVtAG, d;85#h=cJ8_`})Fh`HCZ" FH#GUu@ivlA[i>Sj/J@j&3S ǀot S\KS!'d8-b\1O4 VvL2Q[d'PmOnJ qօc|+0a3;:^̮T 3y|#w> ţs`\)X:$LѠVNVrXae GJMQ+a5XV)ib-6[Ũ}E)rM&3|$O$}hx!췐}7v%&tP #a H(@BĬ $j:Ip$(&LBGn;ODo$}S!I=GA3\OAG%JY 9ΐ:qv#*hGAfӌd̪e:} Ge;Gy:>p|A5qş|0m~ oaSjG@`{.ڜ YxOƻR&aK%1GANMe917Eݔ uQ(P=kOSB,)2>ܵysa-sz9ZpxOX/bOwuWΰ]EЊˈk ׽}S"@ :m-\d9b1QO`J=8r',ԠB( '1Gx8^5ȓ.y X^Ӏ O+R- Bmu ᷅$ğVYVT!N@Q.:.aF"ER)^nԡ{Y݆b#Xu^_ŷxQCg\@v(ܺD% `QB[.DF*Zzh tIdiQc C(fKTz+='>ŧxS Ao|$[7"͠5BD/ 2$~ѐ$׬E.JlR YR*CL KH=h $ГC\3U)M'1e!f|'%<,9;Vu;H|L㗓/]2.HnPiEASjç' .5|&Er`*4gOTgêM @48E|'D=ປ]wMʯ 6'؜Idk&V Y,XZuSCSP%I FBL8ԁY -](Dm\:DV `~rG4ZO*o_ݺc3|-늣/#r8nx"jid(PIT KHE%5SUőAsv6sv%!\Xrͅ(o9&4|պ[e `1ԫP/-]H=pP$ՂXubʕ1*D#k#,SHI')UZÔCy&-g, Bm*\F_yH>ф7K":sXa.\K Q|f8dRqZ:AELU$e:E>R8yC箒G fW摡4 ye.\y;y7Nnw.j%\aUkV0"fquCUAT%QH Q(jb1RF7u:ًb7yd+V* MzYg7L5!l3xѻ#.`"ĕ~&Cq5OU:dbJ PSj &RJ9.(,!y1b9'[*_Ar>8:cxTwE z-4{AeF/ՕSIjHȳUCǓ{<\z(H r@#AJ| JC]S"hE3xٻݨkGCՈv7:@.u=pR)GvDTICz(<'BiQsņOmPmXWJ0aރ?w{t|8ݸN7.HqC$34.%JG$sd#ʑ8)F(U!R". h v_x,0`wM]Z ]<%HKw{Nv,]6%Kmec$ۓ ؤf("&kYm!el`NzmB[oh;`B\9s EPgg ,;)>TwtϬݳ+F,K g[68/fe tČ > qO|X)SWxL U@sK n6|Nmz]-ݝinv?Z Ct ݰ1B7LP aP@쀞#ր|A$LaE<).!:cl Bٙ3j .Mݺ]b]-T*]>ݠ˕Rr-@r1S<pA׊kU WtpV`NtXsĊ V%2A 9/nN kuqV յрum2n֥4v_wv_bWkvSaWKvV\WΕ@JqcT9D3A9D%H9In3uqnD]QG6ޘ7Q&Ե5nMdե)4yYw۶ub\WuW^W{uZYW6s\37'4AS 84].C0a8E.{h7.MktmPnhӭtk(. bԥٛ۷%Z/ںZ-庺=߸}޷vi.7{ݜ.Sgs tG\7-~?=ſrN4|S,Fhk8ZқsYlx}?]ٮewh[4=Z&t6OݨwN 7S={%X/R#& h @+$m1CRB(V9*I:>h: sI|+!oCV h6`f hXD3&<ѐj[JsUF `i"К. z ʁǁoP[ {{-Ox~o6tN*Gp20 X7Rq@͋l^ !50¶m (`#`mV*OsM$grዌֿax?. xQ@\(`Fa\|Jp!AbM-n5[hmi׶E*-|e' 5U+|"Cq1~d*!SKʒ@`RvA3-N̐89;U'lCNނ<`>|b$/G ߍ'['ڮ'C=Q\I6M"{>؏YD>5&u}E%6.AؠaIE rJ!erg9DohrH#@B";~D q(ִq܎^H=D*| #x}+^ƟKpYwV4(wFVrW:lYQG%^T 88s&Y4zq8CBF<8q5gZ;~4wxxŷ(x`.'@,XXX?^$PIbH`QJģ$ɥFG+4Xt~渵PCH/Zi1Yx4Dw=R×E$#U T˞ PZVbKuC#V* "*,a1GTHBPX"P;q'HmRnTΤ?)R OBǿyU&g f&|@<ӀC(g@HfX;QҋtrU'f*'T"AP!WҞ9SV/$*b /~G4ʇn <e:ڈA4:0MЈ&Ye*B'\E-BfruP!˓JgdMHC~?u9l4`"Rh^]<$4<ȣ2y*[P#Sk 9B~(FxpF#)GhOK̀8xDc  E:Ji2%&If rq &lC8D9hu4Ci,$p4we<%e$,]jEֲ!d8_\,3X˵Ė ,QH٪ .,LpxP!9qАax0xp'hVp]gx.(b̄W2 a0%bHb̈ 1(dR(BKU1*FQs8eBPQX9&M'5gh:l?gS{,I΃x`D%DvSq0"AaZ[)|C0W\-kh ZԐ(b( @,,Il[gwij)jڮv 5tHb 1XE M@TDf4 2Q1@d юj0bld% 3H|0>gw30vi+nܮvjҮ֊]/ E Й .@hx T%^'B=n$~] &+ v{8] c]KӶvbkwvViWkevYa7@jyt@ A+ Hj n$ɺ@@.K֥Bغ>[r] ^ݚKVvdfwevZeW5v\^W(k%{ENեե եRbu)$XݟYsİ>,n˳zuw>`ݜϬcuonMץ4faw 6v\bWv_\w]mq׎kKZ W5ċ;Z-vCcݢ.NwЍRsn_+0>et#Sn=[glUm5oi oin2iӮc;յ:bt^\iΔjc_G}$ÄרFkT G5:.R͊jXOX5'$LmIp!K&–LBPg>!ض{?L#Or@H<d ӁR9p e$PР GV8`݈fes-J j1) fZ7 дfEAT4&怼ek|)>9\ @&$E~8P링~4C X 'ut:8˱T Q5BjxK@n˖V̌VfFB@o!C8ǢQ4;dgAD~C:Ȃ0eGcƐ1SvC4"bԆ@a ƍf80s{Nm8_˟;!:mC00#1*Q6(qd" CO3Z/ͺ#u5ޅOELR0l %80ǖ6*'ڂ\B0 ³d T,qXb2 Sֲ*҅&Υdt0GT'* Z&ix<~E'Uhߙe-|;VB\ ᬸ q5|!ME5g!`#:Vy,Qką U" kBM ک,!"țP"5 .y`(SE"q8NXBNZj}rzUpŠUqD(" R%@Dx(\{Tcbk%"Y߬ő#]c>CyᮇȸRsxx9&Z|t%\K-p!D'O0NT\WRLEz#bJ1;bH)zs4$L#[T.?:{b0 养qRZΝ h%]P9q/:ld[D#PhѮRXSMG|'(̽AAUbK%CB5Qa6DZpB w?7Bܤsf t=>9bd|CuB:zZڵ9E $TAu1tD#>6\!%fx/xNϸ97B :/ԉP r3:7Сs4hb4zYЪeqI/Pm찭|B.B+qV|c`8 w+lH0w:2h Wcs&i !R9Q>tqtU$P.=+v &LVjAXA22ۄ:w1GJrKtR,? 2[KTifc,ybks始( 3;  )-*N9 8py(?R!L !S9+rF$UtH6MD% c*4hʲ#KzMrcFE{U1|0sOd ;۶v!Y#+\q 8eq|K8m֑DیGk+ S\a*b %/P\725uHIZxAH`$?uܤZjy2` yɼ<ރ vc" K=x⭋b 7&*ڀ(X"#33\k>ܱaǕJ RD7 v>PmmW2?uP`$L9A@ƫj+." {(a _1pi:o8Q؈a?@0 XT$&H(GV(Y 6,Z :Dw;P]K8#H5Fv`yYqDЇ d6 ,v׫a.C&YXmX"eJ%d#EJ/@dr61-Mw$; ^Qߝ5=3m7l?#d[5S"&ďI?'_3xU"+S,T\0 ~a\sHad/X*\b##p',q&p4؀>|Az`s&YMVA^"^-6])_n׵ux];,\ghYpE;1YYjbWWwXո;)y D1@hJ&8 `p@CtKY[4bwxQWxLzW 5wnܗfʉѺr` @sP)0U99@uDsL嘓@JQ,**QgSLw(qC~#P0iRjs޴S<|Mn͐d !`G572^' ŻS[[eW[ ]ڭd3]?mށ7SHί2KZ? .ۤ:'*'F儸S8.潺Y6wfz]0hN(ۦ3aMU›VHhq4{3/|+dx56*3WA_@jB,Z^|3ƒ.|p lBc:0rpSȁڍl6wռmGAKq׆]xl6~詩‡ 䯬4Ƞ ^B08ZER,b4hd'\:iJj<ɧ`CtWޜ37\}r7j&FZhp0yA5qBa K~?@|aj6U+EACd?Ȍ14mIhy)ﮊ@TA"o @#,r eq8+w‘MEd'J 14*gpn9BT o0 'Vm\ɼ-] wE5h b|"dtӏƨ=<^I3ԝ !ږ1U֮GW+O$dNiSLV6ZL9GŖqblZ74x݇]}_ 82 HN@):pᓏ膼BRE=}Pڶ5 +2" (A1bqPx3F,Ԋ' 0&;%y|3C 4O@*4|->GHh [`*4 45sLCRY/Wk$7B %*SBdq5`S ppyng١~tvT3?#tp g+U"D5P`B J.AА2<աԷ#HNđOW%\T"=# g}@φ9޽Iw7I<׀&,~H2Mxh\KQjg7(6|0" th)DKDV蠉Ղшru` ppyBWb]LԽ{e~]*WTGr u(V7WbCg 3 543ؒЃJQTS iS(;kXh\GLB ;)^׼K)BN0 '0|O"Kq2P/K)5c!lJ#Jre eZͫڣ+kw[w%,Ƹ@qƝwXo%QQCqǜٸ?N4aH$4HI%a,pUb5Yና.FЅ6[3Z Wtj0j/ x;P]t RdΉ!EtqmE5NX:Nyesrե'M2GPl`mC6c:`vj*\-)^-%wA!qWHF$T`7*NܐBa1(e`WRf)qEL5ׇ0ZTU,I2*T#j:֪B]^*ޭSz7Mڐ.KwYtwvZ\wցe 6>X A5X;! ͪv]"l۵vd]nw*B]ѹtQ]!Ľws߭һ4\v+n.Z-M4Πujbvݫ'hڵ vv]*jwڍ2vZnUFtWhݜ9{wk.ݥrۯuF*ꚝ`uͮUe*k@ٵZm)UQnK5N͞ݩg7 څJvD]ReݠƹXwsݭ!4aqn.c؜?6å``^[TcxhnMeN4ojvej v[K[uOLtl\5᭄%H{"I:%R]pp8/N啺iT7fݭUvh=v]}:ݹ.?G|>oC\RB[В;R͸UP\Ջj`\ BdST@6dS`0(brT ΢`XE5 ؚD+x[PA\ftG-R'4H> z& YFl% 2 8lx1xpZ mmV ieQMHnZ8̼e0w%\U)P3xP9][4xhL"<3;nf$m`npds@b+d<<&bvI% 8$_t|#R'^W>-/W_$ >-aT Y'ƒ 9gc0hyĘ~-7|+m2'wŁM(up^%b#ʝ[(zl#F328A-J*WU& `.]$Òg-͗S-ցnCe%3^83/ʀ < })"|v. C-R6jQ-F H *Tv=*ѼB\DC 7(9cQDlHJkx#k}y{Zv'߽wG9[ϏG:H0KݡH灇#YI6Nh?6 \RJhS8$"x̔Zb%6I26iv4fƕ)<߀^Wp0$b 0` ȷ1teq6*e4q,YS>GtAJ iaU=D\67l4$u>ob $ E0xhC2'Pczj./b IT@ԓPOF,GB?uj@|1 Zhè`+)RzupVKUs}eXT_FsEx`8ɚq#a?(2yB K! ? hDCEDcp`4PTQVu@4Uvb챕"AT$Ik!f ƖbYHJp=y^-5a FK0`OR!g`8-T*/ÈLqUwZfd9[}LA3gM`fraH)"hއD6j !K a,.:* NBNXA$X?Qj`qlpQZLB5ت$)o[lVZD+^P=r'lzh&+h*%5qHfa/6QO 1<|֐Ӱ!gfx9"*L}z[엛lL8@۴j6 mڡ̫Eb'\p9KX@4bJF*Wq"p 5V+CGg!fH =8: QeOKP28f xJj@ uzKj.VI^TQh$ $[.0KdB%3D!y>z".؝j`-C O&AX)zBP;k܈^@v 6uڤ W{DyLt!xc ^G8|Og4/㐽Eb'ԭV.q Ts+`\5r4"R~N)9;ƫѻpJ6)n mxmEW( +XJ?$I|(.ٳE#;R^!JbYP,C&M d"1t$ %PeOK&U^qn9.q%6Q @YI:/Ȫt@J% q 2fPY['ʻuZ){D"ѻM<q A.vaM CDp܏2r8QhCF$*2DNIy#&M]0(Apm8/楁ۨûQn };/̜d$# n?3pXcx>V# .re*H-OHӼBt.Z{yk&/㥡"4Xz׏2w]NK=({퀽 ܶUdς6#|` Gi188[AYl c!|y0a C Vj*Rޡ;fyq&o㥱B4WFw xx ݻk Ufk q)w );&/4y##^ d'Ns^WknK`#n* Uj."ޢ#xsW4\yfQv,@q8nܙF8΄pLK5wː] rwlAwZ]vkfyػU.e]R$E^i. q`vB o$]7Fpe2 nn%vǖ]1"o7w^.sw˵bT.TUj En2[wk. 쾁@vJl']KF%`QvXc_خ+֤i`?W%rZrʴRqO{W ޟ;wonޭ4]rIqt^P-G97 .8ަ uxn.ݵX6mwzՌ]1[FsuӶPMtB775 _cI&(ܦ  HuBq(ި;VTX^V` V%Q8Alj dR&A0  %1a$I" uF$t7;h"(|Hc錅dѵaRJg~%l`Ag_a:8zҺflƅb6V ޲@hwA .3ȃ̀)1@%A 0@g 1 4i..] H!VY$?  uۙqy,pf;+sYlub 6@&W R56pUR_Eu \R`k3G 4Vx"5px9 OT~Ɂ0F(3 2uo2s;*|im`p%E2UP> 7'9bRPI oXKZP>FMNVr +yz2ᝰWj%pJۆ+lX0DEjRRJ '.⾔i Sb0xq͔kinhU"z"#J$#OPF@IXPxa&nM.$X[E(%tJiV iXLpeqebc,8!rM&^F%X8E f "3zHc1c_ yB04pQDk< fo kClPdsBM#(iM*rMq5L C fB@R C 3iPdLAQQDH7*Cqh}ī#Ül(dAVBYQdƎ픓"5q2+2N2礨`kQX4bʊE!+%mx\bVRf bhjwi[$3^=RLG$GO!4VI<:DubهĝBvHb#-FZ[9)K2DRlB,q2D10VEJs?| 9RU$FEwr왁^@VRvYفj% ^bfa<šyuE+gd Y_9(!Zء@ 'R6UQʖXs|Bp+;v 惉V+' 3Ѹ`w֫ZKAJBtZr`ʉ L.2v^0qAD*裫)k {HYPY3|48#M-ڥe6E06ĝHj@(Rv0,7S_>|f23HC'U""iꡇTW |"Raƺp'Ի] [$ wx#?،`Ĥ&(;DAJ.,(`҆ 5MXqcQ C2t@WC4J)_[aݝ@-AP$VIPfcrB3LN2jQ.!,~@jO#5V. K%yi o" >Ih H¢D$v)aNIbx$Q .7hӗ⑌včf*dt5nP'$с@kwGL`}^K#xiC+ rˠc lb8x "G#1=J4:P  6\^`VasN3zBoP[!po8o U奩Z4W52xAA\`~P32B pR.jh$f*E4!kJ bJy\cyo4oU䭹B4WwP w@_a`_zJ7Ah ~@'KxFo$2`eN^-G;UX2u^"VtU^[Uxiowл|wA{=A=ׄo"}$+ r0/\ JxL x"%/eӼEtG|E^.[%xi3w{ݼ F"<ݺ(yޝ wv]8}Mﲑil]P[*16V^#6yq4Y~=`w(y]3u$ԑPA u].;G1qD.\fCﶽػk'.Z%#`wRR16&AktyoY #5`|&U@w2w]Js9 sWNܕBw]8#umqwb]5z/ W+R9*ަkxo 5bz&e wTQןxr\-+ wjqHܯAefOn]cnK.ݻ74w(]u!k6vjn ׿x Cabanؓ+6䎁d_-֕zն<6- •uUOJA޿N@,3*XaD?#^3[V<ODb46 Π !  -ngęz0G&ꁝاy`H+N 4{$qW'O E<~Fܫ %rJc hWR|p!"jt`P%)wzH)zR0>%A 0ʨ DFI3ŇTN Z5]CV]]ƠXz'fY);9`z~30l4T& n8\Ih @CdTCp8`CPVDp|p 07tݱDE3QfhYA槍MF$I89STaE+&;pg M&=`" @2XKbQpv: K0hvy5\K7zF/Z#h]^KSx4!F XpD'P@2$b`E?0a_$wb=WJ%˦y /. mEm颼6\*Q PLb \A ;p QK(Dh u^@) vN"ҽa J`/uiaC0`)`Z`ZaZcC;˞<4[ô Վ3\[͐m1ö ݞ*{0n!¹{ }Y߅?c?L{ʹ7L2m2-A3- 3- T0m j[ (`{ow^t[ww_?~|>%i>_#}}DM{ִom B??5>6ml_Mn̾6ۏn;Fۻ;l/=FH/K;ǯ\˅9؟ss?-JmGpNEUemԎ j=d3q5[[|nq65 RJr˃4I|Wp%N y*iګ6%Amp[VIVGZEVڡ-QMq3%W&3r03̯4o*Pمy2<yx5gsfݐNjW2MvcSj,/eˌOܿ89>%Xb1{<802 UGrJ}n򅝬9)U>~on/-Xj3>[k#\ >r#J>2gw2,MLe[9+8ˢkY\5cf̄ wYWp|~Cs|}'Le2[YX^e{`VI q~6s 9KOp|_Yj6滘b>%Q&"0LcnGC0LN` g~fI8&};$V$K) f1Xa`9t~f $< +~^=;!ƒov__]LP45 P P '"oq$~ ˩(O! ?_& 'C x>a>xlH>oIYa0-/FKB| -ߪ5hyI-<ϲPU>My R'@y7'@~Vsp=w!)>V]/u y$H*|$ϙ_CeH~K"_T䁈|!|!8FzdaO#AxAw sFtCw#yU֨Wzӯqp!?Bsf>gP$BB" Lh&AH DХD{=ja|Hˡ&tZLK6t)>RӔ^-{mY_!5'?gvd>ti:(mFTi(J]z(өMo}SjFѨENj4ѪB :*4kZuz5= yI_)arvNi=0esJKÔ) TbZ0dAC [aCm.l+O.]tNm8~1'?R#+<$<}H^nmA> ɥ~^R3Eͱ5,;Nl5ͳ2L[ sm%L.ޱʺ{HwdFo-FmC F 2%21)%19-,f4bN8v}lM6鶙^p[II7d>xo1 _7?Q'~WCh"]<FF3.f4Fhj4mM4mOնٴmƷlqFc[ER'Qߐ +߰ ;!?xьpFC![D$l47hP mS紓tZED;ěPoý2B !DAAO+-WP j $1(Gbpun+:hVi.Oo;AG ,p'T$8~xՔgWs]?%v5:jXѸm=m1i/~C?

S<#$ٻ| }$Oy<)4WoƄ lӫiWc^=?Rm@m6RPWx`zy%ޖxY^]ySEG98w@_X>#_Wܻj2 ``;AXAR;;CmC]vNqº{r:(Ns!: y܆wz+~/%y,w9š k #2WXcΰ!M"ͩTnq)רs:A~9`zrYXZq86p!nHǛ}&օ%pXb7 o42iL9nSnZ϶I-f6rŶ2kܮom]6 #ug۵ߦp?f|]kbw푦 iEQ6I"r˚f͐LRU1ô y\8Lg :;l^Ŀ8/gݪkfI% f4W0ienoinWU؊ Ԗuj9yj;0h/29a1uoLt1^6VPbx',PӴX7Lέ #_[__#6hQ@^^ozyÎ^ F*=t.TWju*ؑN^|/hDMʓyWKڠaV-PP,Ql- ZX W^ Ϋ3VbuCF|gE7+YXJ&*M؆*g;kaZˀT{@hI*V0B+F u'RRF*Z)P#Db O%|:qTLb>9Xi_M6bqҏy<ԑjZX> r<`L:K$I21@J1Q;+hƐF4N4`v W2 g yIh?@R!KZi?)K|@%JRB,6b+A/6@2DlJƨrGǠ?<}Ǖ??~ ϐ|ld6&DYjD9_ ` ?d A#cׇ&X#+::FYgIfȦ7D1${$yfɔ6MnyajqaQ.]ʢSyV5\^˃9~^!8i yn6:u6?ww&09 d.CR2ulecyZ.1EԿ 0>f s܀#7s =i[0 u }}g{Ϲ{?Xe1wY_6gb]73 b3Kz{g+}s.=Ex- lf%+08,=O,#+ &=;=x3+>0[ߟ}¹|` `.S+<;Xa+`{0: p&|;wl߿O}G*~Ds o,μ?>0x$ >hs x<"U> 7){%t?${< T#~ow:^)T<_D=e>ߎa/W(C`=/?>| Y|0̛&Oy_˃u-呲;W_埲}(:/@~o}—|iN̓8O^ē+{lN>c5(&䇖ϒY4Jlz!Fy\=/~Eh|Xo}VU~|?*i A~0y /䳀|I@ 8?z~6u a0^S=!H4w0fh̅EjlQf7YӆΌ6vslf,?GՠpR jCChGe^Eכ0pߪ_6GQ{PA #g(+~ޱ+>]犡)WFϭ8Zmb*RR0-OoMT}l׋ {V?{?*7x/V5Zn@UD 5iK2ק_&|ZUj9S:LDZVK^uP;]:kbG5@> =M{ gHJi 1J;"D4B`MBk[/Dp!F 5X)XbCmRm>h܃\|N˷p-~ַ <2h\ Z4"lRAkTkkTk[tk[Cִ6C0-f;]ءfu8eiw]Łm&m mxGڽgs&gn~,}9)TjH 3&$jp\Aqa&Bٶ'VSMvgyoy­v!n rg]n#λ8_O(2;h6%\`yf@3h6+2jX3hD3ٶnL8-Psn>ͺ4v,3̽0V;2pK§L|Ǐ I)BݘvAdNl8M$nxHJٸJx^Nm'ﴆyڿɽ&;m$w,(H|gh|8sr94t!ˑAC0PpHx5$L*j#t<0Bɯ+o"\hps G G?A%/x N@@!93qLޤڄ\LT>2nZ='g| r-@͡-\w? }&Py&\œ u+ԹW30! ěU>EQ> |4|Hȭ"vRgo|Y)> mg/tK}T5^pA:17O)怐Ŝ"x97qT9޴AڐEhWij6aj 4xyy]X-s< [ၮ7:kPN| AܺGs Bs.'ǜ 9ʛ9Kҹs4s˹ [H:Nm" -]6Va:ѳ>Dbs\ߩx=w;Uވg#%NK$o7;2o\mľU9} }@s|Ӡm|5pp 7֋^q+o*~xiC'9ΉjMHbj2ҔfmB$3t[ޥQqJ4dc'H8cG;vq4 01? hȞEE,22eLH="( UL(VA`>!!z*%~BWc2@BBaE(yϑe yŏI,ɦ?Ne>QO%}أ.y'v#E?t!'"# tL&Snlɳ7Qa36U>aseX,h9͖Gm˝5aƬ`Ț>1`?i` κ`;tmV)ler-82ü1" t3^``A;s}~_Y|bF1gL`! N1?0Lw1lc=r;g}w^܃|C@ͬ {,7W|b qs;/aMx^ߥ#}'ދvH?.',<\b`*cp38W %>!E7%~]{wIߓpK< $|~ cCExK0;c Uߤa@|O3k#!h_G''a~g*\%":.PP?( O)FsG-I 9y x~~2wy-@D=oI|/CEU2Y<G]a 7}y#/}uě8Fqa0!1JXB !c8]=PZ b0ւAԋ^XkZBS} {GBIUqG@mGZP"J ( RZ'!NTi?O\}ykZ Zq&V3ᵘ JUBcS QMlڋ{ּG>ć¾(fՔEAj ,%Y49VhTrmUY_ aH1-"+f㱞 NJ6`k_ݴ=rz;L>5W vsEnLMn|!FTfԌm[Ie%Ѧv@]bn.ҺHF7zۈ}=?{~玷4r1yA3rh8,ņ4 j7j>9`ry =@waw41,@[ 2>C.q۹m;BێvdۙٶmN8r7:l6;j$/(^^]]BeߺPxTFXh\[A5/?3=|&gxMO)T\˹,zr)#rg%N L;S;P̼ {6}54[?AfNp4Ŀ4l\ rm,1)7|e|}o!1}?L"}f${^QAзygfүϓsA \CNǺxA_ t \ʞ*}.ȫ!g$fO uh󼱐icQ ^vovt5ӿTsho=z'^x4R>=s6yLɠuzqAwa'W 9tn@@m.ԅd֍H}a- ;ݩކWx$VK]{9/-=+_W&ٹqx6A.8 ;C8xa6sFM p4q4q8Vqs$>C܊^W㚸o|q9-T粉t&+ktѡVBͪ е54kIm#[ ! _[Xb6Kݘh:kB 9y+7x1BܳSPq0SRzfFF٫M5e5e%hFNh(C,S4c4kU4{<nLv+'g۳(v۾_Fw]7&6e _)jlxbAC«ŖŶ 6KkW Wv/ }+N]OҺMWca{>xBEZA3,Y.ebl`ՁUc 6U-m-լÕ-W 2ՐLxd%Y=YTTZ&3O`%[uΠS5&Hf C@ԬJT-PA5hel"OJ}ZS5,eE'*6ahKaR`&j3PԽFK@WI$"3d.GJnr GMaFQ^a$eETn-iѕ=a;Iǡ0yo߆x0xI)>O{ }jВP?2@Ts(?Y.. / 1p2 cJfy`A `5 "a);=tI,+X=Y<[@| N܌ }qc+nM( 8p c`͛ Ût/S݇. &,LlbF`*'p c18S,$`+ ps;aGnܻrR<$;;.O?؁%8,K![lc xO#`?3>Ÿ}^Esg'ܓLC|3_pcg7li|fE$,NIswq˾c}ޛvM@Nx ' oa[0Kx=~M:S?Sy. &%м<@ЋIz/T뭴}WC,G`W0)| W-=  xbx&oΏy0CeP}M!e̛1Y3o'{@5x~+x-^*W*DdoB}bO'{"CstїmݠgY<.ˋ<&O|\nv-4smJLZn&҂󏖜sR3$a=7*|g'L$x'8x3';B9+|g'?= ^NrS ӄh%H\=^X/U~Cd eGMcHѱC0!IG QP|BҴZÂ<( Q8?~u/|~P4 - 4p pIƓV(mRZS 7μa odwCFZ6Qj]Cc}WA&ZQaŕ )j8PqEcUMYY4Z4\[Qٮm엖R6L(;-[Ol9ٳdUO,&ZvϊGa%V[+_£z?]7~C֯え/`p~Á1"vĘ e5#h0cӶk[6_.nXbjJ#F߷|?>iG@R3B8`jsԌGȩSi9k8Pn*H(mU{xZ:U=-*d~k 8<I'‡~ꇦ6l¡킲A(OQ"l?5D|Nt`wSϦ^>~[$QǷ4Z@'\rA!` \ϸ#r@7>(Yp3!oS^? 6k9q0qHԲ.>q,7mp|rhvHv,^8!HwqkuԚyRiyԅ̉:-8|oAs'bJ?k7DeQ|D`.\ z P9B&Ne"N&͡c f$M6nRV! hsM/Q_37]掷$QPЍD$D ]X4F2`!B#$ L'@'{:'w.)s)#o+'m++i,1sڵK.=̰%,=4ֿD2O"y*?m<D%4ahhTIaQFD"B$PB{<#w2's"!+q!/m#5k"+l$;mZ kM4 D>dSJO !|"<@&2DF"2H\4 qJbяF"B&!T%)*R|*Zl.bL.j02V1dks̰s={tGӇUq|F'u|ot\Ok9t !N0,ȀBH#)E@ %fN ( b50G&C :87j7)v-zЪ-)Ս_Q,H!{W`^+X9t+8,2#XYD%ВHHPL!@( v z\ԐIf a(M!0 1u1D֭aO:,781+*y*I$t6<$ZoJ_""}nu9#ǁ_?J@76!jQxZAGEhb&SVx"<?gОg@s= CY>eґE(y?y=>lù"s&/FfϕV;WB.@C86~/( y hAԅq RR7 4KB :?'B֝K`g/0_ m۞dݦ긹Z;ll Y>j B "4DBqdɶU+)ZWTXVXX~rᕗfE_V ,%G[ uxmho<]xʢUEEeӧ/{x 1[p*s/>!0AA0ZoQy0 a݁=XwՍKRW%+Z lZ(BƼÈ402FjdF jX(!( 8m)ŚWPw&;ԝBԝ]HvC_W$Y2/T]( .Ba{Ph^$%&XY0)sN :a;Zѝ .tOb -݇GOW{} 9cfxXf@ǁ@p >S?t.8N;bǃcw>u̦;"<6d>w,c_='=4  2_u34 I Ϸ_PFePF{24ϒyO܌%й6g21=k9JIb|3OoղV^?){&pd >4> ORz*@cph2X( 6MФWBt W*HUIVTv4є}<}p?D>J>Ο!-VʼnYږ`dI&mIQ0ig4iY$yҮ&҂&z3ԏI.Lb5`$k#ZgFF15 >x|Y ?ųQesuT|ƁY+=j ige/-c0`z=7[bhgHD<9X|ã }Yk< -v.3/r"ᕣEW^94dᔐvc*cfMg43Ҷ3ֶ3ڴy3޴r3V17 dؽny"MX&~ƇF(XJL`̧ #'|n0AGN( Nn7)lSSqS}ST.Bp8|[">WE*7@޳M]AuM5!0jBIZׄrhu UkHy7|6~4z9Alb_.8iZ)UQ;)ꛖ1`5\qF$|34h 8% v+-QjDB{>}<:86~4v۷TFY\CV>ti/|O+ރ;|nC*aRʀ%9C;r!QP?!8E}*G yGu=(zhH#G*M#~fNG:z/Au<``Cht980BD {"54(nx yǡsU /nеJ_oHh|y-<#)t¦Ll2I d)$a GF-( al|a@Syismg>Aw=C6a'5$O4@Z.]C, 4lbipeE$FC2#*dRTjZ8nfnnVɳ&SQ!L=ǂp;c_a_:G._ҕd\LH.6- dEri$# IG%HF &)6PZqRbR􁴴xy!GfsC\fVMl3TiGg 5B'$]JXt+@Plr@)beEKJH#+.r.^ ɨOc@H6x4oڲIgQi2?.^W~%lYp`3j*Cͥ3Lj6q1dJbXai! N$T3y8 Q2C"86{4rZ4HsA @3:H /"D2@%/,I xɋA Jbn@:!j*JbpT'afiϤajN!M_:۵E1XQC8QEޑ3V0ۅpt6 ݿ I#qP?P%'~I?Dfy8RGZW:tg*$ pB`kG9%n QغЊ!θ F=1,q'FyD-?AsT H+EIHэ<"&SأM_osDx;t`nKAL eC!NPA3dV g5NFOi P;r7r*aXף.V /axNP]u[2xVl,.ll- XA˚ MhQcϋ=(vIeC# gL"5oGhlhl+/ MݷkwKf7|B0C2e=x)RdGeDtdˊ(;jLɍ3&6ΜtADuL%LˢS2ژ )́2̈́{:l{!l`FpKk &7@rK)6$Դ~Y1B3 F0/[ʰ0ZŰ2E0VY;pQcvS:WKɒ'7hIa JM,,5OB3ՅX`hR`be* %6-,Y hjEpyq0WݗDcw ;׽rsܩ,5wj R^HUfK%jm@2Vj (QhSВFE53-TZũUg2ᳬ=uʺiݏe"CslJLRZ+Ih$E$GIh_$YUDRZפ IhG<~*f*}\2AKt{EUVwTu(Z`{^" A`hBhim"PBhq}!VV(- !B#hWJЂ:Aaj9vjt'cg=ݡLQw&ԝ@{(^ hp?'?~J9A[j_\튟-s?W{s?Wj/jtC;ѝ*tzO=] wg_A+a3:%E焥ܓ܅gu7tVնعZ;W s;WKsvvsjyvj~j@wUѽJNtbD?~ WxY+':Nx9iG p>w @^s^>G$hK8(@Ro@x9>x @w@yA7 Kԋz/geG@:| , *1| >Qv hv l5 t` x$ |g1/bދkA{+t/4s~$ 3=_c9>Pg   Z(Hy$}jmU^ ;|%O}hx!>ƈgb{ iK؂ xA$/ItC4]ӏ].Lj0-U_iICYuMM^Y ?7@< bŗasH՜i2E#).Ph@Ŋm'Y OOOO{6ל-&e;59{ ;K>{!ux~ W2Շ-ckG [: 3K'ɐCrRQvaȘ 琲!g4CҶCִ{CڴsC^n w#oXE(%^HН!D 1 @&7(A↙O+48v0hv#1\ `m^.x&],!|g F Y0oz8 kZT> BM~Uy#9庾- Mi7F)Ec\rHtFvD$8KQ,h"pa Û5q(h( ^(&BayщwLp> [#UUo|)]uN'9qpZj:' vM2RJ)y aE(* 2{a{Ԋj~˹+Dl_UU*Cx"!_{QE-AO(!h|{:i4P)!'âREM} M%wN+qP3kR-lT=[vJ0iNOMe?č7$y V~r!Pj@Tl"!PZAHA4t`<Hd)*8J'f) 4i 8hJE*qH[C'v<3cRpvRp UY.V`H OE(M@?#:3Pajܐ ȡ 7tjDGaSn$:OhwX5Cp9_l@j2N]E3Ul*ZmTBXcqJv1 BIPN$OL#M A?4}iiPIbِ5x9^CTU~G@ZFN]KH*,!$|bhj䔋KʨK˧TLLML G4/Qn~9Vsh` 6pPfZ 3}CI99FJŹыdQ7 cKFYQyg[&OI#@lPi C8FpVRT*N!p`FޙXv' R,MPbIL:JC`$娋n Ev7Rrśp!χO^H>"9}Gllh|墿25 m w܋#@"!Nq5qT.Epm7˶Y ~ )&DH!&kFʱ( *ؘIJ9iec&GN7+9oYn$ Ɓ͑][U0݁>sw;P%2t͊]3#A fJ~ 0s,O2)>Fʨ(c ,;)̶0㢳z% N(O425i8!d[@kV킽>xJ4w')݇. 7, &Q9ve0-:yѹFgW!_crv|N nPp vj0j65PcA1 dl6ƥі펴vB ݏ.Յjyٹ[`tBsV6f ٢eƉ6EՀ͍,l`dA`"+ZԸjQPseA44`uf+E.jȮ^evg*ݏK^Ke@Υ:RJ Q`*.S@5?01A͌ jZLqPbSЬN@b*T#X2a+iuXwuo\WKuCyݗL]u:IP#44Y@@4_:_Xql;y<{QF0-˗,y2Q|~[,<6gmfhp|OB'd ^r<'8*,Ͽl0cqN O`o{;3/-A0ώ.aỌ1_y.T(I1#BAgV$D $/(y%?b=Hdb(|KF=# e5^Kp=3ƊOC1^ޯm/"ǀo!I<]+U%>x4?oo1_?HLX|_ws'ᣄя}>}6"})GBw8f1¿_?s on~@l~ “ZxL/:7|·#?σ9_SZ w>^O~.Mn #y| "1"M$J${?gm_ }]=ճuZٯ=ܳ#0NF>ß4|ç;|:_2"y~E݇{No{?{3{o{9'.gokBffKObVa`/Vbl/Fb~8m@ci nU#~~^hr٧-眵i(З҇==eWڃ{ ~=h퉠} wM{i_i f2nBù -g#4vAYnj4dB/VaI]Re\[]dߥ]rߵ\Eyb5G0FGƶvfQ25n^oLgxJg(1~;aB|w-kQVkuVoVoսg)gvK0^9+Ɨ%v&G/|͋b]@ku=4F\Cqe4k-}cI_m=c=Vg=cUSU1Y-hCZ?jQS hI -k2,2h+ZzǪiz* {+b+{+§| P%UA#`tg^a%4ПVDSV}XeqTHM%54QRK/I5]4P=QRDQ}GeKS=W=UOC}~BDTR!e4 Ue҉QKA1=ԴړbNOk9AMuSSGMS 5UduN7m}S?=GKŽK~C]~o4)Tt 'W4fӋLP.Em$ueJTw%Umd5JHY!i 55I`$LdtNjPtPNMڃ$HzJ@vukDj~3Hk"꺉#"!" Ԟ >|jRTu"a]-kQ+i85P{B^ }X]Ac_TE@hv#ɽ/R/%'ax#o m AYĵa5'Pؒ>0vOu?f}6OZ>kS6B>pȝ3h 2~݀X|3v a{cvgړ-͛kv(-KZs3x)~e>P`>(sCY{sϘݟ}yލ]}}q6f[nuPC#:ZƵ7=>gy\_wG |ombO{Àk.x#^x}x݈Ýڊ8؍oxՏ? 9ґ=ϔ|̯7'qPku|7n>3pbWOޛtAw6lԿVk֡fi׃~i>aCO}GowL qA|Qꖳy늻^wwXnUVg3 0a%XW~]Ȱ8vXu{VqYl쐻xceȍcwLAVzB%oŋ.Jg,{a9}ǟ=ǒq!#N7VMV;b[9,CeqF㻏=7d yEF]WrLɟCQ|ʓW+'e¹3v{U?,GH;(wyHrb-˫a0^f ̎/p<8=v_pL6k-Pp ߂̧yNÑy2T|+$DzHUaPa5Vw,L>G:'䭞<ғb:H~k}S$~I{t/4OXn<#ǫx :p?t<Ѝ72~__O&~ ~њBTi_Hw0'~D|)~-sxotJψyן_S/=~ WKLzF?|{?Z@gKM_ ON?N(37s7~ c ]9f1r9B_Ogz_wupowvk@f(?J|z_xvv]?uLoTA3=5h;oBw|NWv\?Grl/'rz/Gx[Q|bK/2~1>k1~+-~)O >o o~O^. ޭ-q?B߅>|||| ܞ3|Iճyٯܯ=O_{{ρ-m~ki죇N5w?O'rFmPVi[hghsgoFsO݋{%toc~5~ьFgWt8CD?t=\'oFϦkN6gY_ed_eo_veOcq7p:j>fs͝o8C,ln=Ln? tO7kEhModW?ba?fck?&cz/6ay h=oƳ#gj=AgJ~K. hЄB m$4Ί9;ZLmYӏEՇe=׃=ڃ=X݃={M>/ he@͡,K2Ɇ##zn1ƎCZJ,i&1Ė c'i2{{/.| ߯A# -h`D̯JKi6Ǻji'k-z{-J-{.g i-\HB:Wү\Jn-ŴVӂZNi=YV^XR_aMuEUDWW]as5U[_=gŽk}gAW}^=`5"XNrzp5ԄVSUQWU5aSWkMa=%uGR\Iy#KS_UcU[M~ONQ-PI݅Jj#TRӘ:vjҩ;0*k(砶^z'r[)!q:{'j{b|˟PߧGº@] kOYxҺikO')Nlk;&ݖ6[L,nR:(){ڦQzw{({2Ǘ|(wj.yM’,uf$K]XצJ`&m4"فGegQcFjw}U6TJEoS47Q#ٽ7齓C >CM7׈hl^bDv#SZt"3vZpm>n,(n ) f  >_L|6AHm  H&AkZ{EP۫@nwzPܑ>~VɛGo/|{o"|! 2|GjOHoD_IG=s{P--wށ:|M[h9ޅ3NP8' .tN6>O 4>-MF&6{ᛅM߭5~Ix% i .7l'~a3uU;4GK4<5\~OTx @}v|' '>{-6\ćq +7?7vKlSnva_5SgE[5>ľ){;Neǁx!=Oa+\93qwϳΰVBwgo&ME3%8s-k^dk9-:_s3< /9 8{wnmMץ_bO?:uW??滆> gxKA=E/0 tM_tS\uWuEe5.q'&:?ˎ3 ^tF}ʨYunU_-%ċ Ywv1[X'gۑR0a&wGN1Ubb_:?qg|y#18F<ǃqAVa#k%?䢟som66?>ֱw|[q@?''OO;wSn,eL ~|'BF#gEV2b'ե b*wʟcZ|˓w/'.7m ֺ93|+߯Wswy`Na'sLzw?73 9SGp,s{p"u S}I߁=c" x cG Sml4~W(~U:9gkn}eC=fT<d~)OZUobE*__w @iOObwo(/F>w]Od>wk}֯b}/J1ҿ[?OM4zIwį/D{A@3 px)OtiS 8|ІeTx j;-|pkYx) T6?H2o._kI9AhL?8Z'zj7xz'x@>?2$U7H>JI~_I>'E>QAF/Rχy_wuo_Wu}_gtp~_7?6G?S/,*( ޼sM=-Y}]ϑ}~a~1J_*(i^Y,@s^ ԙ(pg@y zSz9 wk{7w{7o|6GȻY=г^?jݳYu {9Slp{=ܵsN{FZH/h=sN^nȞM~ *?-×g|~g-u<9F"|Psfϋu@=WatCg -EG]EsG!#tMӿI}ճmZا}Zۧ}ާ >e~Y" ][_>4 G = 7t7߈|+6ܒrSm6ښjs ꜵW4Gz37S{37{7+2'biHvjFY+ɚ6֎&v4ҜjIC)Mt^piM9 4RhQ/I]tF5Y7@kv5Wdq5,3n{*l=+V"{l.*~J;jm!}޶{*{{b||,ҷ" ŹX} *ef؛Sd?N](u6tQiEmYPnOAwѓց{{+z|,̿V?e2*[JT>nu+ڢOi[xR[҉mDܾkz 4&ǘZjnRڻg-;>O>WM~U)Տ2}i֦´&_,/$M$"܂Hu=wtwQgFzeLFSDQ'>S?1~TKaGyx({$=#8ۊAQDAIPގC{ 'qBN4WWACx;Ak$,ӆo2}tLP%H!@{Pߦ@~o?'cu>(\>W<yQ?=\~iGIX|O'|wGp,=&x G9tq :O,Θxa~8D>#yg7XesMK5 A^hP x #ߛOYY DhuqU-Z/c0rwk_Mӥ}:SϹqr -b|q]ot;EÒq/]mݨ˝ګz׮S:ӱ]bQls W~`#L:A,Q^/w=r3#}ǝWd|7fPZSW9y ³/p!B}ȱy#8^LəCr)?Nĭ<8r_n &ځ}=Of27OVrg+ÞԷLz; s1g8 {7{Tsp 4 |_+]/0,f!0lQW` <3`+ >\`6 }Xߛzl`i0$xRL̼t;cooBrcPG+^_v7@|,ߊ1`+wϬOAQ4ID@t<-է/в{Ny(C_1A !O8|ؐIA^x_m4ſ"]}IǼ{\`<O#Ƌdl2KOx?)~ϊQ4)L%X5K~90'WzKGoLKyYG1?xo8F<ڈR$\}XgoR_!~Ry?IK9\Yo! >{G@|R<|N߄ 7;)|\zJzFA܃ _|%~~Ϸ|lXJ<\NÉ= D/Qzg|(Od>̷s^oOCsI3e5>/ EϨPox^X~mW||W{W_g<Ǘ}TI/[賘l#g>ق  .0@$ h'@Do>ߛ<:S{;ȇ7o% dO{B:ў{EK]wqzI) tVRyM/9M5w]=υ}õݿ-}>Niޠ~bU%Zt+Nkگi:kNꪹF:H9mȾ -@? pOw89=4U5&ԋpSQ}VfnWaՖuTJm\uuΚدZڧ}ݧ}Z!~Yx7{U}תjئ66[Ufu5װv\zPM<5ҶNKZ,-4оnB Ll3yޚYۗɽݛ-Yy~铕i}ϴ-kа]izκ4k2^# L쮱Ȇ3;i m}ض{{1{' |/s2'c;2 YAȼA[Rl0ʶ+ lJ[km&;Njk>_L6_Çl3`c?&60_e~}lJ/Rkm-ӶrK*>Ң;Hn{-|/j|/j}_./!p Y…c.ZۖYm3f=sWq]mfEUYVwKYdP>U~Xc!sE~wa.߂}-+U"`-V[.rv+KXmj*3vp;MJ1)) p()DuS_WUZ^kk~N=LEj#RtېDݨPws{)ȧt[&ۚ?k,¥WbJ\hiq&w(ɟ*ͷ+ѷZOۤmʛ+hLIWU3)p$'D*;$_;R.ƭw2h"k.)*|ԅS/R0j({ G_p hpEp Cc)(8PB A^$[iAYtPI@\'<)/~ӇwZ}t㗔|AAgwAq؟K #>3\^Oz>Tحg=\O<6ggDoH|oGz$gr2;7ѡN/t.GsP9o,lyͅm<'&x60[ f4{ь^0!=6 AH˗—#TZɜ1ğa7a50;>:tAw6}mVk֧v=i؃&(x3yA7ao0)dku86s3W}gqq߯=IFq*Zv_qVOu,nsܺ쮏uGۙW"|'Qb.q'^r{-v!q20917ǎ8>{=o"s}dr\$.c<m,dXDz=^ rB9/rFɏ'9%価\پZƺMyhyS {CX<-G1e3v'eK9)gʝ_Ys,WǹeۀV`xjF)8;QGS&s| cZvqMe1N8ܹk' .x[rg,^`7:Op)s '9[6a]ߣ}~&#O7!x~΁c")1^~.YO%BhGzN"I}kܛ6< /)<`w^[J]y *#mOKG!/^Z=k؇`Q@x$? I/jLI~ɫ!#e䏈| :?cǯe.j^ϗפWҮE{ s+,c71q|!f8>Cx$/4b<y[__IkϳiғF}Po_9&9/P*ǀS_Cc(>CODbg~ n_5M}Ћ&=ǐ>)>2!rNԏU}QTCS}6S}ԇJ$':} 1-:/>C_ X i)|h'S:|_!C0B?-B*:|R@\?[HzͧzI4EG7L|7 C:F^ ^x v.l@3Յ.lt 9\ˤ|ȪwűG\¹{Al|p 5@Y55^ 87^ xs%k=]uK&e:Ck%^Q$ߏ>|.g }.'=x9ţϦ?{>RXǠsqh/Gr4:hF}YtD7z¯,?{=߳| wz pr(táeVE$ZbіG'"w4ݡkK2e4NOB T(5!AzK=ϻ{<3|<ȿoc- &m%0i J\5Mk: xQJ}85XUGYw_?~[y=Ž ~Q>]}ث'!؃ Bԣ{S[أr=ZM/<)k#޹#{[{{9+|9Ǘ|Ϗ p(n!]Z&rgGa.N;'Fگ:6.l9N+[Hlk--"m8귱q] r\sڈ4&!u⾮[.l@8Ɔ{#7ΞrKM6i{_{5g+6o6g}6_q-l mmmlӶ3}V6#كkg뭡-djkwmSGiqѽfhzfjk^ç>o7l̶@l0je'Q3(FMC{CZڙhj;]x6YtfnmVWYYesKխTfY=d{Ӑ=YM>ixVp\K0[3Řlfk0c[m27ކp,?1 ;-n ӻ)"0#|c|/27e_x\1M{:^7kܡ`s[{D{Ko.ͺ p(.2\+ *?l0菱v}> &^u[nW.G򾰥wߌZ}fݗ]pYa .UTX/])>txګת-/V`㽈} K~w,]`z7ߟVcV X5!UTQ>Gx۔fSM5~5TFRDYn#UZkRs-%ߩK8T#0x)H CبCD M%_|b> s\iʹ.j\i &u<6|_x,bX; uB'hJ AxQygI;"Udy {HmvTa#:2(EE) oP!^J;@G h8 Ά܀ò6xá5 @S&4?nSOܪNeo:VqZӎ=_W<{2>!¼w? -dwS/꫷a7z#ed"~d#9t%gɝ7Y'WǧxDz[g2GmF/ls #0+8a^d'. ԱLC߲|/kx'`nnŞ- :w\ 2k\6y fL2N05f`n 5xnF\p'\Tw pߘ_)% >I1Xg!,8 }ogÂoQV( ^OG x>HAoȀp"ACuT= m{y/*c>ɘCcp׉0o噸;WFW@ocD= /t 8y$Nކ}<tqN~[1&%_䁎)4C~!<گ1ѧxجo%ӷ_xf=_=ǻ~[x'pύ75^[]ŋxL\kV-IXЊ{T*{>$>[GC;kXs>Mя:=hg fyO9/s# }. Q!C !/ÅxUTڛJTjsO7>?*^czέK{HwL|Goa;WA{ ڙl@35wr΂r38š)>/q8PXGc8Φ@ZoizHh>1Wg6}`g*<>,,*80"'|Aq fah0 DgY :BJOq4r)^͞  7 Y$g&R,B F%ѮEVYytc%ҁIU*V%^M_Q: E4dARHRM^Xkz?(/H>~LKwiT3G9tڰ1jFVkz-FnbĺhQk [(~= =`e;Bo#Nm`Fr(éKR]zX5C)N<0k;C >3kB N" [pis߇|ϧ|gB_\|Z5YZ`8F!i 1k [ P챿ʦ3"־{=׻<+|ϳ|S'&؏|dͷg6mzhUPym# >]Eix_Wp|ʾwdpGxd7Kҥ݂Nm׹Gk{RmE΋[.Nn5悳 yM m"vxv_}-xؗZ_GyIE9P7^a?JX7r?%_c5Gej]wr%nɵ[n=mZiUޥuyUVKY au4|W"}1?ì\-CB<ķh)n$KYjqWcUsħ<{pSݜ6laЩFWb"hԗZ2Z/^k˯$8anASa4g´R_(qiI{"e8iÒHfDQG}xlGeSDD\ԍ"UW逯хk FcPy ?!PB 9@|ȠP/Rhu^ǥ9t!kȝY#WǗxSfgy.?-f ֎o?K0,d'9F1]/&dG9t)kW\ˎs/>m;v`) Y7O.`(SYF2^oba<*ߤVyU>D@|ޔW]R~ /9y&{|*H~#|0؛XWWy!dCÐCNȧ O~>=~gkf?ez+ڣ=M|sd|3?e,hi53Ef|?B1'x5/*( 'Se;,ϔxJ<%7rgxX3S|bCp( sxtN|=&n)#^Dѯ2=ԗje v+,kzGHR߂/@O=YGLj3m9ź+K=s*П})Pi)|i_2ʳ <{0vA v~VҞAI}$Y~@}ZCa0<=G2=kzOg4}F2@si2J/\GOTh:D1Txԉۜnͪ6ҼJ'ͬ2z-~R!_Q'?C]H* So09uR;z%*(NzՙX7Rf=H$r]Mz-6$Sa3I=I5juo_ Ip^-]ҞQKk1K QK;ۨmܚF+4~ac~c1X_g_- `3p tvY 2̓]wƜ @8xR ?Z,j"rsx܁t\L@re+7dXŠW0k9C5Z.b':Ō,b5TҽDdx %Y3 L'Ł$ i@I GV=nq( IP0œ)T1J, ;LlJ ,.< j"a)A-MEix uE.}ݸ:R9Ȑ$EhUbH %*4RBgW&: TRi\*+eD rА&I1qEƬ`^yLa6ԍq ɐ dN':`hÈ&(p`z$4jR"LR}BJ H.*H04 O5 `!E,1  0넄Hos5nd&CUX&EVa a"v V+=ZGp;/%P̨#ȑY)96"#*[X$?"`3w< W7յLg1U jqA%*&DPK<jB V|ز㔅2k &INcP Z#KN_^T cqU V`oz9Nijqb\1,F#'1Eф'CBT/ \X2ɒS23ƤQ@+"<栠G}6Lԣq`\1;˄X ٛH%FJY0!!/ 6̎j1J%PJ>YZbJTV|RóE08Cx@F }2ܞv'{`$9BBRL|dPDtP"Ur2'(T(b'"Y`)_$b1 el0)?w cq^W&9A#h0)2z QO$t H! P@+"V|rcY )Lc>_l0YBA cq\+W&92jCQKhB$ԇ!<ɏPi%+N 9JȈ+=XGlDY&a!+"(%b e/] 5IPb$tv?0 $MpdC&6D,A*% S*GPVBˬW.5NG% &̄LAHT.pťLqV\wǪHHr%F:Qb#N e R$Ƈ)GdKIqzG U-;`[X}ÂtH QD(QhPb SqdΕH:ArH#ImȆ)JpdG)HlR1C"-HQQS.9F~:f#qD.MhPybBSjP\P"Gp@r &Glr*G~lbD"6:\"CKXAelj^lĢJ&+"<*P9" $4,1BcKXLk,Պ :q7$!DC"K4L! ! ነ!ˏx!e ]p 3D1.^Q˜P]5@px (-cG($qI\=imݹRqB&.'p9H,@xj2>>ؑaKh! b` %6%XQ` P 0\x݀2vw.1흸Lk)U*#g(62{!e ^t _l P11VℇR -`Dk3 x]C+"H qGܯ=i BqB$. zT#u|np.\= I^@;vx/ލ ZkDuKn+Moy UTMG[$%OQD3xy`;Hy )/]Kxrj] go7 솠H@7ζښ0Er3? VP@p4MjzР03tCL^~rX" @>JdlAҩ]SʥtS0Pcdž"d2(0!WIWPRG?4(c@~B 3 \zRViOL @%@(G) 1GuSL;S091%Nq,1G@ƮqZf4WXɺMU X8geF!P< N+:UB [c,!I;TRG( ,4R`Q ,2ZdPղ t tœE/*4`˜eH[ (53α R8V@C/!`@P1 &%"WƓM ,Tr ''?^xh#5R81%*ltBF--4h'kP|nQ0L l%@JSUB r@ő KTѰđ JF DHXIUC@4A + R\ohZy%aP%& 1L aTI.֏j~\)9d2cB:HI!$tJ !R`JqT'EXyEB$&$|hF1aC u_br#NR%Ɓ*(#}T[=ϓ%qJ ZEvďU\RY *%Uk!$(rB$)d6aJq]xbSʁ.V(U#wlRuw[4{JK !%,=JH uiPHhH#JbJтˑTX"$W g亱U^;OU|eh9B.&vvUYN{r dR!0|d2I?)CX}"4m@s gEusqt\ R<ǭcE AE_lfEa^qc)XN}!H '0$Ȏ)OP!ZgP(vH6 %no6nUKa"T~@#ũ,Lp̍!ĸQZ&F)6ZHtAx04"dHc&8t}'fB!Mq$npSSĮZAb5 \tPsc*4`BF2^ǜp%ǎ)A$D-H32]P8aGt;Fqj*ՎEq!WW,[vĺ~QB4PBvW'ʤҸEǐ(X;Xuu#h5KWcj(Pq#@8BG&9ʐXv,"#ֽ Rφz2[5RqH9n %P5a,LYda*EGal;ȤH VJhF$0|q% ./g!XCP@`uqn\ UP`1Hi +>J_`!5 1!`TۊĊKpDE$ZP)-xAP2* quqH8.! ^LBRXf?ز/9Fx!`) ,l("N ¢TILĺ:yJ RRd4*dAֱE.*,tqPmq6A@̆CTT-e2j(%X8eɫ )HVQP)ℕPp $F[Ty([Vհq%h)@BTZĊ%Up2")-@4PĊ$' al\^t ["QGd*8S8({bܪ3"ոO$4 LAɔMm"E'Q\pe'(T0)*EɈS(Qj +#P (MdCDYTSprdĈ*AXGd",(DtP@D'mx/nUڌ P'NFqңC'9B4! Q E%+N$AJ*Eb_)l#h$@hf 0'M0hṸUj2.VK4 "4 QĆ&3›yw/]гkN%F좦Q]ح+t1LSVMS00M4Gx$]C$=lh`R8ܐA$:-DԠeF@ sME :p Xbŋfa$Q<*Jc,K2cEh-SUC\`P@T@K!t4g2ZB I\~r"X*9^XZjV.8F+5Hh! Z:eŧR( +m j@d7B8c,D a{I2 dÑW%*`UI%EPx rʄTH&A@<9*P)88ʈ"bߗg "Q 0p-19AH 4rP"A.a#mJdKƌ;bFt Hp;BK^\@nѓ[Cr!{ S16hya44B j0 /;lh91K4H&FlrG-8䀀)p`aOqܚ w_pPc*2TZʀ Nmc S8A"D!e/'f @c;gv/*ȭ)r %06Ru/^8&HLj aTsrHP NprG!:^HpHWێjE07baab1P0B!*`Tqɢ#ɔ;4ɰ# UDڸ2HK>г!f3{l:חkYf[cp%x=!)hA&T@lA(EF)!n4iz# NTTθ҃Q4GCL=!__=Q[vcUŘ,40cW?ƬEՅ,2H9((I0̠R$3ƕ0A@ 0hFR?nяeUŖ,@mQT`A6VD@re)24 #J 1JI[T 40F{3Bsqfԏksb5ݲԅ!b!rGAČp5ֈVI2 IRQRӅ-H|A@ .fCl)-;JexF r@D^ tȗX"\^a`q5 &PX]sC16HyEP=e$VD.Ҋ VOă,"XX项 GYJ]Dp'#=8S>pC : Q!_j|(.+Bb"5 V%U`aHaɓVPJABʂJ*ZPEE$ Je#!ž+=D{` G-8Fd! J(Ub%*&THuuvē&"LDYb UE) B˿"ٸ_d;QQU| #J]re%V^|XRE MHu'PD)GRQT)NxATAo"qf򣃑*>:,#ILd MDʊN@}*&$\,1BJTRJ9aULPX~-M! Pd &Sfxh"'RZpE'PR>qrBJQJIKU(qh 0&8MQ/FУqrܣ+׆mPՁUCQ@`@"(9|`qh8H9ggPjJjYAaLk!.Y-sc gKE:d+*D IUQ*QA<`B6Z逧G 8p^0jA)cXD\}ѥ!1S9Yr .-PhʚzdQ /TxÙ"4̅x 2Z3 E}D@@l u3,N#pQt\g(fU|ΦJtS6(!*pxjCXHehbBS`RcȬ ZJ"v0䊇 E:\Bk&"xhjV# )]z$Ē#JP&9M$DV#KD mSFBř!`IObȀdă)+T\Na r*dUJ#(h"Q 3-4V)`IJ/EY003B)DI և,>H8#J*+aDL0HbdF1*>XyȂ9%Ū%%-=AI7=N6(1;e-"fAli eu +?RP9b P2 Ѥ҈#JxВ$2P9@1 E{brRQp~bǨ:>|yK^V]tD,AQ>h"id#:h}@( 4)%hš䂑! !G08Pä`-t-/aÏRL@CJ+?Zx $)SNB* D^44# a?QJrBY0RNDŕG)V[VynIUݒ:˪uVD#0qԇ +?^PDF Ch8, =HQ4L&挪t0d* FD/:Lt,S$ΦbڣS3!S&F~cdV/) |#ł7@yQ:bRqA!i/:Nx1)ZPgo\!c !?4AIp@Rf蚠@5LOL(-[#DڀszS*˜xT . b(zALL@F 0q)T,BE#XXUF.t G12zɒRhʨ"1jh~)1 $d`"&T`v3,x*료 aTc٢JUK|dIRTQEHc ,%f#]Uk䄟 &'B"d x C6(ABW< hEC?PFE-4,0CNU4B`C@Jrܟ%'"0 xJ#Y:̔xaQE1+xQmQ:P0h぀f쬐\+24Re #F/Tqu=##ZBHD8EE*2@ !ɒ1|QE# ,$v )&{-dh(30PyW!b\c|a=ad˪ $XV[( P˜r o;9C@`q ̉ X"uE8E+,LYu * (KF]L9颊Xbt I֑{Ť )cjAd` E!cP SBȗX"]`E"b,&TaUD '/(bOUDLfY3vYFUA ""aX´FX=eD+,IXBE(,8YmE i)G0"Dr9d9cU^9%?DIkQ0̮J0* ;Nh U:":4ˇ\Bc:° **]\ڠE9rpb#f"!uZ"MV`jtIS%HR&a-RDN^g9hL,kU*6E@cq$d:d"H)W !1uj)AV i*DD!PPxȉ&@r$,[^``H,];p+>L8#ՊU+?ZAȈ)ALH9 O4eiEC$0ttQa#Ɗ/X;ctElYR{bA$HA4H )B^@0F"3% J;a)# .cXwvY݁Eu*T\Pyn9r: ,>XX9 iR2D#vK0%'~<f -e8#KU/:\xr#)[Lyb:˩VHAȎ&Fak2ET/?\$%./ hbRoA!")`Lsb#S]Ny؂: S.$Ȓ tH:eTKA[*RNZkYJ&+T 1 18X1˩\RyBN!G&"OY0"]FTH/ Z0b Vg :5TeNk sZc6\91 j]Rw(4Iʐ? Xf˂pMBA h0sep%@UPeuNi(sZ#6lIË W>@3%S ѥ(U+C{fF5`pI`CT< -TX`F Dȁ@ QF5F03pQq %PFm k`1{B/P{"&V ̫ c\G ZVF@re*2@!dɅS4޸ƒNGQ,$ B9!(JE@QΈbRZJar*eUHjU!QTBI%ĴH %HH ЄD*%dӼP2@~ VZZaqjeHjU URRA :UH(CT<%K${c;d*gLZXgOH͒cKV,>^ Q *AZH œ D`0<2H92VtdaaEuj\PyrAt6ViXL}r#AfD0G#9)qJ ;Xq!I'L18PèPi/:b0ŔNJ-/QR:`hG7&FfPs-j;dBf*6Z@:FS&PT.;fhAqR8Xx&J^lR32bv`uk4DAK洆 LmczE1%+|bA%HsN.;)!Η1p UPgF25d8C `Psʣ>D0HA$K=B˗G2dx(Xa2P :E4^9!T7¤EuǏ+>D$I`&?TA%#x /bPq+@b1@Q aRqvQʨ"P4(A䉏-(XLtg>==<<;::99887765544ۭVjh2XZ'P #>6  . & pP 3`@ @e0FL0_tEK,WT2EJ(O4aDI$G!2DH ?|CG7lԠ1CF /\eBE 'L ;Uk+j*iR<;:97654a.|Tx2X~)I"'L3Pj ޏi>ky"φ䱔ђi5G 9\ b?3:xI?7u?BsXb|=2+*Ï!y< /gmP߂KKO-VZ'kB3lVw8lDeU?uDZ޳/|'U2<υ`)C.|t?1/LY/l%O_4?7M, s=sm} 𽒾?7d5L?D?7~O΃|Wk=kq=1tYvwjwυQxAyc;t#4puy/y,":גjOb9}dۇ3= ט~/o|_|O }G1OQ=S")~-~ެ^ȾGuk~cmv^(;?5=TGY}TG=d}\_]x^^^OL]_@]Nˢ^+ꥢ^)ꉠ}f&qvI#k9"zOJW%_%W{$+P0Ke0}kkhzɦlzGc.b7Z.G$wv1"o)z[TFHHO4IYIi4FSp` @zI!)0~ I;#jN2yf }v!Yg! }3$tZ"/Jz#7?#?Z{#'|CHF}hˢE4FdZC9K 4sAB<Nu FhhBz'r"7z7*"'{%&$A49VQ5pVʹ/H縠͂x g' |=?@PZHhm 5PU=Q=!.a~rV3 :3)-PّBρ?>3| Z'hzLJhq~H遘>郦^ꇸ~H^hD z~cЁJ0iz P$@VA_TAld h&F&6B p!hnf9z-l~QZf"lqioxz@b@pO! grFz2Lj8/9;GxO>wn[dU[Α\< PXH?.[[y뚇^a-Ly_p y{psxzأoAtq!QVF~,+|԰oX1%=ģkÛX/nzޫzܮ-a[7YLaBSխz]uGacęXr3b[12mmc},uD5'~bf1[|X&17=Ǟ8%q@>\ȃo#dvώ2WƺPh0=f ecr<;e?J.ɠ?Rμʓ_q-'e¿&3춙f`% fZ= @K8Ŗ/^>0̫y+.?'1~؟UT%~ a|"h<ZAL~? !$m~ Y~_x&f:0~ˢNKŚ'yczIa!UZDދ=ߓ=V;Q}v~_rŀ軒~鵾ҸהC_{{qYm2y0Bzz,R,30{2ga}jw=󩽛ڋ? N}v-{oEᩄ~襒^饢~ꩲ О*@%[o_CSv=au}Zu;q q=ɢyi|~K=C!C9}GYKG=TT}Vo\}Ջ^%b}շ]g]}+Y=v}nGOA4MFߤMMOSP^UPnM_D}!&oNM=ԗIiNUsSGQ7 A3S8N?0]ZdKHLNMUN`NqϴJ[4(ēvΧO4:/ Գ! cgC?w:ShE :-h:z "(,ĴP7ANAQ=iɽPP @k4e $(Кf`4t?H(-泴S>M78Pz:g @VBZ?Da_tEm_TD~/@kc=F[1:H0zvACҙ=K/04k;O@ͥP:S/PmSu?:Xd5͏Sz  {R!{!r w`t7M_i:/pvyԙ8Q3H x3 m6UXUsuV S4Y;hD5Q A6^| Z{ fAnDA|d?3W-Uo!WEh^9֍g-yo 7hw-\}5RݴH=4@ڲ}f؜Mn3?y?s;.a10s}dir om.l>괣V鵏^[hngqokm>Gw{y _|^d3lmK7)G;Vn[m~kj{^5|77OW6 mSVۨa1{{켽[kj迁gs\o'!5.-_yo[Gp p:OZq<3n9/ ~\?,x'1/Gq _v|݆<ɖɵ˟˅s9p|<~\Ő.9[/_s5{+L\ ia94G4=^WGn~W21vA|Ñx脋zn!6tQz[gu]uG&~(7|51).}uEGªcz+._|{~w6q)>{`8K-g,{q: G=w5,dıt.{.Ƒx^cm70enόyZ_׿5ÏR;I'{\g(Z']` F~Qĝ'z׃:8dx$>ƆO@?:|Y_u o<[7'ȿW k>p>ۼnTq014M4j6_,q$_? x٦>m> ަ >Wr _'y?m;߯;8w#5G'27[{11}o_)[| |6*g-oѼ\ο<Ч Yғ=XՇm}؋ۋPJꞆݷC}k}'=ٽ}n6Ϧj=eC/`M%^X_b_rշm@;q{n?A߾  ^)iQy{e>?aCu]K[U\^E\le[k`f@{h!3>1-^ ^ ̶ٌC6(sg9=c]}kk}_A'R~VS}[Ʊ{7.{'?.FO=`:xLE᷂>诒ު魢ފꭰJު׀)\Yti{z= v}]v`]4cquD\9qsNiފ諐j駞~JꩬJ뫾ھ_q / "kjj̲<,ݰr&yj+z69g+ TMC5SFQNQT/S\?%Sh?SyEOo0sS7cY0pvJKx9n糩~IRAE%45PFNMOSdPZQdQrOTO=?(@LP>B\F( >]SaNBg5 -DSELF1Ô4ڥ_zow2{'oRCX0K?aY o0] hR9~\Zp;۔&K:+ i*>:RZ#w9z$Oz%_zo"wzo OGC[p$# ihHD"}# 3Rhi""r:ji!gj!DXoH\JbdKl$KzDIot`h2CG3!::(1ӓCM 9'\PiARMD5PN@V#a-;@\u='}R'=Q>"5P1@NzO+N@Ot?W`MuW>[GpӵA>`36 Qag|g1>S3:O(|6ʞynv;K'l*tN)(l {vԾQ[@l/CrOdD|ODCT<|g pspkS?q[&l@l~);l1hƢI[FH[hy'n | b;౛ͅuw64ĀcN~߈8݋͸ڍ8؏kyՒ?=9ғ Mϕ|v͟EůwC8NɉX/zvONߕ}9ݘښ9֚s/h 7)$/[~+Dz?Ř9n9Š{N~g8o aA_;tEZ>5.6=Կ/=93d ۘ'=C_N:+bOS{Ygu_{X8j'}SkvXR6O2=Nūz"0n9!7'.<Łob_vͦ8ZNhSG1|o0"Ʋū؃m|9#1;>|v{Dfe+'S奩,{2b L8Ũ#BនP@ alâH((@(RBJ$ŸO- &JjWENkk׮\ QQI! qw w`3x|O6s >ދ޼=0M'vcrX yzġmy,O`o Flșkq+.Fz&;̭޹UIza:ҖWmƵ+p ^ކJhNVOqN:soX e%۔?}&=A0Hv9H}ts*uOWCVmFZ^faB{kj#5BDc5XNXXmnR93ɢ*PIMsAXN^q`8#ރOK+A <4 \雳@WVÎbhwsTNrʡ6yj)8bQ/rB3]2U`n~)P7ǏߌS11L;xwh.Q]DDuꈪ;4w%j(ԃiDnKa=PhgVH|6qM.q3P81}-d#t Ju=.g6b"K$S Bg=nXEx߅ K aD @2OaAL0Y黹xsɸj!lzQ5گw Q< !讣j.DM[3{4]Ŗ Py1 ݭ `aBF^=җ?jL2?t1NA& n| 絃8Cd`Tԃ0t @TA P1!@0b'B^'q))fI_;Wr \ČKř% \5r%ֻ{.D~ IK)7Mİ X}urt'`@` &!'" uUڳVicx]N{2H/O󁹖s]Z&H2P{0h,NQmNa?ivbN"(7ɐbS٫ PC0 beO t*9$qb#?(!;ٗ9KmN.?Y?sQ]-{}~'U@b0Lk[E(x3%]ާz(B~]z Fԭ_p yep\`njPnB:dQ6,2[JP9cP7`Vt?MN`} &z,fD<9p='uD=c>V ~~AE>qq yN${;ܹs =[~'F C 1SP5<@XbUt 9vW{`k=T@.>r#9L=nG[\s c4Oer"0i!~:]"ʜ@ד>۹Ó@IvU~5Z9|[D_ѿOL?^8p & Aڑjv>u,?Tc>Mo>wnn~ZRR||)ޮ}VrUoOq-U~ wQm";?u׮k \7o7{Y07Bv S`mlbʺڊ3M; >Y U80i,i::_ FC-O>ODپI&ٛdo"ٛe&ټIecW{Ji%Lf”ڒRR6JF Qۧk$cmR@za2VQngny fWnɭm`i&j42vԎՁZ(JB50Pk>Y[''ctdl~- UrQH#(l<6(rm,nua`jk;[6ԂyZuگm"k4& 6Z&-^$kxҐMH2Z!Q۟òC[7AcX^:vV fuiNhڭeRZ]ڬ]*!k:% vUBJ^6J&9Z#YKGڢZKakJk/lyʖ mgi{-C۫ZʲviVi6ijTmUtA&1PHlILRRX5bE9 Z >Zyj:{H( Q}/$Jg,[*cV[VPugQr.@:Zԓ ҞzYsjC7R8q6E¹B@ς>: y*(U8UU8_߄>Bx'=Hqc5la+i}p[ eaQHqh$0gU)UBkD01B`yK:@U `n@o0̃FÃ!X^<6 6~[[2pi[FL4te+˒H;iNZM5fJS͡F5Hk*hv! @j6|S⵼x)9^ wpyjCJIR+wp"d,[.fc˟-frQEeҊ &!$0Bob2p1ˋ "MAO  yk/o.'~xZqXw '<@a\;i)2r&#PC+O~WT|AbªH[dD#YA|/b̓x1,H~o dO![!bȇc" "u He0r,3I%ZM4L>t۔/*6n!D= 3B`w߮\Qpk(ҹ8DB.8` ԑүJ=;0T{ta!䴌rȆ/" pK草&=rBd &Jވ?<1͏XQE'se"x<pɋIB0,_#13oQRPpH hb^N_IX \:fLБDD%V,"R>_Q+ 6_5t/*@ ç~D,!D" 8ǀT|1H 4F  )Bȉ0ѦEd ^4rTdH'- WFef^=nRM-%\UR0xg"U f6.JP8d ,eaʳD-Gk.+(sЍ5xsOhsGr-M\T=`MȐA iPSjH,aT.4#BoC :S2.|viGC*PC хZxpƚhpP1>:tܓp1pQ;ʡpI9.i9ٌzP2 "C'c;ᅞu6x#-3HSQ .\Q#ԅ Q~*Fm0Qۨ4NH&?8R$Q#9dz^Y ##lEv"rL뷜&Dv\°h8 B!6qts#Dh:ELM}fx"f-Q#՝PcbktDiu?2ݍ:wȵ:nf"p1c % B,m6PPLB)=\9OJ@|:yK &JjE,֣1QPZ ^%*Z'w^JݑF̵&=jpRFVJK#p'{ Dp!h"^L3auV* #YuqR:6BG!}į%}4:s 㔽'V8benF8a@X9KLFt)QN' K'-+X;V,ӵFisJkuB\kUmm,Hv&R3JZ@r7]1tJQDQۋ6j& ( Y83!촠itQFu+tUk}\kZ ztn5baꖐh$ df8.A!$Hl> H؀ ED0NG TRNeO! m $׹>h k\+ZךupKX%n ĭ ,n!7@ iK-@L d1焝(Z!Te6iBU[UU+rTk2\Zm_[Ӷ|mk7= .ػ= q`E'{Aa6FbWKD 'T}.d*.nў9nOt-p׭cy-hkW): F`lN]c Б0ΎX;aSLnح׎咁Z'ۧUZ$"?3\#+W^eWcl$ [ }p#y `-_X vۭqeCn+۪u+zNzȷE~ d>l3<`|{۫rɭo_VB׮keGm&etzGi;Ը@mc=G nyzW-scka[_[^kUk8ڪ JT_vJ9ڨ"_ ld-SMJ[β<[c8as[Zy<>v;O_'+\jC [dM#"YvɪN֫JL5zh-|ݲߵ߷~O[\P@>, IB>7vQRy$]['-umee.ZV-iFڶ6E{8 i:/e $D. Y&!'cV,І ݪʭøY>ӷ_=vebTϤR#X,Tm Jc sȢr`;kLvܔ4!E0wX`<:}?o$pxZ5BIt. OFgO(CVqоvZfj]xu-"i rV4/ S=G 3З!]?kHXsiy ,͇7x)B>gm67U>p,`LseL$]>Du`gU F-F$A]AmtIZ%FhHa_l| τ9| Vz[2uufn7uor;ܓ.x⍟7f0 @RG,G( d# (S26X %b⫘%CYH1CuTV~ۓ#oό'b|{!Nķ1ld"2XZ4éFt/M:5ؔraRKP!_ǚW0<\!kdpB9T T,|BEpGB@UFXipErKM-h[0XXk0&.qcg̐X)[pyg^?=xn0.C w2ARx&!#$?(PHXb&zN<:UJI5sЊ *,.)ZC]lB-!9|>w\P6y1hGFc 6!K9Ǣ$Q|4B(Ge |!4ăaNZTR!+FXXlt=P܃d=?lA`hs|"uZXNcRjhHVNAI)ed )n"zA*#/ԇ: $$ S<2C+p ;'D֢4nM2/4!5x a r},agd$A,01۰ĬBӒ.DW0] !CP$8ux!1H"A(1jc2 9(ɚZ(NRIpk(iфw2u8XH*@@ %(OXJAiPf]@K5ʦrZ ,AA 9Cn'@!+au Z4tMkzkU@\[CJ!&La4A #S!h2H.K$ #ߋK4y%4̭zF*j]TrӴu>,׆V@kTZ.:ŸW+-\!+Ip%n$}YG`b3] Fidx?ѐ,nq!0`9'h *$~ZN n°)νNM qVDp nceB]7ь` m3@/.w=& Y.Gct0ĀmRQL07Ej#58JyKtt}DsV{z+V^w*^-o -a}vۀP= o <(1܀aq5?bQ5#FڱIZ;uȹL|[?gㅹ8sA z"}Gyw۸2z Eo/@[#|ojي@_T4Gw>c<_c!oX~g-|ĭoٹt{A n[75tn٪حMrf: ކ.U\*Im2D#= >g36y3\=nՃbi&ڿk"4~ `b< L%Ӓ fҐ0cFl#*\!b0% 3U[7[;ݻV-i ?1N?_Lf]YVUݪ\TCKƂEX'-s ]4&]S\7$č; \> CeJ5i5*Ax3/^KhS+fAΎY(kdѸbֈmJ]Н\;ՃB\&L[D#N2!ZeU@lH|OIJ4nk͠MmAX[#ۚ$}-M 3tX@7A; !}yU #U 0B Df?a^>ذ!Q61^ Ty"e{ @]"@w-9.c#nn uqA.{q[8~k)ˌQ0e_M' 0]3v&>hssAQ<0ͯs\ w\BH ,wD> ޥPpo 4q. YFb(}{`FA: IVlm凬y <-ࣝ)v5 ӧ8B>%jQhDM 5)$uXӨgfM[ϳFLXD[-~a:C ͒\|k3σo1 XIb%%t RP.Z JRiK"c.L ~ Zw)*Z~iX8ͱ1tǵH.rd1U'e!pf#RiL5r '7I4@Kbx! f *Wsp4qȋ>L"7 8;dD/&*\ȲYJ˛(p 6!G1kE*]zvt0͠iEBI +PEgЉK2N@hƟuB%q]1AfUX ^*I:;҇*aXSzvA4jAutR9"2!̈ QF ! "atyĎ$082I +n8X(fJE8f$NZ9NJX.eXZ&)F眮(cT[Jc.)!4'T*6  'PTl2a:PInCn@UcU+ bL{\krz0³` 0#jT_Bw> :NF0]., #VKEpD15b5AS8Gq;:JQBX^kT:UrQAh NBgxvJ|8` i$ g#L6yBd , R f@F/TĚK5h FQ|62P!k9L K)Y0p2ĒdsK /.B( M3*o"0DQ!9V/>p{aȭX{ʰ)Ͻ^EU r/E&L4h~idK&TǸr_2LBY%lglATI@ PQ#`9a$> vգSw&mڙ0u!mt[@ұ91v+nul96Eߖ0U$\I}:<\ }$'?n볦G{m^rW-An.۹V Ɖ[8-u&nݖmUۑh1DZ[kUIOKopo}^+- EA1(.E\%e9\[)y+)[ kgkxmפ-> &D~g@ۜ/8RҖ]JrKHAŒm:*OF+UUD[aqK(p6,nɀGm|kx6֤5<{!ďVtSWpjRUa*#{5c+HX-T`N+DeKdlH{(onmЧk yڴV yЖk!Onq\blWC`_:6kŒqy3-]\ gtS}Nz$ضqhl'}VGjEJ:>{R|1w.]emWȦ5{ƑnXY> 2\ 8NpI!mC,,*r*myXol!o7@[8sy+g.o?3m;nxPNxbB] HS&jpMYd c%<~ PWsR[ Dd$@ +bc@ilp#lކw!) Ўxv ! jNHx7:S^J78`r5 . 7ʎM80A$Ɓ *l\TA02'_hPUJ:b&GH9B|tcĀ=216O.& $ !"` @' ͑4 P,puS[BFPJQe.ZRfXzv0г, ]!2>(|!@S$ %@/QH2%.,UwHjGN)2HI-Vw_ EƁ+V8 NDZ YS.ثZBw((A"@A*NI8Ԅ/FM\qQZ 7`r`1#D7R9##'!I5>rFXL-k(9l>`R4RR5 ZbI^H$!BB%!L1b&`Hqcԃ 3Dpb ^d0jLhI9ɎH)JK"BِʱW{})е6} 6X`* 0fÊNV]arc4"-HΨrK$3kGtT^8LrI8V!ecyP5JTys7TbT&P]AU "Hu!T$ $A$Gpd&'LrI^6lC"D2A#r숀"5 da\X\jņ N_Rp2>H`J#7D<@cĚ" j%dBM \&3 b }@0v$`4cplP؋ byN;N) U Hil*8:L0Ã?>DZK>ptҡ h B0XAQC `6Ip03Amp N҆i92B&@X6+$b}>,T\f!GjQ`A @p  :pp@-lzpSL)P32Fh@}#aDTyEm2@3Q֟Jn0 hq؞ ˃֧@2 ӈ*%lFQ%K3XAbɦ  B6 H@@Qu 44:f.+VX +lpn`]IsIp)yM%h:9 [L1,C*grR$#>XR#N9(D7P` i*Mv&GlLWz) }X /\,˽BeL0dCRF32:Dz (͈f G @hೌcW7dJ(q\bEvQG3UrصFF8bFS=B۰@0dU s|r`i哃J-P@'pj 8#Has" \Khȣ =$QF&U]:e{԰b6Bͭ.5 \ ˣ5ר$l~FӃ2;h|sE 0/@:hTnI42 HlH {Kl;aS7uF7Ls}GKrQKRH3"8q@1P)Cy@3'DnA\H)dq0.b%|$*WO΅r.%1 J.UOlEUrH$E"EP|28v!  ށperKq`\A"r:P)enأn&inZ).^(Gp!F:pE(ܠA:i@p2p!-,=p;Jk;&Z+姹Iuָ?gשEl`!$I$0E:!(~XUp.b=2TI1\g#qZ,nDžS\5P\-˕\; ?g)5B K sHtI@D9~{dKV\A' xp.{t%m㦉Fnj1Rn "=nЛ.OW^"aB;rvPXN!?;m!}[A2+p.C2|AWOG8\9ʸh Y5Hz7?hŸWȥc`,щu2OEESC[5WB%4ZB~^AskŽتt[<-5iFkh_0=lVR2*bAqX'˵bKKDfI~Foیm6k|iIKi-Z}6O `fv<%cjKLv9Y/ CZlXWRݲ- {jXvոWn*pvin֐2yj8-+nG@W$(YKfަl5kӱgl.6MmIJiXх+ܹ1ʵ kܽ,]HT*P"SIb+/ \y&jH8ͯUs`ߔe+nz9 stk''zp&kxFb~[uWq[–Oղݡq>Iub08B<:SN')Y(+w(d kl |׹xH縑oc{U 0~La%d7Kce+c^Io>J(`JF2$"y肄#*fshJQd#̻D:ptR𚣓JMH^"yB N!s4e$!qRUN㣨IEL9rB /9 jHmÈk$H^QaA he@ lXtfʝk6+"u \NlqL taH]@0er )&PPii]N\f(Y$lT# ҚmxXj=U\>dy)^:5îH if`pa QAN $F4a8BERrD(Yr4Rz!84\ܤaj 37}U%ۣcyZUJZ6,X0E81P#7𴡄G =J]D!">a:Fgg W:vg8f _8@ 2-A9A(pX3^b5+6HFi23Jq4j 1^!VH--sI(R!tC&Pye&xz2IHfLu*3aJ$B(1cj Ttȁj(+<b0T8AC9t Z ,R%j@,LX{]PYI KjH-y3V%mq@/ Q""J`D(q%r)AB'n )B 2XhcDB4X":J%/N-V)P_}UdAa n MmEBڏ>B,ZRR0 |P!c+ha7ZpE+b0򡆌"vpaB ,lX!i.AZ&ycC)=٠+oWsGERC`/P@` V](`E Va`UF6ԸF .;j,#FEV S6|`Cc.CwA$@ IL򁅽#vlTUR˱D`-GPu)îH@paj ސ1@jbJ̀b䅐 PT`eD \60Al.Gm?I!qpVHd2!dzҚ- S?[tKB 7V$:\pFAb@jDHJF+-rA4Lh@ ' <}cg 8:4sܡ;g>kV&6k$vb2,OZN{Ugc^و2]+R 9"(p$KT$ EEXR()oN4D|Q Rc5:sC%DPda25X6C+Wcdۊ=2@`c5V&@*) |J1@g l)@f X:0" %X,/$S Ê8APiFO+xb]"Yx-R@@as%֦gxF@gpj񀦘`e $T3T!@%LIԐjB#K9b$Q웢-\[z(6*NRPS] 1 ]krR0&hv A\0e `5 6@J 20<6AIG3hJ'0.gիp%֬rX):v@bStN6s 0hಌ ,רZPI@*:)' DrpZE&!G66Iligr K{6ÞX?+eaFhlsy*|It-Š^PyJ6L` GS /8]S5zy`J$N$C=BI82Ky'.rAR̰qLqu7".И`y)t)ѐΙt3*G D:0 4!zq !ʣN"t"$A3eKv&e`bs?|y.\%1n N.Ra.V^@WI7D:P +ØA (F:]GH:I K"i 4,ET ]K=s4ݮҝ ]9"ܞ, 'È'<#HfIXGJcL(%\*yS9hr[X!x<-3t 1lNy.]YnO^>wG47h@S8r7؄¦#hXGb ]Kh J%&Hy\HxCr_P.㐹wf˅R~TG?dۣW(1K`jD0lD;eXvISiE('H.a\"qs@a DQ}.4s/pnndr%r "MOhe5se2XP6IuFs0Ir-,5% ߢQ۵u+r[ [ m%vfmvHAPJ4j,^ ؑ{#;sT5&1 v5NKdz#p e."6)$b^[PE*-!Դ;@ {tZ9{q6,S1rL+%2'pG-6(b5GCܤS9Y&#  Mg9;+ʎ,4@Ȭ ًflD9"HvB`&rQ h*hfJ *jb!"@`1W `$J+=Gs,@(`mrV%ы,+"3(DҀa# b8v@*)LR8qR+/HA982Bֶ*1Y)s= +њx,:XQ]RIC&4IC,26-$" ,|$E$>$t R:18Q0E&nj, @~*w8@S76P !61Y eBvKDX+҉*4/VașEpS$c`HReH( CH(cD464itJ,EB1H4/ @sKnYlZTE'4$td^,T"X iVr"f\x0BF$cd*cC K`d?NYaC+N* ,Y$'Ø\D3@! `0V7*'`5Y)5#tc,X)kE*@Ҡ($ CΤ QČ GȬd K¸d L ATC,R$ Q)`(+ l .q brPv낒jLh:(,HX+D iJrD"fL8BD$cNXE&`P`E $:V#liGFz:9SyyH/!jnzQ1ٮ#[u!6X#$CΈpAČFȈxdJĈLL Bd0'.:$XjFi@R4Bɢ%ٯ"kcjY֤C͇@|!D|Q̇GxDLJĬP2eȑ!! )C :ؘA`sSK..PI tIV-Zz=",U*}7InPMr +9܄z 8 SR<D?J!Ha#$Z@ %:$]A 3ϱ!3A˥bHQYV%@JBK>3k4eQ:x@0Hpi"b^C@tzZ%;/P2-XA۵b@Z]RA@hZA|A@pcL`|EMZ@`E P Q8fba#8xK %0r@J[b WlWFfSaKvK<83L8ϰ8` 2`,`JK)lRp"Ig$| C5H稺8ݠԄ8`Y' Du)6zX); kday4iuPSL 6ѤX@ 2 @ae(XRe&`VP#lA>@l!* FS BJUCAl`戭$j# JݥXiÚ)р d!&%t@S K8#\ J41PsΆT+HEZR5E a$C" \];5:Kݞ {eҰkD@&x PN " B >XK9d`D3U 쀔(-0$QZ ȹuVmz e؞+Խ6\ !<<@)$xp#)CI9P`rΆ(hXrALPhvPʃ!x;J7tLK--W,ߏñq~1Vo:={yxw'|uGrK׸n˨fy{iAn?tJl;(UXBJ 3GV#+i`;H vssqG |%NS6t Eאs-r!p7VnSET2Շ$rO3,F ~@:` 9W|0I!^I$q#dIFI.spIv݆XK[_^KCJj Dʔ'KY3r@pX0Bp SaADEi962diaɁ( ؼEp\l ZfP2Xk=Q ˍU*[thG,BN`A2z%+ItR[4QbB(,:p@,.BFO0MNkl_ꔩUY4&k6dpϕX)\bE*Zh|,WtjCu#[0RIOB$Y/@,& dh8@/ d`G[Es`lI-6؟k=S0AEV(K Y ]  \pa%EL<,UJZ#FrGH 84Ts`ءkoM6삑clRSPQE VHqK X@ʋ]( ^xPNB DuSfLg*4]u[cqDK2؟)[PE*ZP%E+XRbEE.TT#ŋ/@\cƏ'$t,J &eF^4 \.<# :{QVɾ!Y2:!EchOkcl)1h)!J3\)AK6HKAXIU|F6%0@a% LdЉE'' 5Y>$ wWd쀬eR?dJ2Ɣ-#jP2F-"nX"",!vlG*pYɕ$8y"6E$$ B`ib`3rzC n!KSAe Ua%Wx #Zl%/Ux(ӄ5DR@8 O p2EJh,,Cv/Ȟ-ˤ&F.zL-Tҁȕ,I` -V"&7D!"ET c2>( c T@1A,.H%}h :4N0x5V0rX&bI*H)FʔZ1Rt2`=cL(E@d ,!HSz`A0\x ʼn ,8 }AgR3 3qƮX=˴bV5)Œff4A4n HH% 0Bj ؑhC'RdЀQt&;_`h c_KLk+KZP,/>Ԉ&5m|,z`4GPv0= l)]``H}`E J`!䠮'^X.ms,7LrOpKo֑jm)Ė3ikKC=a]7OX['b<읞C]=<ݻ >\\÷Ml+F,L qRԢ2!ՍyxQ$ ;{.9X?F&.r8!P1uM~.b. V#J)%v-in(S[[iis}tGh(cP SE%:0 "'7UAX4 RT&'1XBJddF/OR4!D24"E b"F9:r 7$i!I QKSBVtQ?~kY}/A#B?IxFb8YWug8YpZ xZOԙ7nQu n\fe65U jl]De _'~DჀz! M`]D램pJE -+ [U]؉,,Bl;c lE\*̞Rqv R U-?gL|G5R8l00D\aG1"Ʈ DYIEY3EYE](>nӉ0[B"n(r}ݯF~)瓚>'e=^tcbAY3==Y3R|pEͧj k=#4jpw gU@g`w4Ao0սT^J{(C>)cU}*E&ygA/lЊ[Q#o@6F~Nh %$ͻ:Y K~ەF*8[C~!qkhF~s>'D7[RyF2@IsB#p =97䥓nHIgPλy@{>}=d$N6)?/?31C2ZJʼ:yD\#Zr7Oť_RACka梵/Zx[fccaVtyS~$SrxJ_)-|o=-B WD_ɿ&D=$\H 4BDTly|RF^)'xQ^nFPE#;>$T6~GN0G."a2 -#! 4=i,,xH1QhbPcO#OdO#%UӪi<Ч1Ltb'rxu'6? &!k%znag k-k.k0k2k4F\Zv㮳!?!hɣE W>ǩy7l΃!e%zD2=1Cւ #d0xvɸ q=AV5Y+1V1Sq ȃbB +$}5KGZYqy03 Zqݯ:^3pl\tba]ˇ+ \B^"VCH%#f9 s|f,*/+E9joyBcgZ^ޱac%7UEBu'TH&2dSL1RO!e QR.'T-eB.1m_Il:"`i(x9oUS_u y|7XzISD)IVFeHYel?9ḤGc42GsCl&YBTGH%dvYylQ3A^*?bV3 >es>iVѳ=lfT7 pՀ! lh}'q+;3F}ݧi'2k-nf;YӬk.s M`"p FqW`'s \wm]R<^D EJ;R^0wpG0ف Q`$Sp-XqX'35XLs“0%'^gP#!LJk;8<0kp7\18eLgpE?a̺wwwhBZ>x:EX|frlg%y'zmޯޥޣzv)n&|KJ wwww V@{_bqnl:??A n^ڗ{/o  ?r}vW,GyI4'o_/ 7Jfx=s[  3D͂$&Ɂ'j}y6FT[JG|%?X <À`c<&[x(>y6?3d>{-`eؽoG>?߁kX2O/P歔yeE/-eߑy16|Ia$}T,|oRIӈ.vLzU^+@˹+>/s+@t# =xh+EY1:̊XV~r4 QQz)QXx$j_d}Q6D[DY.EH4LdKV%No.yQ ԁJS"u[ǒPj)$V'!ZHA؃"ï0,ha +auVWzXYaeJGkY?k]+v>vaaao[H<ځ8ޞ|M~ _bś<^>g_xËG_=^f| aƌ03Ʈ4ʬ4ά4> f61֒6T b}}a,ⓘ"?1DO]XD%adDŽP(̀¤0Z (G)|4h Mc"ژbJʧx;,Pt!OC4~_% ~p5(Enh#6;ڈxh#"󡍨ɇ C;> );Mx= !x! !»@.#[;4O| Q]|_=R-3Z9#N. wd&ѓ tG>2ȀG"UO4SχR5QPONl*JeS'?jIg|u[ͧт-h C#C^R zH@0Qh( QA!PBKßGRRFR+^itrʷJ/"ռC狊>郂xE<d&jQ?S駤Oi '@JtiD$($*3Jy ʅfSy>knx ;yǬdP,DZK¤$Tp%R%"J5.N`.qt,"̈́̔ ԬiԼ1q~Z8CJ \nxK3iftY~>`G xHKH&2 tERqɈ$ I$m#6>&mRL8:2eidBq2%F5B!;2#?$#C}#G{%K{8I$WߊM|#=2KRF0_-"&RRDʤM\ qDJ-KJ/v$f&Pܤ BHuâ&(%Oc#jOa*O㪕j!j$q㉋Y|gA>;^D2XePFKy0i 3@(\JPsKNH1 C)0`jXJTzx*y!Q#jbbDֻ%8,QT->*8/( |/A~-~R$A U(p4 `)G(3yT%B *(sÏ`EB<&z)o-&#qA'G_>E@+%ZҢ+5Zң+0)R{Hc2 EK0dOÑ U|BR4GτOG<-}0s3FVߎWdOlt4 흆`TnGg>QadND;D/L CNP<>x0x$d\jXpXvc!?yhIy>,<mƉ1q#đ DXT )c!E 8@)dWÇ4FO\KWG9B9 -bpK#+[߸|]Cv| A"A `F@NnQCm5=%|tXƒ*6䪹V*Ě俵&ϩE Xf[NOg[ '[%e7~ ϒာGOc@zŒ26XcFxf!Sr1:j:)z)6eG1+e9پuf'-:0 7ctklyKHv ."ӵO[UQ[5JՂ,2)/C0s 22'4{xRs'6:9)pI mɂ2 IT{_aڋK.yy\Gi$9 ksf*GnVr Ghe &a?&زȂ0h@ (= j'ځv"jؼDm!&!(p>'00&=x$``'ހX4m_f$wbFDl_Up2 ~+OYK/$[#y^wj؏q{0zX~7}X~^ /cx!x~?Sxɀ?* A,'`Ⱦ˅עW?J/D:|kӸy&mF\lca@w(OSY yBD^BPZB\"yA5x 0~OMT~3?MK紥O[_}QZN>@tJZ!aRiţƢJUHFUI C >~0y6lZ6x!* #~?*gM[yEi? jA BR@tp(TQ(O,0(6и: J*iIZIYhhٓwz};ua%S zx $~œ# 77i5VSi7v3Qg"UT^8p.Jlr $S)ճ2I&fdhb(((= W9 h4̅M %*ƋxQ%I/ӴNk Q{(5¥#T`.\FNYD"iCf'&Q%#'ia穏g*u_"4qѦׄ9s{6y+?I 9M+Y鴒O+I R$HRRI II%FꉤI(O'(;8T.V"}Z]^AbfBفNMa(d%>~kFy-y=Qi51j3Jp̤1&UG)!Q5ARXDm ʸi㇢JGB$~ !QEEE8 JMiNpg {*lHbˆ>GftG!+_+\\#+_`L</»-H{"iR( %X()`B)] (v2Ѡ ;F't}1#yEx\XGe;-'Յy~ρhd͠q5(w(8GfxUaG D5 +"D"H8$?qJ|@`=yb-b͛2f+9 ί״Ah׀&dȂ5#CkԐiQS"DI&@:ԞPBMO5*=ԬavmRu*Oqij;{S(࠴8_|_?ۃv,IВ) Z2&@vȞ#0bW|eA"b^x^}9!Al5UCfLhA8 He&LtVgl7[\W|+K \\x6re`k̵V3&ZiDXydrcmZ+k,F:O&د2L+L lG ۉlGZ2Ƨ|>Cg ?$㧄gܙ Wҟ}h v]hJڭvjګ;x`8pGX%;+x=}g YG^[IYR[t.a pO{w'Gރx;{{;ܻЮc1YN; C=;(Kދx=wwwwhJڹ5`>}"=H;5=^gdKGPg_7SwPT^%| D)y"G?__)(?@ 6p4~͏HL"? qa<֜Io%z^/p=Gd c|(d|q>M_gy{{-e\>79^t]2N-dl<$ :Dmԧh',L["u(@hW:eGU0 Yd6 f=@cĪR0tdP4haO=r"Jh#2%BM!: "{@h !g_>3ZPVKx41N)VS-3[QL2b8}2IJEҩ3UsR" A~>uTk8PHLū :~OmWsWs>!jAQ˄PG BЄ:rM }*UC UF5NI;@KA0M1$A% fځMU5LT*6Bu5g@Q^tkݬMJ:sKdNʮ*\E8/ntjb9SJI3TiR4h*  Vj =L5PC ?`9PKL Q K\)SphLٻ!eGԒ=P@x<ԂR>!IP@;bdIc B)WA,ֶ.{ oB#H6^ip8xZOwz<ɱ@.uDa*Rdi]%vKT5 (DxT:PiQrc;^ 莂$Dx}m,-+´pFpKx{4 R7)Q9f"pYPm%(5 91>rex\ l77gr GNtLGkKa՞ UBaդPX*F a!@ Q4/DZ =I#d$mP2A5PKcBmO6=j| p\3|k|-am|uvmbۋRb]v ю11B#3X0an|Y&@ϐ0zv3#,a s! +WM1 bX6خ J+mZۙ#mG:ۙvkMrZ.7?ƧʆW„;?΋E|;wηy;ַLe&;BS(:F[;(}Q`P6uF@ϟᕜx!64y9n+-Ux4jB_r04Dm`Gept$$G# z>oi,/%x!3~Ǐ2d]!D 2;$`DMF%O+>uP9YRw1 S#-0Wmͅ'a _|(d ;z|$?gX,+\]+xS% W#o2hYAekK^t ׵ {M`lĮZ3vԘ}-` gy|*x!.~a[L9Z-^Fūh|  (N AcXZ& Ga|TÜ|IRY&iپen+/t'yۧ c*ED6'/6)kZ'eLL(gHLAC2C 4 ;Vā(L*t?Tz]YF/*OMhJGQ&Y} '~nja(yV^C mTmjI-7(9F.@9r ʑ'G\cq.IQ'"$BI)A)Y)q|K LI$BRE_f~wkQl38ZFMm$@LUƲ,dGTrhbCWULTD;|Rmz5 i1D "VH|+AJf"W&\LG LEʯ[Rʰ%IJ% (4R:Yl)f؋ͯ\2bZuJUnPur\dDQ?s T\B8+>x?U|))xDSlA364slC1B4S KhْMb1DCL`0jeZTi' !SKiPTG +3GCU)`7 x8_^`ia9=ŖӓLNO8=Ͼp~5 %AlFg) O.PC9\~ JĒbAwh~)@J5ʟbyR019/^{9sTl1GF`娙K2(dB03T[bӃ* %8=Q@U`jqDi >aD*M('\>syv2Rf[ aKad &3T-2a$0ZT"aYBkEST..7c0=v֠`cԇO(+$W>W-;<Iyծ}൐} Vc188B# ;M|ŀLu"EW&cPVҀҴ$B&M">tIgOYM.lٺy3| wE\8kI|" wKah"M"0,*,(QdCE[+i`ha1)$LB(0R\s 媖iL.'Y2lt>tX+(#kihm` 7: 0>:(B@"W3TL"%bp &MP:Q"^RThf16G))Jp_Igo7VG{B*WB{H)jɞ {PpIG E~XR z1 #8XgPծg \P LD{ӷ HrkdjPcRvNF"ǎT+Q]@Et;/M6,T R3Fyj<QЃguk_[U7H{Q.JV M7+Qv%j߰@훖';o]a2Ҙ3KV(1yS4 Ҏ77h1b'@ш6%4*O=ta`G箽jog)Fs;ĶLi4mU#ôjfV jk(tjI3KZEQ BrE Cm j}X󃺆'[=mp{+z!ncs{VE+Io;Sc ibE'10AM F 8AT0b"&'XA=4!O 1~H*&B =dvx V:mrۛv*m"[ݒh&(- `hu\r~*?bB%~hDZv|dhГBZۓAkyDBOSeC gQ<_jh.JhG{=$^ ֊ǴV7Y5,k`[kZ]whuʁ^)ګBk5#.B{"W'B{E" B{!B{=!WB{ګ%^ֺGaqLkMZ+ZŰ*VaeTf?}xlaZSMIGC?K{$ M/ွkpj, [ Qhϸ/oa?kyڟV-hZ_rNO=x᧩|0ފgb;_dZCyp[/f{"JK-KFc\]@'p{7򿈾߄uC0.#ugYԡ`A5IV tM\Q0uUgypuX'u\_ {~ ◡|/oe"7g+Y wH]'#uԵUX]?ĩ`j GkE8[Y5]^U % r`>ggLs *ɉ/R}O6`h~8@^?R "0z@ٵ3iV@Ԯ+5".|4@n꾦{2 ߤ?~<%"ӆ sZ1bM;fliː!pƌi(XIsv$ Q 4jE!д ĉG\":?4x:4zQf728BW@_J3F`˨$T)iWK%jQ5E- 7("Z.<TBܒ;-yF[rR#ze-.3mGW|(~I/xC~ƓOW#p]kiD.+'rZZeiBWF;N68}ؤd0IQ"š(?xQ$yܤXZŤh^%(|x"*Gk,W>7E lq-;q>Dga!xa[Y8D놐B: *%rHID#SI G,h%AK$/!I9>d_Ð&5: g3̖~f ;r, @7V0C UG#*LQ=2}tR!:dԇ-LHxHsC>FOqA^SNó=a* v,hAڒi;Z$p8&T]2.4glVMZ14xH7d;JlCG:\OH,< .10ǴѨ"vb[nP[l=J<0!yF`*::WVlQ:"!U }2@0sF5Fhx#OH[UJ0$ۖC[  ( 'ۓ~xd32!>LG`$ai" (!V kG k 3Ő_QLy\~̗pq4SmO16fۦ^^j gAe\TK(oBR7jz zat,T0Q4At "Jq%A,PiSq O*"ɛ#01$s~qڎz-igۖv^S 3 igR7"n7LGX@M%SM]0ꁋs NL5zXD"Ƒ@o%x\L YDH;@{sbn`[U\*ܛKoZ.=\Xr戵&Rh("L',Ux<-A <5"dʄ'M4d)5?sGxOr_܅l)?-% !ؖR[#. IBInN&Q\fYd$.iXXże#G(G#I Ja;b:陃HV^s{- `qE-8`C# 46`D;!䏮 BD/Tif<)K/V0 fnu|&0'p?۱elA.v.@[itiIx#Lb*;Β((dƢ0EF8Z}TUلs2ee%EL-D`|8X:yG<gK6Q"8i ` C`G 5Bxh"֮EZܛwoO۟6kh㢵u^L֍B TsS Ք:v <Ÿc K"@Bգs/ΐdBz\M5pܗ$nSj\-\}[col վ E筀 p1p8]yME,`gJ- Ӏ90} tYGh+ZZjŷ;Nvk[ۮ jHmR@Ԧ=j0F8KY24u$탤 j P RPa) Anya}nnluk [kVZ:*j-YN;fӊaдbrVlҎi@1(d0btԈաE*bw(43Ȉ[b[blJ-˳IL[¹Z֊ua֚.@i>HZ+ %v)*\w$Z#H eڢ([KkpA*v8ZX+۫Z-+m[hk*[ 5^1Z;E{uhLګ\V;d`Cgv QZ&V+dj~X- ra?$Gj}>+ldn-^Z6V ek"[+ZǺhvhVh6 j. 6h y ,O i<폐>2px Qc98g RWOwv~vu%tY|mV'l/ȶl.v@k2_LgP\$ ʹ9vfAY@i<zҁ~Rpa ,Pj0ƣA035\XKOm= =0GaHt|#ʗ.ƴ XPZ(QueE5"8#*AP*k@PB֣t:kՀP ]@~mg6LgQV/d0)Z5fWj٠Mqu6(2A1yͅ&o8C}Sw^?7$@5]{AOH(<$X`8'tEi3 pRzka,, {ֽtm]^w.-*ZS'KYS`)ˠ; tqP%΃)x DyɔM;G+$?vf0õfu pS|k[օsӵrtm\\&׾UopLx9WupWaDF UFP#e#NTF-*&ڭ% -O^Zao֚%Vu{k$Z86mMj<:a )F3z㊶#GtrCZ61w)>KzoN9sZKi*9F[m_6jijrjjFg$^ uCzh܎vr.qA L_^hZ~nQӹI65[^mVWh:JlXij_gggpVh{6geO=szњ43H[:MOҘޡ 5QդL1*|! 34-m5YMg^#=decOfdjOv 4|Xzɢր 5ePG)G)ʪN- G2 ɶ#:kPbc']f6O[X{ nav/b/b:W ]́Xz}Xz5XBב6,l:06*쬯fRkퟮz.{ o-\>{xrmpiMkk \]z \aOrUߖv[h_JmK0ضr)HXp a=cVs}W{!{i~s,o  l+X% ;T([ٕ^vyWm]]xm2+ƪ;n%*R}W,*ߚ-R* *˔CЎ1GƔpۄRqEME[TgPv[{ *o'>r J *DrTDަub[hZ[KSEܞMdC&[noɔRBRPI#!>tKQ?T_UO%f|iE{䦓$wHrsAR6.w zwR{BߊG w$nGftTE31nT'=R;QC~BN:)  e#C}B{ N)TbA%~8k?89VOQ<'X^9i>+/$䑲z_.9kN~ssvhZ>Kghxj|>o017/83ke};稃藏8鉛>~wrk a=5q$&Y2~2;\MDF>ox9pvĉx]bM,7^1.VǸmcuAc"L62$x1va)Neѭ9-r\^˅2ߝlwMT+\t[@ M83sy^.rsI>;#Ow[I:\K’ZTJ~  "#qE q1XxOϼ{K·|gx94^LbOpS6SD1[],R&~o GOVh?)~o_WC╔x&>4'^ė-?UF>?:q>1'>Q ~0?P-pt%O@\(峨Ǭ|7|ʿ|(W#ʞ&B}c+:="f }=~m>_*~ŷ`0>Ɨ߳?z]#l>54UcMC#5=x4O>=!b=}I>yxA|!f 3)F" 5phCC +4hB c"URGK := a=_]::[˧+9gkW4?QG?[i{'.꫚gוŽ\=ǥ>nSz򐦒5~Y嗁>نcbB,ab &baw5viXښak?]v Y[ n/6ʻNj+-~AX拍\iۅv%:IԾv 3t]Ȯn8.ն&ʻkK+-F(+_(NL{%XnoVvڦ 0Yn/p,Q`ɭE'VݔV`*=wwەgVYU%TJTQ!~U_-~oI>w~sejP=f&Su[ޫSzJ})w߅RE ~58jPAxLN5=~}|ȯY,_Yi{>I 6Ӵ&{0݂ pL2 T8K*%nJ%=9PH#Q<)2i7H0R;ZP:7ywHq;CZ Q8 EHCO>;`~أy`{Lc6#`0a1%x ;S9iz;w1l 88.︂eg_ 0!'^M g~9ssYX%pE# :"3O8c_׻Իѻ$|IQMIގ·Q!3珳eV|'z!\g=C'HB$|g_=^<d Icf2+y"FoTxbG}ŗ@0P< 3j'˼__#[dI#}.߳ _ʓ#>x>GXUoAG%ɷip'/ɛxvP gAy('䋔< u(zJ/b;p}ٗ}{2Wwx&<^O=>vR?C6ާ?U&~^{Xz R5/'su,ޓ/X<֊RK)‰'2?%J>W=ӥzP {̵{Idʗ}5nV )<çyx0_Gq?DGßC?zL)Yoi%1~QO__ѿ㌾GB0JCob>\雕_>+ a ZHRO_h0%W_̿s47|E9?98q^왊&Ο8&,+^rސ ~/E>/dH^2y$'&}>r2ɡ&,:mh GgM-5$FIE6/b}b`A_M4zaHtF[0H;2&-Ȩ6[lk OC-F(u"<>ڞO@x^wPi~0-I)\F! -|Z0jB #zj1«+\E`H[ v=`=}=^5>]I|Ro_%A^>'AU=w_o ~\y uVR}V ۈol=S{< :į:ίC}:?z*xWɻ<]D6uטz^7}-gἼV{ l.vCAvlOw^^_>_Nǭx8 \+q}nk Sq}]lԺ7~#l>nKlNm9ڎs;)mw7C7 /x"{!} o68 7[(N3mI8{c ]x[[rɍn6y~MB<یgS'}A[fvv6UmP).v [ykr˭ͶF7Z^jw_픦74, ?-<-ïx5;[{Mm4hkCAc,. nQ4-fTJ/&eN_wH!}z gut:#4R"JWx"_ #A8<ʹYrF(CE{}nI#C'dznA6 R hV>f E%+&㟞g܃ KxBqŰ!g:ꙧ.opllo}bUt?4x$%޼/d x1p;El9!/'.~oalcmsLa?Ib"Li/Yw͋XY<5bo5/Ҽg*Gc>(_]~.?ˏ⤈AP_C_=u;5g|+ʟU U[M(剜OɯO}X_@zOKߐ ߔK1,T!b{yE"G׆א N@> ϳm2zѫ@zOsj_JZ/ wCd<:)gh3K['\PiQ<^Ÿ~ك='g>&gzDof/!4J|YK|_+f%[$D|PzFPOz^i)'/S~X AwQT_/G5/xMÏi-էR&UH>̓zK/Wd_t&OZ!+DW`gM ?3bCsV@t0-h% G"Qy]O)jy8H.,ˢ`A]26Y M=h IaIM 5TMs3M2t:*L0汼I {G_t{D?{C7t|7$}oXudǥOT:dc$^" IEH6M*$ԊRIN]'Iu[ddW{EV*'Ȭ zưW<{D7{B||'^{H"NS,Fmf1&SXĢըWk.bz\)v赘[`S%Z)Ql#ı{Ͼp>BoZWPz^BV8M!P QuBU ,:‡a]GHaff]am l{@ ߃=#='|`oyk@ {plP)VC;/m?Vc[- Sw={+|<ǣ|C}<wydfol*{d3ưۍ{n]xO9=.˛++>nn..4|uR9.C;CʝBֆa6;IδNnC궫n4:-EwIr~>!>\Mύ\cs?^Ƶ\ȹ}EDޖ!O攳;2nĸ΋;5zK'r3gk6ɿpowp  y8./[7&Y.]B4.M ny{M߃mNfl~vxZFi_M,mDMHwCna^M{A֢5mmBoմAL1mB C; 32kq$}(6 Lï5ʼc}KMoRw2qlU198Xc1'ָlaEear*ʓ0|17Kzs/eTK*+TP,"yIDmչ(PK>a§Js2 3Կ6\a$)'?uXE !IH0$9$^tz'R-RD0" qxsGf3 ND⣡d|L兰|?*0:{M8sVY+=g|{1s-1>VȄ o"}d,6T>:=)q~qcq<~ CqW7H=ɜ/&Cdţ,ZN_1+ #|f&WH2<)"X>]ˢs.[fŜxG3l`>ڂ}௳Gy={OX20 y9.3ό4xͫt=9` q>܁㼶-V{#L 6ma7Y\I>/^q\Εg\8=plzz?i`;W0[e ~<s;^]Qa뼹)?w; UDCD; GA{/0`0nZ~3"r:|9eט<%w-8DSِY;z$d/w} Ƈ $px1|>!o ||72@eJ&W|GCއ1០|Aly)[ǖ5ᖯg[~+O]%+T凨|?3E>`zU?]=CsP}y;G$%I*TJ^.ɗ%-$Oe䙌Eח0{pjƧLr| AwEϧ/xp|ӍGE6?a} O%>s {U?}=}(~S<)Oe`'X|)d,ŷx1V<'҇pUa3}ڋҽs&o>TÏ#\><o 7g:>@bh%D0CoxԪ7ѰMθK·||̷$}K)%-zK6t8BA zImd ZD1d02ч(i IeH,XKC24VRϰD oy^Kx9;^O9MzMhPBPQC }14P@Jh_VWHX yn<ʶ{Hw,FoF(䥤z+ʩrj0 3j*SȤzE2:23kCL0 k˯.peGcT$C}#/b'F~'~bOIƭQ}z!!2$"ɭ_HvAkQI+%^;0rlH"N<; ,ȴNz\{^p  ?0 Ma7bC E`D "",}#Y-L1lc`,LF.\\omŶ.SVq7%mH7=!  f \}/XWp̺eV:e ˬ[ ͬF Ѭ4 aaۜnGhaxsҝVXwYwG $7P_)u}8Yoͺk׬YYYm^;50)^ [0=@fܛoV |o_o @/F<ߌ;z\g]7YI{BfgUg7gEWgwwhޖ|z7]wv|_l//o3/*F?oxXκvtֽJGg WgewgՁgUg73~Op+\0;ijcœ=sWqOuoVݎǃsd'gݹ<<+tyV1tz&t|Vt}VtVt IGs !7mr9\Io]|w|8җS9O~|Cc]??7?oV2o'oVoVoVo!8/x߅ smʭ._Z|HBmPÙG<릩Zwj5Z̚5ڡZ5ƚ#ϰ8j?I~F9`ZyiˡayXf;nDL,ã>[o[n8Bge-ZӘ5Z9Z=>,ek(CdKeH|c׉}&zY\H3+u BىOj>~ɯy/BX V*/v S|i/;>K. vUzi[ҰU*|8ˆ*_(>œx3"V|W/VV櫥Y<6%,;AS 1~x L>˃HS@jLu({@I$6ãNtpeSDD@&-8nIhJ-Jrܓx+b=/^<2Cck=7ƴ8ҪyLCV`'&+-塱<7H|#ƻ?x Lc187.rs?03,d "_ndȑoO[gW,Ge g63lLg SMɻ e9Ge*;Crc.!a>}̢3[f|&8mZs4߹{y,&Le4#Y?f7XCL0 ,:sKpLos0 ;}gw̻w`.`8#X &rxi\.b ^}ΨgOŠ{_{B zx780oxb`*o;8c'y%|A?aww ޏ=}CHs kO6#2Yx)Ibþ>ݾ>՝O"D3H?X-o_4C3 t=8x<oi2 ~ɂ7  xӈ/I|}_E G }x; | I?#WM+@̀#&B'Z>i~4_\? SIz(Rτ덜}5?F u<b~"Oy b1y0屼<WFZ>(_X+OE> >޳@|_KX'W<O߄P^ʷA'䣚?ZCJGɏ/BZ/C[} '14=~}<}yA4S ׀|R?~ !8ːzY"K}?壒~ba| 1F1f<Ii< i3l 3C9iD3A[07",޴G#c@Q_jcK"-t&~Vs''~΁(.E1% Qh8G8 W(~:4҉L0^٣^xR O|O\cU^ IE(l@<$ƒ<0١Nvx:DmA/S_qHڐ`jmJ}y 5eNY;:J=B FTj"S8éբ<:4?ӭLn3үJn*=;~31+-)SW&Բ.|fTk$[zצ`k)6{d}y'[iOuԹk%WGF>C>$d?Qu')ì[ij҂L V0aV%bdVeT̳;1ӖT;m@̶0~0`pWrʺ{(^!1+O17x"xźhuDuϮw ϮD]H]H]uHYSHY7HYC%߶QRnBɹ"鞋-n2H z;o!ǿgOlOD hm'r]D"]7mWۮqۮeۮNݮ<߮,묙v?2}Șw|-7"W3%^ho_o UG|_;H(w¸ qW x@9qHwuqhwpg-=ԳnY%N<[0pC_3$\á?\*/C'Touh#GЎ'A< z<0!V@+'xx n.HQ>_ymx(|;w]>ddzƏ_?~~T?B+~98Z__|[QwPy|#ߧ^y< 䩢'C$FՄU^UToњhs\b- $~Bq{c3-ǯ ' @ jLبU Xʗ54RG^>0W1>b.rxor[(NR*V-y2%v|ꛕJN`mLOa">*,FĪV&:1gU+THC*NNJnQ51T䩌|֒<؛תDFԈ.%P1)TdŠ# %9o>O&2Lwg$gFxxt\g Xd!7.\${XfMɗG)#^忱\72c:IgB!<оُlg$9d/7O.rwσG4x(9*x 3E?\=Tމ| @ @V Otx4+*^qW ^~cT+{"w>/_4ͣAJм 4cy3_w̼Nb)/˯Gq&>/@~ۀ|ba*?Bo_> +Oj%hU_Q)4勤J7!z%H\߁7^߷POx2;F#O'92|$߉ChG_y'#dD>HzWg W& _ŋqށotXy~bGϦ;|3⎠v chDw jGL;>qO2* _Q'. _C櫚W@zW ,(\#a4GAa0#OJCXx/iO{EM.>r1 K@>5O'KSߴğx#!ʺ "jD@ bJ@6JL=ե%,qb Dn$.Ft4k#`_A} 1<~懎~(]||gj]5e],+fVMV:V(VN]s‘aݷl em0l\ЦҐCmxA9=9C|g,R®jẢ;j:5M+Ұkұ2jUjY6c)ۃm땺-Wza5ҷHjt# u=wgf>cjxy3<$L4D3<Ѡ󌇍4 j<.<ٮ>ݮoV8+܇t}}yO<Իo%N6?/R);)=<%N^tJt O =B.zb`9ŬvbY'1󬋘{bk] u9eF?i#>2K G)_(gxqqg!|< @r$:w=$$xR%x@%@~M~Ap3IĻ"dGog,Fxk_gx61g|vF~0asѯGC# h5 hhEtD:*x\ci1Bn("A ^q/x%l_a}@ mAB1'`BQCīdu1:8_"t!R.h9!Mv>a ~`g{VPgDR`P PŸ>+}<*2t:^+ !VBJ_w_ ̼lpΡA7ߧ? 'Apyxgyg}!P@^)ciPz Xy}"fZ7<s?н0/&{FU]e^56{ŽO+|{퍾W=ԭRO[ۑxu&&cW ;͞v<:yH>vJ; VG;RveحNdtgyI.Cq%>N׽x<sk\0y6.L 4}qHS.qӸgܩ3.52Nժa:7s-Fk--ƿ9ogݘ#}^ [hO6ў4l=UHyXCaz ᚺus̓rÍk!6\ZNb0SƗ6<ݖCk zYk2 W,eW$e;<\a(a]%a5%+a$;1&#T81B،,KK]?bFɳUy71(-:X̃u>X7 aW +^)4VXStDS^+Ɯ\3䢱㖍 pܧ㹬aOUI.zYQʓ}y5?wyNn֭A'pc0V1b5!V4QecU8&AcT;>q蔏5~,)Ũ! F nNLkrR֚2,^jfO4ާCpL<&c*K?>dJBDIFtd II.MR'1 e#)UyhE]SP|E _Z Qh!#J%'b!'r )*Xy˝>Zc y¬SkǬnv`! ~H}B#xh2, y9rb0X3e7sl_S4 ֠'i{Ylaf\f(im08z3#n}cp\vgMg+T!m% r`0px ,d1X[<'ylA9xי;)Yw#h%=Ox;؂g-_| 瘃g,`0EX#Lz;3'N3ك `*0Iuq 163>߽wO;5Yw(-aM8OXw3Z`LgSGQbspn,Ʉ'*yHh/Wf^Lo!)Dt<u:;!iFxK:/(¿aߔaB< ~5͓w+%򕀾ׂ_  Ƃ7bM+xI o xjOB :/Ws}3C?&D}bW~J7y#:N/[q<x|wt~n͓4?22?{)-JepwI|ψ\.?˗JGƸѤ Ue $h|J) T=dpy)&G0w l{L쑙Ee&Ik Ƈ $gnh"&)]TR1K:aC*JL eKL/t!wemPdr^|1ktFI%ixbF *bhҥ 0UJ07:6(I  2f2s [6QˊU٫&tR8х*1Q6IDH%)2J& +H11*ò@^Gj>҉ 0MNT"b'e0pTHj =N|1Pft%'|Q4`M] |AA$]cL}  EL-0df9J-T8p!K&a8 )-C"5.p3$,)긁E  ?.Cۖ5:sB`? ވIfF1)|1UP^pUK(^T$@5Bdp %ebD/ C@WxcJ`zGhJVXX4@`PLDՐhbz1|Z,1`ƒH(%5:B6 +hqQ8wlPF x_ R@CC`=$aچTj$IuBLB R^pAc i"HDHf LG fDzI2#E<ȇ72hB4$ J/ IQ2[t|Qs"H)q9$cBV}HiDvX6HvHbBCVТH5'0# /(R0avfVA ZyX " !6Hj b=`yC|NXPE˒.fv!D H/p^;h`V+B5Y ;da\6I`H c8l>)1d J\Ye 5;F`3j5;xQ)9EUS|æ(́tfey*,Y(h(@g@(UN["t%F s,$Y`՝ Ž( ̝ OB=b̦f(ddC,]b;EcAc:HE(IФI)Wnsd 3@4a lD(lLcZ۲TtQY,!02z0`#oL yD+a6Db#]%@5s6+ӌPh愸B1k/V=Y6"[4$ /l4CƎj bMޜRE "Yl@J3DN×8xyYN㘍3lUej@;"8d01E(oJܨ-]8 EC4B2HV?d)0gelU]ejEc?'+ d|8 Ohb1q=8Y-懪\Z`Ch 23Pel_cLmAhC> 9 ( 1 W̨h `af mzlHb(h͢A#lܡ VuK E6' ?Ka-8eb򡮦vyn(喾! !f-VG&1c媤Ay; l2v0sq 6tI ?7vS\r"-oux-;V8cM:&8Ag'>9;| Q "VY‡N. =qaoX;Jԛc(fOPO!* qJ3^U %&$r~[%L1Npp*+10%hIY#e! *8,`zBCH>!Di 8V`la!^#ž7Kr 8PĖ+ۣe{|hRdAUh1 )TK l6`E |M]r(CTe}c0PH3ЃM ްZ@ Y #a A.`N Jn[֨l1Y*FQ %cF58>py@`}`M! R0ATٕveʔ֒ jU,VNR0.㓍@ QH98"b A. LYRc:DQ:T+%jY!e %bzMFWL`:p)sWejFMJj<4`FbPRj%ƕQ-9`2'9: 6 ̑\ , &@ hP@8 ` ntE +VLI%I!":pؘ 'HaC*jgfedc#"a!`_ߞ^ڙYؘX0/.-, (P822**"" ^֭)'pN uH!p̑qXPH8 (PܴaFM4g̔!3FL0_tEK,WT2EJ(O4aDI$G!2DH ?|CG7lԠ1CF /\`BE 'L 1BDF78x( " gP1#&G&"^ļ1Gy;lw:|ȐxPߧC>ɑO= _XV>)aipx}Akz ?) Oe㌯a9^? "NޭϜ>-9__[!i /7~-ѲL^oXCΗ|1з}/ßن ˜~żL^—SVF*#5s=~#A>,pǓzlǎMփy}؋wރ{|ߡ->?,Iuo:z.߂zz/2Ih~H܏{ι??{{%?{ _\CuXI_+L=kmVo[%O*^t^hy*^?k=_W1}[Q} XdޯX&aBM>r^~)} pUX=UDOeSH/RN?TUoV_uVq_b_@l Wʮz:Gz/zO#' S3UW)QF[>)}}bz)zR)|g\_~ -`=VKY y"& 4%Ӵթ)KuG=;O_z*Rc=L=+RԻMoM=;I}#)~r:h &( ϒtpiK>-MJC')mT4QDHG!!O::! H QHIi$HMISdJZJeIxTG/uPs}|R+64"ZϨ FFUD"f*JHi2b##U=#q}R'}#a=C $AE m)t~JLH/!bIi%!.zZj"{MQUȞS#7b|"wOL.SxW3$ɣt"|=L4tXZ=P[D 5SH=TPMSuPP>CV[담. >73L#7 5O3Q-5SMuUWꩯvJcMᬋY ] A\/CaOdDkO$D{?dACN>5vMTwjϭ~+7l-\[u^7u` 6`maÞG}>GW}zz:z[cɻ6뽿-Lv_U KF-4>iW{UD{raY}ؘxـmNm^kn ?pS:qf5EX3am{6.m[>nZoNz~Asst#0Pvn<?;p pOq:q?a*&\;?qSLgşr;Ncm,8]ɞ7'CeŧLr[ml3{fi>ZNog1}? f,'y7ra3|# ܁`ExV`3{bpOف,2`cl"^Yo],=.>%QyH9O{mK0R~&(~?A!f#敌yg~sy?2{0rdzHOzB [`(K/F)e埰DǠGDzAAhf^|_x?x/<7\a3)e˹mB?@ ?\U%>=]YoC ncW6Fg|H}5P4DkVBvA/wM? h 'b_a(H񭆁|_]Mێ>MϢ“}ٟ=$/'s ߫ (~W3'KzP> ʞ޹/{&vK2Bދ=C}}[~Wϗ [H.z];l@Gq^+~ .P}O_2z Du]IZQe[Y]c]t쩜=ixi+~))} =X"Bz곴^~>z;`[;>{ؓ=W''z!/}j=W@zr* _ [)Z_G!z,\߃It}h?v_u=Zu}%GںnP?XE\}Bz*j)*z+*z|T֏b$b}֯a}kaYS*|t%4U@UGL!#5ikGEO]=S}US=K>+t2A>՛V}eV}u7˨|*h0U4@QH@)韠jꡬ> 른^*^ ,_xz-O/Y@YuD( hz:h'rRڧ&MP$NUT៴JIUusYS.~4'ЍLD2]dtdKHs-%m4JJL)5==JQ_~ꙮ)뛺;S;}35~K .HAA:E2ZB$󐒾_HMcQpZLF:AA'*R/q3=3=/ ~R#EFPdtEF)23(iP##&4j EPE V$uVTET?UDtQ2º(둴듾>)^^i J_KQ,i@M{r4Ƞ j4 ÀQ?Y3lĵQPCC^ym5#?{ eHe~Qw,^5PՒ֫ XYjcsuX{Me a36} uwA6cdq}ʻNk-lv{k.[j>;鳏F{jAl/t(?xn` v-O;';6nNl^[kn; 3}a69sS>x@>ϓ@{?;gw!5vkCvn}avn{nκ~o>Yo@=np+91Lwu+~wx睷z}wc8Ղ;=x҃MxЄ_7\!?}xo)M7o8>߄mx܇ىx׍cԐ7 9Ґϒ%1p.#>d_8qgL&d Kys*S~edz73gV{b ,'DG 8rJx_bǕ18b1!Vs*\C?07`58WN%,4;H~}t6f`+G0+qVAwy_N뼸 ?awwO8;[sf7R/4 *X/_4=<3?#͗D7s L?(JS"J}-r@ɋaA(_)HY^ 3V@1<_iMXb|3~IgqCsᎏ`>^#x!oćSp>]8ʇSE+j&~?x &~H|9N1X6=OXJ'w[OGs|<_ ^s I_L7[9M˟\yK:7 A>u|M#|/ZgjuStGO$?m/ޮ۬~ʞmM|;@?1qOYJdD4=x=ѻ%ӧQ֛Y3Oc4O{GUGsL7;W{O'{ {k/г}Zҟ5}ԏUx12/ex_V@=QuX|u~ꞩH{ܮjCfeI?aQoׇڋɽX[P|W[Hۛp{ ny/܅Gs/PnؾhxAjFdG/ 꽨 뽸 +߲S}r*)ўО ^ξȬg2ƈ=X{1=oMoi=Ww}w}Ws %X ,/->U1QMKUu~> @_G_~k鵞>K걬> 볶^~K^uGoM_ųWi^?[ :KhF*h-ZJ꯮BWz, eYmUXUo'[a>IȲ!g,.k趬ɲ: h(,>RZ׫**z+z*ȗ}|&g8U4IER$7@J )iHM"=u#ԁHU!Y}gHXQZUv::k#(X+3}S7;7='qxcA-(FR0:՚Fn4ZHkߊF+:k.",E$6HCecd#}+/R/'Q~j6?Sa@Y:֚@];y=vޏ~V/|̦9m%~ bS{O$OZ|$?2{M} -z؜8مsvf\]apo# 5As?DEvoGtGoE^Ż{u.βB>hjKm~m&[n֚n0D5 >Pq vn2~pcXn^nv;ooo7Zp^Uy{>Gïw߭滛7.ާyߐ}r6g.^#S/H3^t>?VX.(_`< _q6jԓpѩBȚwLwy=;]Ţ>:Kt3xk a=6%U#Yl_ w K[0?<xĩcbWx 0[t6F;aXk O䥅l}>".9N1kXǧscȅ8 ?rIޛu?m)]e kk.CfwfZ'8,ǃ?{"sd22^6AO`r~|wn0uڄN;#Kާy?f``-S0c9XKCXt:oN–0wV||{ {-BӰM|"&|Ҝؘ {?`a}FI޳l 4C`n^͓SGOĆ']N ȶEpo(bVB+way,od,ңgzΤǮWDCOx+:O>>ǟy $|g1J? ">}`{kW{{/aݐgq{~-RJ2[AOSyp̞;K|/qOYJtD3Wy@C{{9|߄ħ@,y`OGO@c^ַ}ٻ߿?~"&_t ?);3z9cz7_j]6jhv~ ߻?/0y_DWS=އ}K=S#ٽO =_@z6Wz4727#3G{?+|M{ ➌#{s:s8{s6es5Vs4JrwgmGiLeU?c^?es_d/ l/Ͱܾ>-}V6|Fjmh hHodO/Fa[C{ /fao:_cr79{hC> wh>MtVgD?tfAfH_ɖ^-L{iWك=X܃{I>vN9bF쟈}$z&r,ñƈ3mh0ĐCI,i$1˜_QU\Y\`5]g^t]UYoW'tm|4/Q| Utы_G}!Mb^Ksy1mSI]OuE 5[UUᵶ^^~~ ?+R_,7@*ZE2JQK@-vz, j¢ j XVUB_m}` >+>KVߪ i(Yb +WrA~j UWkUU k'.g*W |_" r ;I>~Jھ*Fi$2EMB4Aͥ)j#MShCբMdi>阼n[l`Kag-M4vRHJg)ݣLjPnRu/S/QtL'^ /#$Y^F5 $C m=Ե%׊HDi7(-Fl#Ԇ:Z[m#(\ؚtޞ)  ߉T_ku-qͥk#E_(0Gƶ,*{"҆#Z{m20NBq# ]4TQ=+R3 >3QR'yx$/ 4?d;C6=?h}6Oۃ>y{z㙛 Ww/]>{o!7G|$O$G2#o㽭lS0_nn#~3 oֻ[kiSBOFodFoV|P:Lfbb3w{Kw}ހۂ=؅mxևOxӉ#xьC~`~>Ј%~~췔1+|C'lq%QStG[.47 'eʼnx?yK8Wwc.wmo~6b{5VtIch}6UI.f+w\-_8ěk=CGÂww.7mgl^}S?La =O7Y@}}t:St 85w'E76(v[g/RX]cq> c{Id)V1Y\;2~;yx d5+䧕4WQ?,e؃mK9Aޱ}\b oXȤ#[ȗx 2Rֻtcm. \uFkY?N:|%Kpd%KyUv1Ee w/{f,Ok{ mZ9 N:'/7A{$\ìcV!o^5;9Sn_0,8 rpv{zgyGyͻ070wK`g9X0M'aKxs;Wnxoe\SF@V漕9cίI9'Ӝ'[K?/03TBdQm<6dͯp;.P$8k2=?#ڽkG8oRbŏ^֋zI,(+  [}7R=b {wP? OEoh rqx,IoN' ??󷴾d;N=_}_tz`OG??+wyš?<>;@'"*$t%'?p&(j$? '?OHoy=E9$#U3?/_,* }~EχyKotWob=%o5w>cW(_{ ~_3_V=Cz8m`fl[5>[{{ܽ9^Dދ}d]޳Xo}Uu<~6:+1ԻU=[֫y}اZ۫ͽޫ~䣉qqA S侇ܟ{ӹ~8ޜps? &'4QrD/}$gP׷w{zOh\ofeg,.eyx:m֭Y 7S݀tэ?܌^s;Z̍[nHW%ԖtRElL=}ԣ]֓=ؓ=ۓ >Ym~{F Z -!iCH+z5hϴ!ӐLK/M hK14RgO/E}t&՗]=Y֋u}X؇}ڋ]x1| CzŎ3f4cF{3 3Ȑ@t(ҒdL97ticRM%FuXNbW'U(, [_-=Q z1_KJÀ%L`LsԔ`Q#MUܗl_&+ ^ZGymQ@\`[du]ku+k.LZQw`i1[N/zV#&ݪrj-Mk3*MXa#a=EWHr{߲{."- _Ake^Umq&s55q\]]¦WYg^mxյxW`YY8*l1Ⱦ*l%*Z)>?+!5כRv Pj(f: /֞zb# ==K=UWU_ _USyx)o6y-k4M^?izGS4DvgّLg2ǤvF[r;lm%~R)n"%Kw߄Py/SOeTOS5pHeoٛ4vIcA; R=^$A؞Dr;m@$(Ga#Ɏ:o)>2~j<)P5Z1(ZMPQ7 J$7%݊Du ݭ7PnB{ &7WWA? DxP>EdIKϔL~+x"#ȝgn(RG"G6 wr@{8Ϝ ?p;qi7>^CuRK4F[4h>P>}x"/䁮.†X6ȇ8q+~:7>CNx~O7rWhc.\onuO{4E3ϓK1{v#IƔr _.:k9w>}wrc.=ӵFԙV kpiʟ8tO/s-GW] :m]bM5Nq,'ay]#zk8.5?zG1%Ď8qb[q4.^j;c 8h!Qڀ>Ż_ɼb'/~qEsjΚPi;=ރyoerOYVv2w`S/g\͘Yr770 Mhcl/F;V3dޱXO' Ιcsn|E:_؄޵a}I#a`(ycv1&a\A7aw:wwh Ia1xggֽ Ey&D'-Fȟy#T) ! 灃ƋňU4C~s-^QmvZ/ Wep3^l_xX[c13 @@ 0  "AEB#l CF29c3H#`ק)OS+NXiX™DE4IDE4Iz=.D JT(]C]k"%Wٮ. vC%:bpBI9!`}YkX~˛.jJo2vՁuO/D{7REcBW88 <\$k!D ߻Ob *V`q be(n4%nSPyև7a\nZnŁəS(cT?.?34LEl1[Fx!̰y&Z `aC/@ÁIE#8Ї 27v~OaT}"8W9>P9` `jWZ S娯(a@[>Rx g ?|eTaaLVm81y'Dr~l?|Iu!p"$@LW6Ďnj(CѰ]VU3`V{!@|0L/gRW!T6Ay fU0[@@SHċ-SSH@bG// :@">T);n>A0 /v̗lz"}ݚZ`sģ3j3 AY!^턏.LB!%c /-?D9Ca& '(L5_̇M^2P9Dز41<ͿvwJ0nbXhqۇ!{r8f[0 :AubrLKbD"nBG-^ 0ghQgu[Qf.ЌѸ ڢ~l0GDP`MK `9@<Hc.j8Ar7WciYY"j1,'!_N ^ lՃ 3MO C0XrԾޟ|NfJ .PVy53jI;d?/.loViBC&u ?]u pC#FS%%?ԃa?J9d[|G9hr=dk$`n@#1Zj@B(Ww<Ѣ8S+>^t0bAxR?<݀/=w(4D¶ǽJ:PN0BBu:h !u< <qGо ،פS/2{8 p!?AHL @w} ޜ_{_x58hf| k!Ph@5{5|VO_M}ߌ?o_¾NM | <1X^rAr|3AՄPA R0p(مpw }~ F/w=2}~aWߥҷ91`#ʬf2\_J$4!zzdgQe }A!A `14 ht 0m3+!Qaùs1qܝ]~W~J|O~yZp4U14Q{)@6tn^ n (9 l{15Hjo?hDw : }Wkt5Ϡ#u|9@=%=:=oIq/3FOnɱ+c^!_%Z|!B?*B.M7fw.YB{~}_Co=E??O uΑvYۥ8XtyYO 2y@&I68A$Y Gmq@f:o"E'<3??MԅmѬ# W-IWv92'vR;G8;N|F9]p#ߜ wq-7?_נ-=SmJ=C$9-lln!wul>n!wpxqh6FK"搓ٹEof5ĶFlthh>wq9*1t%8ct,Ѓ3]3tL,.бYLL\H1 geV=7olr')q6NS00 n].3Jp*]  r%@_/iaws&~H(# WŢ͢•6Yp{xA"kkMq-a=l; RuN-,n08xV͢tg,9VUEVsIU"ZYWD+RP)P)YAY,J̽@,na;xV%"h?O?9'''N(+jeAPPPV@Md],e~=nogf9 4߬?)7))9r~  Z;9 845ħ;xdQbPmeږi[tؤ]m}j`RRns6gi@ڦmTpBFI( 6jBP` K>EQۧ$j$dmr- ڶInk 6n}xmNWt5Kii $B} >=b҄X1glmӒe"L;BUBHkk4ٶDot>lrC6:`rms<5+myόQ@;$Vhsv"-*R:-Tfک+Z]7,rY˪b֢5h~^@)|>̉2c&)$Cc/إ䓹P>pz'oxvNjmz|mX֒] l֤hY+>_%KQJY);Udd,*kETb>Pn 1nWPz-ފ-Yچ)[uڹYo-goӠ~_/Yf(-v-%K;ÅBW sǎ׬q,Q޶ -mގZk*Z6r 7@`,$djhs9Ry$sQ|* jpg.6u=Yкt5erE9+O*9M{ &qh!G 1*w8j@Fg*Gm@ ilf<"@n8b}:u_Wj HmdtgKRk(`=8 bD#]"=#׶Ľ&..Nn 8"hnk[EVBP0 &CGk(l+@(%xPaRA Ԙsy*P2C|͂WZlۻ` -t|S قgc˞*S.o] 4, b1LB1Ȉ0gT)-@`ȸRٱ4A!(_ddX/$x{LI'{84$oP=!No<[}Bw NDF)mT.pbD]Mm`bCgC!'Y)XɲN0q,*D't6H!tCD"f%NHd!!-$07]gL9%lMD!>)(%ņ*0ް0*"dg˒PY˖wG u~u؃8oq,ѡXxŽDE t6AH%!9t09̽O uBFE)x$5lP ='64EtVIO,RPZ쓅=Ocwv41݉0ocD #`DC #1wȐT10 MR}9Fi(!7#UB"4cBB'?^TX3X sPaR>CG؃FZT|2poZX6r:)9HI,*$#|Q ND-@ O_QBl:dOH*#J֖L{:%CR(`nJ݇Dj$|pN7)8=H͜\crR L%&6d*ЄSQ{.V.cpq 1/F8ij;E(lkՓ4[xDF6h ؉LDkEzܽ=U0TQNA'(R)٤42%J]8H"uT L6$a8S1L0<S܆pLD|8؋NjsI$J(%LtQRD"bTCFh1zB U,MOaabj4FG>xaOǕ'aȊ  XpSV؍JZV['VĉAFP$AԄ%$xJJ<i3E#2D]!#%vugH*0CTlpQƂYt@p5*G+S=)ZFj֥[P| B*bhFTYEs1ɦF1kx0lC(>A@Ka.ILE E-?a0̇<PaV(X\‰"X+RZn9a-' 81 lp$Y,g82G"06z+zO&rQ&QPTL;lZ5jZNך֤ɽ:nb[~ͥ &q8%rG#IE& RHH L%}'8~NE&p HHl4(OmrZUC:Us" 5s[n+-e@&(Q!b(RqH@O4J8Cgʪ0?>eC0N :=\+ ɸN$Lr{:<͹"Zmk<[6OH3 &A ak|`Y+zP·]BvKrWK5p1\nS"&)8 2c/2\OkD;: ^kwWzn; p]g nU[-'yeDo7۫|Ko5mf21nZ; P;]ŷ9Zu{ Kg;AkRZnP[-V+jn[avŊbWI*R[)yto< oB x[.+^Saٰrav0bm X"#V[jR1)"t+n3İۨxKz4ȷ6o}ou:mN8[j**n:۪ ඪ2T]vJJ9[ F6C1O)I^[$üAm|SƷ<Vǫnu58O-Yݢ5 ]$Uqܥm'CxvjGmvkGltMc9X,օ)BcdVRr!n#2F+VTc֌oфv-hm~vkv/gj Mh5ZV|FO_Mh_*د ,X, ɆabQ4%{*]3=k\5&epnJuFqG~A vЧ*\mj3Gs P<.')jL̴XL+ %p8PAm ֮TUp*M39)28pUFϩMRA7PpB (ZP8N[x0AJ_ 6kd6FF)l@ - GqZ@e"@/K?,EA8ĀcHg h }AG+ !xȃk![0y(XșрFF &xKC[ L cܩ@lT2 ('(b@q+> ,1Uy!͂ѡ Nha1C++[ V:N5s}#9,k۱NkF $pDpgU$H% X  !pd3Nr(aQ8 b7 a`ՀCɗ<__<fo4 "^9K2ne0|~DC" oּ8*+ C^J`?HyG wy_,5 . M SpxFAyE )~:.RJ"!i?+zX2 |%r!-rÞTren\8 OXT' y yp Y݇DCD69B(A4G- DrdrǑHHS<&XBI$/gdҸE*&~B΋&0HHj83f2>Q Ha!k-\r0*HBJ~$jaǡf=z4V T&(M#|IL52YfD);b(K*1d&f ,ā0QtFx IE73$!Gi Nj0vjع䦏4l\R y#tqBT=ᆘY` \xQ`pBĖ/9W^U` H\c/jV $֊d'Tt3r -.a + ߄`4!tURPsSKgN&06|θz?K)o3œIVex q KmE""#A #pбuJ^Tu^0*t LN$tFg3:`CK>&gx y\ 5xbĒBR(~U,U5 #FH#hzRA~vecs^5*uL#dW  A87QXB0ylǦJT*`&b҈ M%/8(Rh*_h)Lc UlDC#lnᒱ:V,6'6Չr /,}RE2%,y &H%M"P|r L+ZMx~%cIBH\@ 0 IF!2VbuT7G^< W ˷Fv $(D2qlF(kTzaB)$G-@ 0`L;؁"fYh @.xCTbT3E9Pq7o#8SUpxBHH2% bH,c(a,ˆ&cDeu#8%X@ "Hᩞ(D;=C#^A6wT GcbH6n<ݨhD! JPr9Dn6}.RyP:W59S`m+Xb@;7!}8&ȭ DnOWhPc9=TssO"~[ߒ ca WppXKչq H rCp P!lFym61Ts F[X|o?[-~5o=0ۮ!Cp9+;zQ$HvKƸ@ce;W aPx*|`'- y~Q^쭇3{ vY[.'}eo9[,[*R"p qp{> p9U}B{axXn=pJ mx۵r [aV fE[H Z)ap.wpppBnOX|Nͱ4bN,: u.TPbj\ A;U@|{s~6˧m|C~Z;ZBjr6w֢og- %;4drl:J]#`ԹZFU, #۰[M*۶ڻyZk2ZL/FWIel(%)zx_N, uÚD,&%[\ {djIˆoFr;gamȖk{8lF^VZ#fp x3:^ˑReCv KҲdWOȚYسFbצHMCe8 ;evNt|Jo+xDbĵ#a09!F2m f#ړE+ 8q; 9˔S|5<8p `P;<խooe^xAX"_dfe:妁)*>p0gE1X4 2+8MLQjdEQR-LCU@y*c0 ;s8Q9D$OhٲBIMK@a]"骲AV6W!{ץ{0bO[‰QMPfbt'ΜqʉQT$'HzJJC5Utuj0.Ia؀#ǐHKP9 T  (+ ]+:YIPȡ({eJ%ѽBI.%%2=(q ) NOO`jj)%P%B 60KWwDI d(u? ӕDM۔̤d;[ETȱ 2ڄbM"b~@i HSP3"-#TQX-!i2Daji "V(rpHY]/iBtKr' 5 ( I <]B\ /ꄺWνBE @ D]'S(2L !"B0M$Mm:%2Ɩc }g rK,PJC"n|:؝ 66G<؜ͽFA.$ 1*|s(5Nq*fG *:Jt DF(N#` Dubh,,E@ Q^~A"3,ycw8XU9Q%lސa3G:>5Q:3AJ\!Ȉ!IBup2s*$,@#ȩzok6aU]~)2׍ɂ:T)6'jts0\5qMI%K,pr`YdHHTBeh'G,|iC3˥ O/cKI ib 1  v76Ìj;U'Vh x6H&ET Pъ1XD@je٥s$d,gd\љF5A$<1 c!φ Qq4$XåbwNWGHIn5|D $PqA# |Y$ɅLgF4H\$%/.@LTԂ:OS(SCȢ͆)`{Jj-ĊB' (4{O ߧ9WfQ!#j8 wh 7Zk D&xUA+ 49 ,l^DcG%S)Ha@-A pEP.#TZrᄴ# _9jdK&Ng d+kJ ʎcG.4bE18h:@)L a'N S8A4a c P"IMD ¹iED%[T.~qG8G_H:Щv  B5',nLIzAZT tGX4! a\ CDq C<("'tP"I L() '0dCϐ60NhɰЗ dj)*X^PsG-Q A t!fG );$[QG&RQj*TTHfLl :Fh% *Āb 4x@' ת&{dN2צƦ dh`&24.aP ;h|ac 8J8ݑ:bBhP^_b'pWu)CF怢f& 79WI6GC>k^8w+BQ!`1h PIZtU&j%!GJ A%([dB%npzZ@Rڗ @` Ó7:lΔ́j9N".H%1T U%i%)L]Q5zcE.Z@M!t?,5yaI'c`%%5-X 28;Ս͉Z9P#괹TTTLj .&b8AMu!Ą(ZB*S>>E rɉIc*D. oJXtX(pbQٓ6@_HG T %Ai S JUS8P+bx!-\)3Ps abPRJ0?gDqԑE:D$j8 PmQB8 䄠EgXX tO p/Z(0" 12^L>FwQ)(cf/<K$rY lIgHR >\:v d8:>ti4L@jcuO/xpqœG%?V C$PQ (* +#VYs;We#8Zq '_B1X$SJA,&`zfR-'I 4Xb0csF eGzZf* fp<X+bB!eU9J;y-ZOkϚ~*~͍c~e_n /6,JuŢJ%\ hNdlLrJr[zFdGAY3:NzEV67Sd+)T6D ȞqilmWf%pUGUsS[p2^9-."$QnaNM-s ȱ5M^0em˺pbJ6.,ʅX0KuJlׁs*Oq7.&NcZ2ז i qw8rN)dN tp^m޽%Y6 vc\l_Z`%$C2n5V4lQĖPaXF7Bq1Jcu >R L%?0' Yd21t͡qLGg^D Bגݹ:dN^FW-DXC?@`qCj 5RHbE&BIH&'P^!ve$%4-% IӅdwz`ڀ*Li”+JpYQɊCVБ 0^* 'F2C)L!R X0僥4+n@8ٟEgÎݑj:HiĦpĉ @1(v ŏ/'PrBG:vxac 1V[X5 RGF?w<12@E(j<%8,lԤ)[@l3:R{uP+dp#MUbJ1E#%djF :bP"1ޠ©d3Mx",\Q!i FdH6 p;S+vgj8X } fT34zXChAHD0NaB SHGɩC de4I#b^栘Ķ$Q >Y4JuwDM@ J#.a<C NB "HIb0m‚)(NB@^0@бHqL4D tMPbCHeCVkdXݩ'=Slj|`t1'_8ҩ&4~FqcD[PCĊ31@`d |0槤uITj+If@v,hX+c9Ok:&6`@ 2Ps(7dRDʌ!Z^bƈ1'VMѐ@A@( 9iPHɃE!"9!ˮH nAV)D%`cZlXuVpytK$X2i@eLZ1@e\*̈b)&2-~lQqS#MQLS Calw \#d3 g9(lm,ݹܜ'q &B IEEY^$E#9+j\4,`e(zp>@-J1DQl4eyfPjX q: cTg\mnfiE+-UtDGtOWJ6Qِ8 l5HD E8CO(L [$5\<#KCsft*蒍دKXR{%)!6<,7د'{:NbN rǚ$q͘7][ y˖4pHl;gdyOHk ֑j1svĕhS_@ǚYV2ŢUnUUkS&n&MCa8ܹ<'[R[¤U,BGjI8z|Jjd۾}c&(,Qh EOTIjeX|UPջ?ly+%Tll !i6b+S6֦'k]W]] @ q?U qA< t0i"L |~R.PZ耪!4'w09 Lu it h:惫8(${m *6Z kPG1;)a$aaoH#d0W<))k`<"#P$p{X`6 X/2cB dsbu) 4QdJ F0Q +:R\aQ^N  IEEo/" C %`6<du|T. 5D&y`DH%F(,rD$J*XCEOFgbEgUYhJza8fa!+u~c p P6Dc`x<ΔbL(>42&e+A,$yG"4Y%2TJTu"#5SʎQJ,E0DA<$oxJ ʌPTE(ۓUdwn쎆{e:9Ƨ18:`ѩdI$@rĐ=P}G0DH$i%X("x<5lC6GfW5Y-!SUcwNPFdK U"d)4LqCU/raC!>Tt9BIH*\'HF=4hё CBP!%dyf4XjVO{Y=52f/-N@aabIHvXQDF@VШ=¤43MI(a#2p}!BD١HgX7֤!@j"dRS|I+II1`aH;t!dF)$+B}H1,*r /L]u\Jz™% 3?\"`46kM|9Y*N6Y#=6٠Zb]$)dFDv D%FhJ) 'Y⣇(5HuT& 1|h$oܑuR Gc;Vᐺ@!e* &Ȕ1?lS9D !T0$nA/2 kDV3WsCiX!/'XnShI ,(ʎ$99H4?X7p2(*pVP" >UY|@Y*cn&yc}b۱Hd5vȫh.X?\AKeY|ˈESm e". \ Çjy12ORe,5ٽ#'dt|{n*vZ&mb}$Sɰpޖڦp2&@e5`f'"FQp D Xa0G?kbRWq6XHΝ\7Ldx{+֖F9Z&<NrdAf,#\4d/gل `e6>D 1a [ؘ$G$TAN^#spPqB `@[,:> slaL ֤&kE±X(+}R "p EedBT"5Kx`y9"MB-I(-l,!rzmt3aH N02( ^+ԕ6٫ * @` `PESx &KN9*j'$0)7@ BBtP$ q#hRЗT!P 'cdl4i{h ǒ{Ǎ4:^QJ *_*ⳁ7 -ח5ty`i>8W!pȲ:TIV' ^`,$!32GI4҃ #e(CLftRMSdx.*&~daL4E !l)cEdu2뒋%Rc0UX G&r1qtg<f8PkTl y?h,OUiZFƤPe%j,X`q%ɊU~ʐb '.bH!%t,1HF2_. Y7,ZE"KC^?ÌJQKpA%˩+VP̸*ARsHɡ‡,$YCTQrȘL&k6#qu]D(B֜b{IdGvȫpFQL(Pu 妑 %PD?'Ud3 f?؍Z!g!f4$  *Ph~yRL%g Rx0HMqR Pz) uH*Y.;%^c- .cubsRl4P~p9ȘJ,LHvi * afHOz@ %`#*^hYIP' ZCHd,ݑdBآ36+5Wv @EI0G@)!V. DG<D9HH*,V;4!cG8Rs@֌pr Y)tc>ؠ(< &P)G4>LL9\2PI1pBB)xDHej63Tır<֬rY)8tfc\Bݜ'"G33 p `_(!xq2P.'PUi "@ p@aR .yظI[vcxT\eBFb+Ub{N7Į` 4. T ŗ N`UM29=A8w ,NP{.rƒ%X.;5Wc`"ERh8)I LPr@be#`~d3!#`m#i .P H<}d3N&5k4C`ۃUbsL,mېb6Va.U]uUYU.]Q)i> tLd䵘8 E3Aox|M 9dغ2S]<UbHwKt(yG͕w{m:?TK4D#gkhֹdq?nԒO+-@*.m!Li =gʼ! cpތ%{y+&ڝb6}.d\NI.fvt{նSg{ '9Jm @Y&XAG mu`X " 7',"hx$! +Xi%ߦQ/6Ǖi2[!J\4 XZ Ch@cCO$r`qs"/OC$3NFMDN9L\a 3V!Ȇ4hH<` dx,Uq0ESg QUHX$L+1 D53?(!)9 Dh!ww9pp+˒hx8D@rl"{]ұ:!d k m  e8j,H@SUPTJI/-Hف DzIb7B3:lxMR + .0(\=Y >c! bSpA YD (n0%/0fupfAHj P04>,%#csll/_0(D)3,FY䄗%$`%3kVDGx&FqЂ.z{ `VVqLĶƃ0(yQE0([HZr%ˑ+Xx`2#%U-QNpeBh>4ĠQG H**p3{}$`fG Ge\}AY,!S%csh,,G0TYrɅL.?hn ("b JjQ,/prqR @HBEYlѩam IfXvh؞4c`ṅJ/Mx~b$ ##8bz%RJ4idDC@,HC.hpG)D%S@Z= 4*Xn2%0O|ė>NETPhBGP(EthZ¢KG3L"qO ֌࡮)-{!m+$c`Qb(!ȍ`vHeZV{ȨrH'E]dg+-,!)JE4 M0pܲR(kბ A% [8!ƉT1GdC /5j*;~<)䈑Fl3_\4c`a!qeeޮ,ә-@dplՋqTBEu'bH ^pȱ VT(G#BU慦4[l eeު,- A[6j/T1BTb#D/:VlᢈK!H-~K n^RӲUpSlY!sBʖ3X1J0ONS/=RRc+.^D#%v1b/:,@[@Lb8`wX .6%Y0 +Ec{Zl+WFu|"(E$9M&ZpJc˓01qLU &%x+qܓmBCFa>Vk\xE*XDt:KK0!! *YxjB#ŗ'-Z9JC=XaO>R"쇼)kdЌXꊱ=W,66:dJO#[p&"3J(#lAB S1Tni"CT0  @S N5B6e?E٫%FdvPsbɗ$Ql2b%3ʖ)XD ȧ*>DXbL%(\&8rf (TCg?|@Rb8$P@T @Iٶ#b'5yXbcdLd#c, 岀 R2d3F<$:F!XQD/Rh R&$zXaǶ% ~DNBؤK>X*6B CIv\KlLt`F` ᄀ T{Z2Lxb'XK˶c?Id0 lPc4:L*CL%6X:@c1dBɦ+xRC9H71UU"Q[`(:c.s,cc|,ۍM*{DNՁZխuN]{ׁuX\Ӛ\,@W{[H5vil; F%.NRxydr f2g;6Ӝqy XL`f,p (!a(,p-ceG-04jIa%*Y4r e ƴf<.c2!H< "i].[RZ7Ī,בʱ9O:*aDs eQ.ŔJhWJ|nIe &Ɛ3=lL$Z<%@hAsz` vVqiJH,uY+c@IIIN3AttL (`Rr)QE(O>()HJ4ST<4с%֌GSZ=d:Ya-DH("GF 30QD jXHD%J#\eEgB` `C7 raVxRG P ecspl'8HaG@㇉32Ỷ#5.!E#H:Hف˪5Dh@wjJ( 9/ VeƔPa@5dHr #NJhpQEaRRȐ)K$G2,( 8@j@ D, ,9.6eƒPUcsnl(-Ra"E$1VA6hyCGUAI2$OؼEeD!Ƞ11͠`'flZM;DvVՁ``Q i hpa& aV0D'TJ\Q"bT԰Adɐ7(&{_VkFA٤1#KA|F&(b $DVy0O*!D,W<SZh+nw`کv& {Hj/ڏ6oP/S\px28 Rx@X pf=('O{,'Κ>;N kC}uaWZڑ vC8%Xx\U6a'S8pqixO||O|p;;Ͼ%KOI{SCEm m g7 [0 p YQޕycwwww:ЮhpaдkpZἯp\q}3]ޫ;yG.ǻ)K=% ޳x*]hRک.}'?[3wاo7ZT~"|E;0 t=!^CY$Jh⼕8?do‡F~/W{`',  Ûc w,?l~q~VAM ZsЊkS34A4F Ι'#` kA+tO0'B}x6^FjI%rQz/J#&i+`..0Mi/N#~Kq^0|%`]4Ej ]V2sdQ/Q#/dH7V0ɠM4 9%<> EEH+(UVQQQ8\E([E-JaP:k)(_+A[I7 K> A|!ux0>_-Q~pY z6dD@$ -$DxnIA"^9EE®E@XY㊘V$E$ DF"@Oca ɏ2+hixm lxIK %8tZ)f;9t s`p逳!to 9_g"ƯGB®+r:VDA10-Ȍ(+"-ІL[u`t֣"nG 9x5{1}s\ I0yq/E8UƣLW)jF[BhOӐN8MSMI4%'jI=L!0ftj9!u@(| SxRtR漥P-"xSƇR@~j姆~j<đ5;ZQwoLpc"3#.AdbHt4&$_"@ARC9V%*!9b~5Oo]UR+&pwHivKo80s2IӮ)ӌμ |H#<4=-H~ ) &.ԉ:洉v9ΓD|#bċ>i'Egf3oKZK>cJrI<,X= kazDIxԉiXDHԨdكIyy=Դ~yz~)'Aߩ6^ȃ&yN)_xA 6R0i%/ld ODE E%HHBL%>',')!+'-+{,1w.9s0?a"ILԇ2S|DcZy0HeIhxa 4BuH0 c!C'L,/6:&:Ba:JZ|R䡛aq,}*S\D+i<@tlrd"q2IDb呦⥅E͡EΠBFQ;QGӫ kHե"[l)kdKrGr~Ёh #Ri !>>@ecBH(%O<)`Z.(L-<4,4r< :DH'OT'nQU2Nj1㏇;y4(׍`G+hKX$`eP],a:dȹ Tz)(Z)8J9HBH`>9j.a3JRb"oZ^}\ Ei~H뷩}M$mB=fɞgS=P|VF$8gZwJDgB00Pϟ A9m4c9*!aO_0\4x,hmh.uNӘMRHܕ.+ މ#Ҽ"%ںD֕8ZJ'Н2] wtFR9ȥ*SM1H t.,1Y7CҮPC;}y>r\)=)eYG/ϳHxZ8USQp*Rx[۪JZ^$1iM 7E.lX@񆃈ě!P8@p>~"( ;GVd*_m9iN>sx~o?۠]!j2Vm8i4@Hq/h:(*D3Ђ Hd"ШO$=kKxٜEkbs˲Ħ s`hlЖ2B[ch D*3fd!%cI-1ٓU2(>dRzFQ,Ci%gьU0]2`/PG!2x(myFغ£= Hς!Y0%? &?|EI5ŇW(^Wzxa᥅_^vR|})FGL&k$!+k`&CG0 }B9d.aVݲ³[Xxhi@ˎYbvdѱUFI!Yhrxd S#+M5ԬY\A^f5`mԂ*Uv[_lNa>$A_&gl՘"UfrV G4W BEJk)I UTRXYHQ`=!eT*k)Sl}Umz3kkڗ:EB~ĵ\ɀ>pݧqMX0]`” V&LX0u`SVLl*F2`erclB*Eiv0 kڰ#aGvY`ݣ q2 Ơ (a1E%|I+AXw`QhkjZIhFkEȌ ~57|X'%\#Kd>js^@e4 `h vh# ^/p} ۇ.E|>$a@u(z(4P4~I@RPZ/ %tLw@iv:i#*:\?f}BT"I9 4脁Y1PWDY/0 38hZҌՆe+u\ 09Øp#V/c=K6HY}7og\씫7Uv2a XwhWplV`l#+hTY{)u$4>2>R|OǻP'0F: pI<QA/J^(SB N%dR-k:-SJ94sRVL41`JE =}KJG& -@C.UR,KU+U/ħ8)d|țB-3Fд|OaP=BքQ] ՛2'C/ê( T M# .G &A.!: +3Q;弊Z9j?]}8Uț.TOyD vSi7v3CM6 ʲWd3pJ栆P9D;2=/H~B"-NZ jԉiCFJj"q:(@Ic|i/5Ogyp"bDBfH"tꤼ\Lr4B% I'NT3չzg:m?_^rOD`Ȧu|ZGH 8EBW_0BVaXA2Q s 3 2HQrd%Ϝe5,X6^ܱOTQ>3WGް3i-vi+VRM"UWa+ ZV AĜa53TItQIsq$# $E& SIM¸aͲq%*哿]^3"| K{ɴN{ jZQ@iEpJ !'BO)Z2a84jXyX% "EC,a=)a2"$aqOl9xK;ڴO+@Ъ ARU1upTj 0MR: `LIIѢዡAD@{6xw1zh4F&[`Q?~yG3HF|!<*_+]ڿR+zW*_{SXN|)VpB[e JF6 s3 h d)jy1W2 >۽ԤKzL'/5w)4p݊ NVܵ<Kz6@p e(1HHO^`7A'VS"Բ{XP"|_Gk"i\\غLq^<ŀ0r4x2"wÉ\s;VPB4Zӎ;#CxI Eёb5㲖ߘ8OyN}mo-Áp8-R n;&htGӕ6 LKڄ,i#r$nHTߔcR$ f\I9*?sWxفݙÝ1-pu0_Nx}'mׄ &hՆҌ(eDI ڒ.hM=A2hT\Y!JM2d%(t P&H61;nfr_Yѽynuv}(mY-s2eO Ȥ}0VcY,b"&c^1Fؘ deP!Q, :!9`C"-;0 7FpEd`Zl Ђu %h/1A| ~xI'W>:<&@lip6Fٛv im*ۉ٭3=@n٭zvKM ZhY vdMS$Y@FV:Y 'T -r\Py`=[n5?H v#~k*l:[ՀV=T踡  r T9Y>ȉ2BN)69BD1EBNrZLbR79TpU{5kކiUkk/xؕ%>aP,9u8'6J +7W&N*\8pZi2iN{ u^arSk5Zk {=Jkڡ Y$uF. E* VX%8ZhazhPhCh6iH{%E&^:VAKZ7LSW6TW`Ma(Z-ZCk-3-Ck2&Ck5BW D{AWD{]AD{%+%^֪wV61u+jXTWYʺ?볫|֚ZYk>k+>k$?k?kV?{=W?{ZZVVzhZkVrаYOωaþԾ~~Ͼ^y~>;d _ 3Ebf}}XֺꜴ=-B;4} EcL=[?TH<87W?V>4ur3 =sX As P(z NH: _?`z g#@k^'\./QCnBAF@6Dh 6&:`1pQmׯ1{ ߇I|-?E/ICh< ,]&+ `jMV&8YsHeMAԠ`X B ^~{j@VXԀ'u^KK%(~m$9CճPASV:kXPQs$ 4rLd]7l]tY `>`%Oh@\fl3g[ĽׂU( +1C^|ȿBX6P-\B!p *KЕ.t4ω.|BTvYR#%ez֥m.ocuC\j{+/Ex>?7<'b˒ICR&8 K2JX'bIF@(3 # eBQ zs/]7)wW3)zVRwR}WFH|ϣ2 +OJihfNq=bt3,CB<rtcH!lHqK$g4YHȝР=#}mxBGqG<vNîM- *Xa%"kK $-tjOé= /0a2A TBqh8cqH#\ I49a76n|tCA7J1cH^Uʗh~2 kBojMU<Le]wYJNmqb *(DbLq$1pEdQ J>l.:u^Z`8*D0REwY yMބ5 xG 4,iԒZ-H BڷńOi $PR&j,Ey Ԉ:yЄ M"TO"vT^=0ԪF+Z ǫ"QT˃yK^ jHW ZU`]`>|n5̚!E( )O,GT>=b2!L ,,3$wZڃfD VT'q ȟh%/)O hXNF\M,6;HyA&b(\A4jl:rPyeIIyS*I1TiPt߈!:R>k9a%\X7x4=%|$oI<\ IW{ ұT:vR52r%%UK˫L*QLN QM ;M>Mمh36:ÚEc'?"-M~ʋvN3oy3^a)-vj'+9 IHIf ';L*i#2C I+T:+GDUZpr,y PSۧV.QG Yk o3,BnV7]0bv@Ø fFέ`4b:"HB$S$%EJI-,aӼsS>L edrC ?xŰ1jARPѳC.,\IEz rHJbR(0h6>M>21BH*QGy#ΒOf&6\i.[5<&<)A^oDE$1Wp I>("k !-&Y@O7(Y0fņ){0ɰGjFULRp##B%B}O|1nn5Qqs!xD ^P}ih8 գahLW;Wrj w>p"OyDJqK$i"@+>v]t X= 0C.2C 9`׻=(n/]Ѵu>Lm d9"S]HA;\ =D՘kԂ(L$\tQP!_=?3:nia \(Ll4xC;ΣDnqGɅC´oI- ڠDieӒ*NBڮ4p4m[%p_Fuʕ! H.M9syX(rq_ 3F}T Ej[kSvm ҪQIZ*6 hZ*@4%.ha3I9 Hh"T5D ɶr@(_EЖ/ |]+ !_Ɨ?-@9g=amxP!sW׵~P[+=ھ'vg"٭ ~v. aK <@+/Z"TV >F2' =1J <\Sٵ:VksZ6V.Rilo۟֊k9Yxa'vD"ǂ*7<TSO*8m?Ku~~9z ~ |4.of}$+ 6gkڠvh k5i& l/ԋ3 <<fπh [)L^YcM`{Zjd3Zb+Eӭk4*giqZ0G!isd`#0kL|+ߤѱ"]pte麀 HW-&$]Qhd=-ɺYBՑ' Q㍐7!X8E p݃A+ohdxD&ȰdQ :\AG R:T0 U΋ rL/nZhA-^\Uk ~E9 4~̷BNTCp2j8Aq* O~pz|`AȮzY 8Wj[Rm w[>߂TFI?2K/^I+i|! i1`H`ƀ!-vc1cJǘ q9 8'72tW32wVdwҘ{nX.ox <^ǑAS@!{(dP+ 2( ERŤ*gN.1VaY J\Q:+wA}W|a a!naA< DX G }&o;|'KаDԤQjc$J$iV`Y5AVI $oL$MR焩o R^?#@&CyL $9q~=IO2qJL<%ʟ T8:8%0<޶CCuW'zQA_$ $ iVRMkϟXHI3~GqeHy\׌jamXxY˄v^Ġ# ;@duɐ:o)@+R614Xt2x/ǤL7=f=D~ᇉ~x.^1e4yWބ-+3ذVokU sV.=(o0ưZv0 10& ,4 0G[0{dUPHaC$QL :VңF)ef \dIB3G9yͰ90?Y)eG({A W$Fߑ ,XcBE"ЉO(PU "v[(5 "!Ir"MqRw F- 7D Z="W+bsgd;$[dhYc!wPrIGj)Խ,Fqy4 8)Q̐_9r#n1fT31Nd CyQ:]kvN2lbUœ, ` w劋+(rZq1E 0`P+ULS(09!04]:i\ X7~p7 ܉=m6 aiڸ,O %j߸d8͋mah+#5͌+mdh&"nizVR@B$9&(B/rPv*pܓ"(n_Z[2IVFi,UCBOldg;rhBm^lkb[kFZh>.+bk6]m6BA.pµBU6 ڠ \kdt-1ZN<vNlulJeVNXך|lx ΏX1Xk`!?=Z'Tf-uXWbXZֺkUóS+0= LmORcZ.RSEj}H "=9'ҩɬڝū۰:qZSƵW/jujmmZ6Q˃A,dK(D9^(g]ulUu{tܤ^mnmjTsW zݣBc0wb>?X7M;+CRpΒ7P;:`QB(HX@x5 5 k [4 xV%TF$'/>A\`-6Ï|Rp#. <0n3= @\4 i`f ƀAGs{ |-%>JOyS耴 x8rÀ# 2:Q8 ˆP!@Pe!|Hl`M'8!uO1pB5Fx~/+C( 1-P04dOB(s@&]f-)$8C4tAh|i/gL@G 8󳘾 =|,zjU37Xή6rv)ZЮhQm* [69Ir[R䲇4^ 9/=i^s@ZPs/KQ}&3qTaV.qzL:ФÔ&n]Cqh"ڤn 'xVO*ah"TE#)AmT(yȪd^|eECVHgx F^GSѼG]? ȉQ 2'F$HAT2IDHaգ GD^KbYIW)'&6rM\dh6< 1_[{bolm`/x!3+bcƍ>`%pzՁcqC$*`:L"bH)OW#,V:c#7>HqPy]>E?_.`ض$K-hѧf_g9Z pa"6eDnQΨj9ex3%;Em=P`dNAr@9,Ḅy ^rM(e*u- A+(א0LӁ"E*# UHQ_+.,Ylf=B|1"C` Ӧ<(l吡ӵ#xYhkX^>ř/E91xSH=}epJ0}`F@i[RŶ$ks,㬳 .NZ^"l!SNpDv 64(خ,PS¨G#5_x!v" I>)whaZ"Q(4-9Luf!!}` l3Γuta]-c7-6@d氅ږiMc Aa#bƥѰpjPUW઀XiJTN(2fodAԋ8R<`"AN"L "{tǷ=l"psJ'2Invi`Z큧] *ڨUa4 0(3iDmIJZOJ< YZ L4,hs FpK(A}u;fzaoWzkpVbp[&J{ ҆`i:`rcSf"0 fnd͒.1$ J9BIqG)ֈZTۡGt[:I[t{0שjx[ZȶтhF@ZNs$ HcIK"Iۣ%b{DDlzQ [Z@!P4[ óS[69[m{jkZBuCغ9hP^o, x)bExآ}dbu6^l֪ArڭJakF! vȁ`khPCKvZ![+kJh4zm ̉NP.f5Kl\6pXݰA*lؠ6l6( +k`BMVȆ anlؠxfvX:I9b al\ ]kdد0+nܭupּݱ­m vD֭-jZih5LT,֯ }y⯱1eɩ?9H(GnP ]-誸EW5mn/XӶ:O֯`Z)+Z-vZC ΂xB|n?;X~E`x$0dx~셝Sbt8Irr WCNNԭmhu\Va?, nZ6mm ^o qKRG +HMRAdLACd+XágO ];*xKL'.7cױ7fW(YPh; NP|,ߌSf| /Ize-B2@.#0"p5aUԃ .B"*ؐgbB )P#YOi;q}v `Ϊ<}(1YfV*1b&UҲ&5hU9֨MXæt5rOk K*$`?!} @`@ʀȕ uΌ/( y QǗ_ku-+tY5yiK[_޸X|:S! ^f=)0Qb"a$z#SV2L̈>df0'>ˋ!NǖqQ'rJ(qd D+3@[Y)peHȍB'B=S2rbXhE1G=,IsZ2Ҷq!Zu1Qկ[\Da?| 1q%GDB)rB:6Աi)Rȝ#zg.8bB$&0f1.*lb&P#6)? ɪb&+2r#J>Ȕa&xv`gG[; ءe^}oA,3Ws1>MTAMM=lEe86OȒ\%Cg1t2 `̣sJ\0Cn~8[Oӵ,Ca.zh.G\%4:GC**j' e3,GHBN$aSBˎa Cb(I`%ӹD wl7SVEfx{sinӼ4yZ&M1DQRuΆ*Kl(2eجQڙC,󳇇HQ!YGK8i;Qš.<_-X5 `"hYJbD%Ig@i쀍ADTZ:˽qGk]ۮ=la ma;$ě@'kAaKO ?*kU-Â&p.d2(jhÈD4Yk 2| IMz:ITjŹ(̵.IHqIku[µW:l N3I ^[ƘA+Sv>pGLiTeP(Az@7Iϙ囝`W kmj\Z EX'Z>2]Wk΄C3]1!+w5[^_=ࡆ^\-(pQ@ 4hzhFi0;G ru JոV(ĵ2IojX-W{W;@kr3ǀWrj@n3rt"wAW9 i%:0/p"\YI\ UFLs#c /D֩4zg)3ooMpw7C^!oRr{Rۧuj8ڶ2l 7llB`#A57R@- Zd¶%mq-}MpT"n<(n-ڭ]-lm^Z g4BӦh%@- P)5 PŁZl@'/hdmmӑeL8v*X[$lkdDp'>ouCf:6^W-UB–Yڲ  eT2q@LR]CKYvIJ^XRMaH*B-L!Ƶ k[ :_q׬fu  H+iAIZMZMZAlڈb4A*Je6&U$'btDl~ k[ e;bևNmyV'ms:mΖ^̊l푛Ek,Z$)=GW%[ `kl^|X &–Tl$l}B,ꌩmΗtl\bd9=֚c9#:9ۓ?W-njkt-ۤ뇽`M֬ZmRY՚h;~>N{}o1}-.,O%Ixzl-A]Eڑ\%e[ʉ 9k;cVװpZWJ`֧qҚMj,ܛP})}.-> )hu O@Q hFX ?)6Ï\ƽ) <~s\;pA'mrDז]:m֦hY@#p|4M{Y)9>7{eD^QXFgˈn("lED]C%`A+c?yqz񨭰C[Xpgp ?W`(}0o/)D^MqFjS&x91)J&!m˜h v!E cJ$%ipvIBZQlD# 7B]y;a"h0h BIl`Ŭx,7ɐ7}| ȨR* ȪȠKfUJ0A4)e@-*' /pxݑ{. Z`g .}uܷ1@H ^ ? G_~{@~Bh"F!jE*$h-""\B'+ԤPeǵʐ(*%W%Yf9 6F8+F!`5 ROþ6Vo8ڝ搔m'PF _CPCcI!Ol$)Rqe#`W4>/ p3- ǴHBQpHƿ. Z3ntF'\g 5$ s%P *O7#?ʒb&YYf"f | ݃&7nUX ˻]QFUrЂ2SR!NHaBUS O;,Dȁ ц ,r sа)Dp!S/ iq9/ 7|C'8ݞ; 4bjIxDSο{'FܙΉp\6٢# i$j#4 Qx:b2Bf"ka=6x?Džq["nK)܀PXFp&A*7) x%NЍ0ƉEZ 2U^/|lQCݍ&Hj<|b0BBh̐` U"ehqߖܒ[R{) TP hOK@GEc1 Rt->KP4q 2W!Y/d!% =X~$bPH Iun:W;\cרɽ6))pk|+ Dc=@[z mk`-^ԐEo*]lTd3%Xa$ Jy+Xp\`:X{W(ýb+!~xؾokM^Bp'vHs< ȪqaE@~"p, ADӢ*1nAnd9nT^5UJoVpa(ڼ {X u|ԅf@ _dJŮM)wn5$M6؃c/Tb &U ,"; ZS1B+͡k^7V[}a@96[*ȍE\- Nj@.,4s+dy b9۫#uknnܭ `"oBo (/pׇpw:Ά͹+^7ƕ@pm΄}ssU78WUokm-SKVjHf!d-*6R")[~շ:[{nsam1lۛ ۖmSAjVZmL`[)V`j0öm*mIFq[ndmV4n8ȑn-ڭ-OX^ <礐 (3|e+g[i fd&C4? Xpޛ`) ްCF'qcyNlFd&Xe?!t }9`/ F`v <5/X0eӼ7pl| r/y9|b.E/aMv͇q{Ozjx{"pGKMn >N+8||g{{' zBcyF 7<ܼWPA? s,K_|&m(OA+)x ^M1!'?2?KS(I3z$kO$} ~K(|3Ȍ0ḟ(ZeØOD^d\R~(_BI!iu ʏ! O'Dy( \<'5y!&߳K~%_ɏ7y!DdPsԽ.>xz?6 9 ?~sz=?7f?Ca~ _|B]8//Oq<\<aO_0~`ы'z?/>.~EѳXzRbYۯ{UOzI[$~I|҇EOP?P_>_B}#BGzԢ?Û{ԵG{?_ZGO/t>G3<-?>>@ @@?_A-<҃F}(֋z1#G5臞y|&_73M(埠닕:UWsJ(@ eMBQ-DIN%W>ecr}k=gw'~~go&@>賔ş{,1hPF!\$njEX4K+ţ1("tAL[5|#{H{ʿdLKԯd5>+h E0XD_!,:-A`҅J ^MM :%4 PQ'b(P>VOcw8?/_"5{L/m.@Y.Li}MsD0S]qVo'U=^F"guMm3ÎpYi5gosp7ܾ6Sۇj+{[@'&~9az۲~ӖunӖ6omO#<\rj`}x,-l4@LFZHh!BK;G~noH0^<_#qQx<Owۨfa` >L,&ea( E]9V B|71 SCږ {),ns=)~}][w4)y+be/ {dQ1yx5h_~HLmA؎j[m2/PF򐷐@dvn/gX mM>ĖJ;XWQv lzh[ێ&슛Kn:۸e;&\ ?D>棍~{o9ȧA1/\o]o.l CKZswfe!8}Ao {ml_ | k+Íj|.|2GSj2~ǧy)ăC!l{`= Wyjoʪ{*p:*ݨ?*\lS_I-%8ȅ4Ps/?l| _Ű|>@o,R~ cb(QE%\kPA=SN35eNyN:}-%?x1"佺|gp.M'iB<SL.1ι/D>xA )YtyRcI{H t# T߀㹀|%U|Ը$%8zM$a4Q,-HǢ-t.h=D#aCo >%t Չ\|-L9BWQS P\D\!ms @=GtRsNC{NA}u<Bgus85N:B;?~#)(̼# 9#::I҇\bKzkYܸq1{t׸mc}|u6K9Xy.oT ^M{ߜb V+22N$18\Ǔ6I{]?)/es?(=oSqqqe7A!Ȥ$gə츓rSֻrc9m.Ŝ3#'/"F6#Sp˼)7Oqg<[X&=ˠoY.SeÌ8?s C|^+4[Xwwʻ+Ws=x-a2sli&}͠\;`'n n[AX#VKLf'o0`W/op!l]s0* [Yy})|j`.w? c6x_L*nL5Ox޵1xwD|c~sSx*<qk |G{?{N~Ὀh~ WkyR1{co `< ldvY6|JHhd\2"| 37.0 gx >Gly,E_`=_F>M ͯIUxiDL|%W&3DļOw_R~I7q%Wa{!z#q\TyV^Oba\y+x|ڕp嫪O|P߁?K}rB_c~Wl|C~ QOyL#_h|#[y("D䋆А!Gw\ϑ7Èħ2~e 3^υ.j6Ɠ._el/B6_i 1>_} _m|MR‰œI%)F?UK?T}ӧ\S}ޣ@%L<)D"~ey~ $F#.G_(wL,Ȣ'$H-ChSɣ1%NT/rKE24VOGAB @R}$O}[=oB>'sfcxH=lz~ttEk(6"ԍL̰].C ,Ī~:pCY z7K~%[~W RoWP)f A :uO#&Z/pqF Z,P[cr%@yi_H)&3xC dN]]&}X۰*E뾽<̦u_z`k}*/<3ۺx/(Ϙ/0 y=(v\+mg~5W<;.0w9ޡ{<+'?T}C?Wl徎3s^:>>B.lR91f#[0<8Bv^oisx[{ʍ&7y{;|=}?4%+oۘu@X 7@X čnevcڄliﱩ=6ejmwZPjs#-lu'~& oߴX7L-uBXCX a]ZEl!jhHK4M4#6Ds3B}"nsw0 fy+Md.F_x7`&P_}) .,N?njKlV ~v3-`w#Ɨު^{ӫoGzܯCw@ZɺJ|CW\h8@k/ ǻ y&{;mBw.p -Il ΩEYf!8]m0omjCMX[<'K{8}bƧ4#ɼ0 `Ca6WJjO++oz *&|r2̽6ǚ[?ȃAy/.%_MOq|0aN)s"KX|K/FGL"=0%ކtb3ԿTe"YqҎTG~Yc ǰ{kFht$HaD%>A"or(ōC8pzC.Ќǂjc_/bKQ> Ky j>jW+t: =:1>(ғ}XUw\lcPgx2ƍ976c=krBY+N>@W-/ȼW uDo-] 08]k,8-{L?rBȍp$伛 7ϞWzNh3ux >q={I1yC6ry|d'yKFɣ?(g>ɫ \n3w-S ,_gw&LFa'k8Y2,d,X0^Fˣ1gnq6\~p -`7huwyrf03\)hX/.`&Aw!xr f 98n{vSx]g?>;;p!Ld ]o0L™YrV́0*.gOཁ 4f2{kWMH{wc>eh+ A#N =Y~3|#c+x5?5??\>K[)d$ѹ~ O_=;xD΀/+x i_ y3?ϒW GO+"}wh~l|\>Mst._|\Z[^J7a#,Ot|N_ Od'@G߸BśWHm|J˒'],P&cZK~J;)y$O䁒Nopxb߃u çA0DE꫺xq79~of37x)=ǫxGD࿀|Q~ C`x*._A_|s0 x/kx3 `,ޅҷzWY|$Dx:^Sה-3 :?pԜϥ&|4󡉠Lfbh2C{0A4 1(LPZ]:nJo/Z7Egi@ 1D/ ѢM)zt(ҀIӉ*&4PB uCBuԇjh؋?/"A'Lo%k4˥9 ;dZGf)0ń:3BL ajS˩?\RuYuեPW'Yb=dKYnY;W&c6JYQ82h 3R= ^zdՕW'>baǬ&sˑk]K9vF_4cGW*>&gL)q7lb] =IJ."D$bY$nH"]$fM"qkLD;aXYe􁰢Wv$ه|!Cn/m=C6^VzoC}7wP=` N>/x < bݻ2-T/4VC | Mp/47,3ƃ _ D|݉s{7^/1y9,̺hwW]à]YyY[೚v[ pE3#C!LqƧ_"|h:_åoܓw!YϺd}7e} cO>/  ax# SNq8yɭ>X!"uDꫭ>\rHmʫ> Z7lЪBؠ5V3 Q A2(xW^#zAt^yC`6Сs ]qاP2ۄ|_!۵+tPra*@WJף*$'59VZ3ja^xZe}:,sq }jL,\/Ɠ#͸EIrҺr,XɃabawj\): /  #(F UJ(N=xi5'9ŘRK9Fc!c9*s  x)6o0a.҉(n#)ūI*>EZ]ŚH.DqQ f xT#ͨ`EAƲP&"% EٚD塲ԗ x |[b7EW@2C/2c#C2!DcB6NqPAv 3H@D9ۏ|#e)+'ty4ߴ7YJ#Omxp\NʕH>1i,Չlb#ϓ%o2O.\~S+e9k2Gf gGؼ _BއF`$kYMfc(XʪSt+eϵ9%`&||ynF{_#9O}"}*<r_Yebv1Slxt<͘YrV<o# .; %~n 1e.a3OpY& x Ln8OGw0!l7 *c݁~=ov`/S-8d 1ϸ[)<-7Qp;p[ޑއމz,x׀0GpYLNnMn1 ,N\Nn^et V5L};7c{n ޗޓُc_>!>_B-?$5 "H/i>'tx.ho>/`y3l^ OJ6·_|ߔYB|? kvЃQ.R믄x)udD4"44c% <l5kB 5oA ;h3?} oEp}o2H )y6bb˘2WǼWyc^My,0_F]~pc~RP3z%e$}Ox@y3PIO/vRME Q^gFO~<<WbH!d}ſbyA oEC>t?y@"/GCy!53 !_4zАBhScwǡ5_%Wu8dX߆O@x$5 %lycg^GE/~$.^xI/̦T5JO1ӥY6=JSM3ŰiJ+tjRHTD!$ƾKޔM#KK<A;}|eM ` >3|SB>9gՙV7~b;Oyjs\kuvMaNuz ;SA>TE/_=.3[yG'םܰXV* \bYXn`aeEܺӲkHZzg%k& ۫R ck$eD!Ρd{˻|MNߩ3Cy)umuuȣkG/+ G0+cuQDDZɾs41gg5՘vUOl1({io -m|$_TX {c?tA I(v*hfD a]DD[Qm=ĵ^;lmC7Ɲpº #E$9q:y?.f9+Xs`Ye9_r!xvU!vTb Yݬo ]'(!hVSy3Mw6!3G< Jy=09˷}j9(TOMZΎ>>+f_ozG_w}wƗ7;m^F{!~_r>bW\} _#yo:ot.s$NG^\Niݵ*wgM˳靇Ƿ^kw~u';1d8НU~xCo | slݜN us:锩ӁRW.^)]nJIg]78\ms+ZsK1~A!ɧ|K/ߓpir6OG8< tD /5j hmVpo-ǫ"or әފ~=Iy/V/grVhOMΔ?!l94sR# lV5D+k5>8ZfkUAr(͍>Qo'x= |ܘO[hM̉6AN4B+h9Esl!^U)^I-Z9g3 $;r2 V5T6O駩7O|mg %lbNe9-d!cAB易cb,ZbL9Fi>]8yzcύy6:wAzKa&d0> ǻ8$'ݺ|Z 7,:Bd&\>P^Y^{]^aeZOiZE|qZ5<_/~^%bH e`^'Au,.ꊱƏx4#׮|ژ/C6f^M.+.ϫO+.P+-Qk[$2rĉ[ jO+u:-ٴVr1k`c$lX/f^L؈Zw X HX\Z\:2q J}DU*ZݱŇT/qՌj|6/)_i2# E1y+(e彸3,&fqX[<\/o! =l/סhxG.95X g,,-ͯ?cwqz-V뫤}_BF$G@  |~L$II4|Ye<W"Ec>H^x~cAz+P?xgRG_/kñl<WʕrW<(w~X˓aWG]y%+oYnRJ7%W둜>a{ |$$PJS#*y=%ݔ\Ssg$%>- z'IQ"Q?xx !oe _O:?s?KA!t#}-v>^a4Fs8O=F꡴P _wD8c_>MxC"mH tI4 .4fc8QX wа `Lj!Vb] oa,WljP3Dr&p\R1XjF&RL2jTR7h(C5NptF'ꏘ=>CNYͮ6r, ٕÄCN`bF1 gzfqƉg8d;j#ͷ4~s; sCC?C[' L^1dN鼙 2l e؎J1 tXP吠4jR])ӬkVJ*v|2rkQ-5YCy?iϙ1>sSF(_EgC,N f<8q4Gَ4ƵNǶ5(gs[αn5ǻШU#O~1X/k<+m|! 81th#J"GI/!/|gvVgA8|2#DPG :k oz78`h 5x`|/ }w:#:-fgx֯'_~t?i|Ǔ꧷#O/zhZ WC|O~W='9Qq~ xE;GpvUfw:? ݧ㥾_C>a;3ar> l+Igأ#ENEи˸ǫcPN (GiՓa&\]rOʼnX)Aq&~nj|;}P^o k([κ%bO n> )ti[Yӣ͑E䎉6Im9mȶ˫b[uMis-n#r#U[N}y8 4UY5H"{"H6Z Kh9h9hJ4ϫ#0M 6šF\Zii8 M3 ǻ#_벼gS:(̹Y3̝0y*(!Csg OUМd"d#dWIR"Y +!vd)n#S1`,5Fc$3#o x0>s6 TMXNDS4`9/J\Q^INX;R`)`*` X r`2F1W|aA|ؓAӶ|Z?;A\]mJMU"NiN]&rpjJ.RZqj]z*ú%JEF-jOKvZ=nX8 9 DVUji//̏y1!b.uXX\.`JJ *U7/rJR*R8 ȓTAf2ᔑ,'3 O@) ZF O3%sx _m fa] 7(K2P?d$OCDdNFfMI&mZg1eE).Uo)n BIɨx)/5WeT"* y|Bґ18RFMb&FPR$eETQdEWFQ [$%ї!ŒHD濡3 0P{1TP+y𐑛w.N)d '/4EI;[ [c0kop;xxo\1n&|&AWW](h?'{!a.n&a .n݌e~򛑬A3Wq}}{}iho*z) g^a3.TB/CwWcxO@;e˾}$6$.W x4XFǼL4%8ל\N$n~'5!M6H*6yD_O j ԛbKg{)󇐖w'k\..P т߫*>Co 1< i| ћQz1 Yؾ?A|$/dPQXk)|>I+zk~m͇y~joAy2A, %뭄K }(n]^Wkx_~y/żM]uYK9\>[az*RE럄}_G_H|?ŻNxdg6Pt.U $ΫQzsd]Eh(CEZ+ SQfRtQg2IHQ{"vD}8ͯa1X< ĺ@ F HMҧE,b aHiD#4D4FC2 4Ddq=~X/cĿH1>Ǜ'xPʊ (PV&89pd8 ZՆ8qplXnd]uCk[dž({n?Ce>w,P_EGqK y&)V*GV VfXX]`":BˊɂآÂ-k5 X|8[,@h]}P /k\5pJ( 4\i2ƟJ<0~ScI'ϨY8eb7d@#8!#rA7T~Fz6v"tr{WGB+ۣe{xTʑ&lj'1ER_Oy<#g~hNh:눴 !9qpϧg1pg1qP00/jhuS\PlL C%V^yN+H?y%<_b~/= !#2GdoHǠ_?><;:9WFk\5Pr1ʵ 1PQ1wmгs>KsA(A]09TF.rH9 r?+.}L2{@6y.:w >uBsD\HtuV:PU t-6.y$ޙc>7K}JRHK!4nBU4mhi#MΦJ7RƷ÷ +\ N!XSp5Nzq;yN^}y4&ɳj=GO 5i@XD6DO 4Si;i&t.PDQTSqT[έ8mp|4!%'?W<];0xmv֌gH{┑ S.d=ML!CS,*T:#Xpȩ,qu,q=,a,a3ӱYʂEVd% ˯uy1e<:;! TlG X(_.v|%C׌_5rLn\Cr\5t\'xX~X F9#-Jb%BĺUƔ)(VSQ&T/(C).i2f(zjࢨ `+IA9~g ׇ!.p,'R=Xg/a`Ye, /)!x/`_( x[D<}[ S'>oƖy8eM?[)@fC9sfv>3W7CMd&h1EFD᫔KH;R [hdIod,Eof11~XÂ4?\Q:ʅ,ti W橂\=외}7bB$04^aXt%NX꒨E,0 #.orJNSRu\=k$;H'Cx|oe OYS9qvPЃյkWW>>8X7hYpMk5|S>^bd[y7ax1^ǣy)=Bg]h -61x sE];3dx1!+sƇbcVeֻf8lFf5c 3F60|ŧB~DbgO, RH% #)~jᓚGTlRLQU-PەONMe*+w;M5ߧPbSS I3,hG˄vlhҲJˈ(:B" gAi?l>nՊ|%d㳍hO9 NP:IPм==Ѿ+(MLD^%BZ?1~LJM(? e^YK86pG|C w2h\Q :wBz@|>~<;~9w7ND8 9Ԧ6+s7E<@3[NyG0OxFm;2!.)ɺ$0uIL"zDԳ*7:t989sHiHW#WxU|˲ UrS);udW^60|DW{Gn(`Sø䄑cQ ƿD" 2 "}yw s %lVH;HnNR?/g}By F^MXSApu@XyD&H | yBgIyE+Y"ypg2)&R#wE0:P}uy+A+gBGo/ǧŠ⯾WXo=|B0ByB,/ּFPq-4 a9#.rGLB~ȉZ!7H\iP(]L:>u(xRڭkqa]ã7NAvI^Y.wx ̈́p#nо0ۨnE`JZvJẗ́P 7A&R|[kb1NFN`+܎怼5Gd(9%E:wޘKt6>ϪU31&iDdT2*EP @i0|LdTCCqmGNZk\ڐd)iYg-Ǖy~.FY3>kgZP"A2Fhɘ c8|liAYd?v@˂d ɉ,+hvhĊm!,fv嫲.+gL9i<&ucacg?8zT~xKO/"<;:+%9_Lr<\/\X X>dYf`cBS'4ƺә`0Li´%KzБ4fĀ)RQ@=-QJtOMLLJHFEECA?>><:87755fdc >}yťaÕUՔ QPONLK, XR(K߁wZBghre #;{hxF˸; gB]Py*WϨ<W?S$9b$:ok`\#ĻU+Yx<oy\R=ިȁqG :·xzYU>5O@ğAGR_ET?OS-T|j ?Λ NbÛ{Է/_ ??bLO>o yD?G~G#._4t8O_i|oi!"_I$Gs&+/K I;޳?'߳5E7.$Q >‡**1?WƟ11#5Se_}gbOr܋"vzxuy"~*Hgu}F}_lO)of ۿh{P_t{W{{z{O{ p/>r3v~;|aG{wd_|DOxx>_R2~?Ax/B}}|]x; |byXH/8}&^ Mؓ>}>ӇO~H@_gCmPgs ?-Hd}Y??[_+~뽸1p7z=r~Xѓ-}Wz[{;={=};ӗS}X=ρXXiXv^ꛮꉮ^˺[C՛uxoc{8ÿ|̯}߶b8 FHG1S(yR&bR4SOEig*UsXz. ^$_ND|꣱Ӄk~ QPnO?\N_d=&xjikzzUكh{3O|o6g_b/`0WLϸ}K.\vAJ<_)!祤&BGӅǀ᧬j>|Wr{' 3OG#} HVHUAt~Dg#Ce"5 T44*2ii t*~=}-~Yy~YP}0olvNhg,E< )E"鈆N#"Ð2J BH %M$4Q;E 뙺>J*5~勁郭5!G ]DhhBD =T4awAG! H鮟 c,i'z.z'?BTq#0Oja> B;xO$@O[z.s4I{买!y;M8]WNY9jUH֪rס_dImT7@sxtץM.-Kw#7-cK0{jꮯ0Ss8꬙鮋Z}P=3~TOMcԿS=3 _ì$K]OZs ם5Z5]oal?Gm6nSC|oD3|Kx 5*ް9q5;=mqmQa՗fq0u,";f9A_:}utQċ^Xn.Ve4Vj'd?J ~IMO~)F`Wyu!x0N]#7<njplwE6VNZN[h-ef5 \xq?^rwLdu$̙<R}rky. /|D70<&P&a)T\-˦91g^87mr+L{AgH.gVa4OwYK&#p8\:.S08'sw-i9lr,CIgP-$~5?+OB<,eZP˷iy.-凲O% _HG8RoH{w <!/Dȧ y!7ˆՐ 䅄|Na00 oW^S%/aůY&,Fl.x\N +~)>EԧT?!&KtBK VD_:G!HHH'I$/I$~=OI<}/{nOITG8o^$?Re V>Y;] ]_1?77{;?sAC+9Goqw"_JHguƿ#~r)޲_LM|Ч,}M K^ߋ{Ǿ{6X~I~_B:?<8JC$W*eSr~o<}~WOކܗν}ݓO}=Q ٽy B~="/ֽC:7v"xC< g_GIikOtk^kQkEl?@nVzct{COFlJ6} B`_Svx<|__m_뷼럼>닾~xiTq ~O6o7I[[߷oZO*1d7ʲN39]K0<֟y=[s{ |<ϣο8VO7pY^ճЫVee}Cu U:7Q|D3@UCl8z0,C}۳=1>]yK}5$HREH !RrӠ32) @;E%tQAQ֑Jzϲ {1'_|w351ʙ@]eHQ:B< 3vO@9$VTLDC1}aFKE-2<cu{@{kj%lŏx~6 z3&g0lІλ@4ǒJ2* HG3!!}t4ё@IM0ix /@|w߃(?3d,>Ƌ4t IB[ mhT$3N<2z7#ˌ*RCх~*bZBE*LjjR;y}b t_/cO6N|BQmh'EF(:Dқ^$pqDM9a魡0A(MBR# MmAꠡy3({J|/!`<wo`Hi3Bn"t!Q9)4cPAҰ ~S5|Rz&(k?JZAfO(DAOfz0':x43'Տ;V \}6d!gk+XOY= ez Ulzξ }Q+S:`㩄VUZVj(c 5ZGu]ḽN?[j._mm{GJ|ȏ|_u^pk6wx}5+;qMvf;m֚Nǭ4E-<> 9~+}~C㗘mc70X.{f8ĥyڍ=xz-7lŮvkygxoN:p t9?Cot\RO*FR؆3F5rNv#zw}ـ-׃k]Շg8Gq'&އ ! ?䁶yN{|\p /;_;bȽ\ɭØ3џ =Km?V2p lr1^t=r|pޜm|lп]WMMN1,v,#>IyW˓95Wqy蒋X'qߦ}ܨ}bS[W53FCx ? BGaLb)Ȩ'N]tgz-VńoMck1<EfF3h)asbvØ2F2s|c;ǘ8 />‹ FJj?yl){Xg]V:Dyg5D`#GAV򌛬KTʒc,e 7>3;` !Tޱ|Yn-kI c38I,px }?v`9% 'd6ˬQv`!C'| &=?9xrvg#w mml8wxz{ $< s"x+qG 0S} ?7_ K<#``>×/=]Ly<>X{z"#ɯ&$Ւ<BEHLj?#50Ҿݯ0|UxbcƓxoGh<ohόeĈU_+^EOQ(߄䣂3 ]J?駖x<6B}* ;&~?ӣ?ZJ%KGG`>wvo[8:󯝿 =чB({m/bE$ }O/⏐j`t>osP _} ?')ʟ4zKǴ̯LM$N|Moy}?~? J|/|*|9D^q>'/-=Qto3PUݣL?O{ y!h}_}fxC׾! _2#11MPe{>m큸|{E߃6[p?NpEq?$?sCuBwzOg> Q5?Q~M0B?Y=(;á}w@+B hoDiZз=i {>/T/|B9Ğk&GnAL ?YңA=տu{{=?|#z_&xx <cx=Ozy4V:ԁg~ X1 ë=]^]~xލ[a>zS-Z9j錗3[V 8h AGm†`,zzz3kqr_vo'tjWhNдVULMc`jzi%04CO}=Vo{O#}7b<&h6MFi2z >V4҅tהu0,a'$ +!M]D;K\CX7|/S}3Ǜ*-=҅ Hp$1(ӒHQaHmFRMeDuVQUEVc(šhGEYmc)55>勁>/j| (jN+EQ )ʰ" ZՆDU] GÆzSC]C uFB` Sھ3}sx_&wYD!wjF@+BV ІI8l5"}@]txk| GlzV1 u(?*$F+`|֎Bl>[гu<[czNaxtkLu.g+hWAXCeE[봭V[l;vq[ ăU _,_WU?^e<>lC8ڄsͶ![=,t[mwnZo&:p0>q>ӈ:\|7IyO{GӮിv׾q(vt';{m[=wրW-8 gpq+?gD|# 4~_PGYLv;{y7|;w-݄]ڇ؊{x%EkGGxoIy0~ 80.=O|o|pn|C|ؘ{XssNO4Dsky;\ުf_<ƹ8!S>gs.|~r&1M>)nzGX!x9?sOǢxĨz闗q?:*{[|AgXKh!|wcI;;n>)6o]\:?4Ɖߘc{L2C]'j&CdsLe_Y}˞w/Gf|87h;npkc؂`Az DYlbsAyuF݀GO`<7O7ӽl{"uFal"!,W`$_03Xb>݃GaٜONp{Ѣ/ # 1 ^˂g&8?F%ʒ!/d{}#0\̼3K3OfʼR-WR~(J#Cd\=5>l&pN9y0'/䑜Q'9Rs = m-( n#Ǜ:Oǧx.A#!s_q}C¿z^"WܽR &̉?[~gx?TJT(x?G(ңz]"ě{T?O8M?O k%V$HO$A#V1#~4Ón=)ٓ=ߏ2%^7x*#MHNQ>OQ~U>QU~_-LL׌z lZIK@Y >'"~!>o@| GOAϳVƟQ!^'=|[=fk>fc~GyW]߷оw>{2{*{/! /dCOEz??#'q_ `àQ u>a?z³?od|oE.޵W4ھ+۾^^ھ-gy܆眾~ a#^!x˘=#ğxB|[cf׻t2Kf>ߊY#}bDs b:+3\fa@{}z5 64D`pӗM=ֳ=ͽ>_m~߈3w-^g8zx3/U=FBuZg]DuY8h*.VBh+i! xV?^>o:P܋wj [Cl ckU-C*:0:Gq8Zl\ڠt 3a1}4?4<׆z/k{4_{8ş|:ϧ#r)-ƫx^,l(z,`-WGBZ*A+.VU0OSP+IaHJAST\e{~CY>Ù_!|T^*MJ*P%=dJSbRiC)4@!j a- :*J(ºGW={1!_BVƏy1<vi20CE08# Ҩ-2a'㞦Fs:̩j&5PƞJ7=>73V? y4NF)8aKS4ixF%dZqj%.lXǔR:+i몤:CIa,}T{E操~'{b:ȫt^: V[T5%$]]# kT$o"exuwG]yF_sUa"@TD"B?彔c){y?x5zNhk S"qu‚=-x I6!чĎ#qcCe 5b+!3$PCBpX$w2| ouYU;~䱚C! }b>$(A N46(Pْ@fvm>k-㶇xK;v-w->c|:\<U䧤=!ޞb vBLF&:hQ[E"m[^^h;{m;=w|SwԀ_xY /|w*TԌUnqCYnIlx?| ڂ=x؅wp#>O;tDW /);y&(eכzU[~o|p _[q7;%0\̝Ї}g_}^Ѝ/ FL(y/4͆÷17>GW.|gnqsfv]k5l:&.Zu[4Y>Vg(>.. Or'_]靃?.bG|~wF8Uz]+]cu3d #,b$&5>]uŝr;:-}oMmbx[ ?]䣓W$_I}W^Ye3=1icw\yq@&\9"}䶕lvrS޲󙟀~71iqxL!8>2:GdΡly!U.ʀcY-e\6=W>zg䄧 Ts^^1Oܹ/4f XoՆ`S'pXu.1X;`-X f< `=8o{6ߌ ?VȼP%| ]&sp= b6xKL#n&%GaSm]]}W䃿zc-d;  *wb@}/R@~y1iI_UkPͫI{c~0]~,J$:o8cwS^)OeTޑUy2*E噦)NK~%I~$;hXCm,47b||kA ?* ||P M/c|lBW }U<f;Y,~;x;)ZPa9,~-WaU0Wiz( 䣆i7M}DcO%x9OFJ_C/Q>P=_jKtKtxo:j|(bB.=1_12_-4K1hi>mDy=5<փ}ۇ(gx? (Ň#?s Ȟzɤ  ii9/Q_*=_&> /'|O|0RDHo ǐ ~;cGdW.$Lݫp=w8ބg=}ޏ}[}_{#=C{;}_-}°?T{Ew|LJo,*P*=q&ONg>{3^r9oYCMq=L%Y^+/{WT&~xծYš֞Xb-g5x\ng45?&4PiD+]Vt9lL&qY_Оo^ % I_x<k!|V DXB,$-̀aЃfCەVfadEw}Evt2ƒ>Sh>ePVk]6slogyߧߗb܎W+F!14C#:I]fayvt.l/Vz ꠯ a}ק}1^Su3z! e4gx!m #zXzp.0 3mihKꡭy:d;3W{7s|;̿;ԯ[jzNk .IәZO+jE헅Ṭ qа6Lj)Nֆ6l=W=74w8͟s&>ŷx! /Uu%&2̉%uԀWTYUfe5Y^TXgQmjQSY_hk!*s~k^?-8mP|ۋOeF*jJ2UR7:(q kA)IeAk3 [5W0tX7a D`Vnu?,&HZO!zXհ8@g4`$Æwd*A1luaTzP{kN`{p0ĎjBSEM!rR qoY߁?T>(!V_L^(?xZ'8ué+<,kJ&[.]vJfs)Kl Ii7%}P:Lv ?0苡q?'{!\[҇(#II &E"ţ}whFk0b˨m*BE1 TH+``9PyB#m@VB,m6%R[mG" {nHnC -&dw9=SPH@ c! Bg-9(,Fܶe}3wOݑ>w#ף7q<}mxNtߠSTMčr:GDPԋVC~ G}ٝEй+XyMcQw[8Nx߆}8܉ٌZ S4Esd ҷZ|SQJ_0.xsy$cU8ox'=ݔmژ7sA.h{V9/>#$叾ǝy'1[zk8{8|n}mm:اo}S~4yuO@iʟ$&qW~~bS;w8nq0ZYxj3㢃+h>x/-OsJNǪx&f]9)4Fƃ߸c{E01̤:s!*Y<3X7rm}|:EDȏ9$d厲T]oj/;-f ~~pxB6#yIV2}dGY)ʢ[s,[eȹ bƻq0vgv`%xh ߄?N(WޱZ`.RsǬ2)F,9'` 8r^| o^lv>aW{5,l efm,.-t wwpx.}pnf;Agw BoxQ)؃S#pI%bFMGaΑ`{ww ?2#*Gy~ < @QIgᏜQx <~/#Mxˀ`A|_{\-?3y%F$lA9F$d,$ˣ[O˫i,!#]*3GO~'OC|>zYσs} .:OA{0y&D^F-Eȗ,#U䓈<ѐ:A ?y;?o@ _x{}bWh~ŗVb<:+g|b4~J3H oe2л0O[*ۯ{UO#[oJR≜> D}EgN׉:P'^!csN(zFoMX7vJo $L_D*h>Ot '|/.Ч'W"}п9 F/"~Da#~GTExP\}@!97ja&_(?-P~PT^-r15|k='wRsԧ 0|)S >'/&~_%~YwHǐKoC9("3*}c?b=K=[>fgf>f[x8~O=_п@{w=/?xFG/To ~>'%r%G{H362nh8,Wٛ^oFsx;z 駷ܔQ}} 2#a'T3^ǫV gMh&kV4њkG+!ݧ\hiKc i+41=ZChQ6Wuo{?(Qoj|BB$fΐn"!FavaN3͚,s 50a,4<5&I &aY֟ٿm=ㆼ ^O5XO@8)@@)G i.LS} j-0/}i-UL>m$?H?/`o3~I`^ 0,=V5uՒ^Uz:p6|ցa]Kk \\7qu!y:'3gC8ʧ|9Ӓ]W:TCSu*"6L|Z[iq5{au>/Kf1Nj!yU*n~-O!6t6 M4ɴv$SۇKn-m. B^Jvg)MSHI~%nmB| ZuV?^K^K^h mKԶ$$} Hr]yT݁Fx-g7fE}U8V_`xQ=Fd4x-.`p ,:܁-G<ts!6 gf 0pz?{xo6?0Cp%8ld 1XY(?Xt͖0$x?aS6{NyGC pKms|2~)|b.΢Ǚn;Իѻϙ6oimi$| 7[yN0_|ؗL]hey,OKQ~'?}pσq > IM/@OQ:|%ڒZTK)!#sF"_;2Q6~p|[Ox.7~_913ߥrSGD8>o8zI/#c[f~*GExP|(L/I4TTKx@V֊ZN*I[PB*Gſ_yաO)>W l(D壊P~gkD?1OG}WρHHϓ=)ғB=/R/R/}|?JEQ|I,qo2*3dilosRg{'܃}^~Mo9]e6oi|"G ?cI:[xQh+C2}Of}e[=kfsrcJx~>B% s,ž .+CaXBk(h#B)TèGzEXooY_Yw|A/_*e|n3O 74c~}+|Fo]Ňt_.0Vz/L{D_|Ƈ|<}?>^;+:; #:]!/ ]Mk䒞waqKcZiI { !4M-ܨV6ڮj8;{<{B7$/t]w ûA==] j lQX5 SI}65riU]  hހa)δF:z;jbrtoyGh*O9$o=4@BΨ~qV5V_:l~f+zLk2ȸjQc^+}ma>cclvsg||G~ےw;%p2 : D6c,kSZgؚQk@:k.0>Ll&0v >ޫ a3x- 5'Ƶ a[?|zƇyxkǯ0<-:͡S]f?uԕP:^r/fo?7t(x7"zz>#&:%6d؅\dm-eVɴB{++m1d}!m Ѳ{K|3W|7bW;eUޭ1 [ ` _@DkZdf !ē>܀߆vbm+;\*n)6;jY觕xF|Âun8; g[uv\*mUET]Yն"ہTpM7%eSuIٽ%TQNU2?LėvC&eچ3vSmrۉ<4!n scbC݄QwE]gP|zɩ&{9x~!Ńc:D,+@Sgd']4mʤ7'ߑK}#. x. vp3%?(ΥUPI#!ETN/5L<_?+wY f |$=T#M jTxF%W]QpE=NVɳ*"|ihs C>gPU2"ZO~RSTZ0" !ne(q"Cby"(n xO *^%nq/<Ɠϸncq̷>q?L䬏%+ 塭|Oi>灿r;Xele;1u:K1@\ȓq##~dvrQ.^_-?e'c2 Ev򑃜/MFIeЧy)XN\˃w/- bZSXcaB  0@@0dCȢHmm8m\CÓ@݃:2~ t!4C8|46 Y0J  |!. 0n#j]Yl," %ywE2t? D9BxiFOO R hirt&q!QIP5$š0el'JJ|aCmb0Dx _DP)OٛP΋ c<9DtFEg}ƁsnWe$=Œf8ȱۂ7˿@@f.YB֫ga0(+0H-B͜x4(LpYKnDWHHGƴ89^=6h*(y~$8}Q{ `}Ϛw0.c!!"c䟐+ҬDZ?<]he,(815ԢԽi)hec-:`- w>F qj-^t,'_(ش2`Z(&" x罸Rz!VŁ2ڍHڗh6ag_J9tiA%E8|&˕]jq2]}19EIzџMJ!gφtE~P]2KdոC.xp\(. _'/D6(l\rȥLCZO.q 8ù0j@ P,s v0%a r9jq1 t/L> c=,DVt(;]N1H`24~5ډm0BFC콀ax@^{5j<&&"hg-qE*Hw"t ߷u]\-_3׍sz[z:4^{ :yNpIu#ء&(H ]hNӳ( D8!e !L_ 3q=x܇ŜR0u^bnCv+<)6@nozdS4WX>TڋUG&a<ѺgG^yަ?It|T,x`YjC_A Gw]vK<>2 x%|e<Kd'1m$\5B[n+>ٮdk|g|q O˸.qp'kOeΕh[f*y6#eL Z 5:pfp,}$B44t)l@QUk4gރsI$^]#GZEzAʶkz ؠci<3͙, '%GGzAZAG>=" Q=SS x!»-x>`'l-XOY 6a޼nd u6ao7($ܾE(ĉe'I K %RʝRʔRÃqR  gW=bYxțbof)9fgeirjqu8WZ-F䐮6!tfd7"|7"\[[X]KS18IL:cQK_#+R)mrt,Z`?M{ fdcHfJR 4&04EG/P=?\e&L-SL?ՈiɟiCE!XX'-D_ ڑMZH4s6d ^؍;{ `+bUŌag߯z+#WCsK2?f_Ѓ!hb"!놠\$GQvDу%~wۋ]~A  Hu$H ED&OP"UP3x #صc}d##PoHqCVyBDZ0x)-wI#I{G% 2nzA+\yjEDgYϫNj]57!Zp `Ņ3@ nX!&*(sgEpa'T0X:Ԟ~}dȔ9f9u#QN,9P 'rsBcmA/;!!;1d J|$ЙRh& udbe*fUH,eiR2xE%D}bD狓3ww^Bln 1[LgÖҏQb-;ACTP4rccs+BL$We ߟ>H"j~X-@\o٢ },o4/=ApJS;u"zPi, \>@b|jÆM7 UtjBzڌd="d^+>T} ۨ˕6ޠhTߖ='vDTQsh*قwrL9f xt<+d/y/6{."85Y5JqSxC PA˼/Hz( I KBKU_=|_*'S\ v tPհڠG]4 @j#tpnjh@xpa*og'D,WbdtޢKzU8+#Rʱ LIL*k\&}`[4T¤O*MQ#F,y"j Ag}E}U,?J /ު1os< OC sEbK'BOX1&$hJ)"mlmopF4''//]x_]yw'?E?}M,Syw,)AIpA #qT+d彉$I\u"F"Y}} @ `]sYpWxl pR6d lKD(&oyH)T2I&_Ò5?k]ۓ2~2yI"9ceVn0d*K ()C 8j%J͔>FڸY)0t6Uj 4xoe Dzlш{ P@ 2} 4!eVK˨j;kQFgLfM8 P"Uzi>{RHâ% D%iY l4]qೣ(ED{ xH83 SVd37:xC,S#YR!Eb! (Q!PHL]c-bP-ճMKTxKazŖh?k9A(NSar7Yg-]A)Xr844 X8Xhdt J#lxǰPSo6^|*`Yb{~\ r d> wJ d\DI8INѐ:]3^h0b|wzyr5a(O|D"nJz5`6kZM $IiiLk9DiEa)\jqhTo`9A@|U<}O rڔ2Jm~g\VM"Z¢Z욖ʞe[ ,o|bP@/P“O*XUFH9I m`:*'S" ˠy GtPݱR2/Tq(^"䀴91|D 5S UݵdU`Z.,YȢ0o Ct%w:ʑ*JeĀ?Pr`BWrwFb KccB) "0{<\W㰸m5 \KURtYDW!]`ԭ&7~8 CKH hyGwNd Txode'Ǧ(LY"u\;Ϙ1n5vhq[=fI2qk'[Z1YR?L&ƆԒSC?̏ɕl*mNn.^veXMhuEݮ]KpHlũ_,g@U!IXU%c u.w{d_G}_:1Xdkuf),H6`pJ Nw$jwlŕ|ڐ[;en-[N5ːste9;o3!ee!Dd!oq23O2R}Q7&8psvC.e="rֆ-N's%ȵ{ MnܴD񟳵6֜\ \8%*rNn5"ǵcsuHowN٭+< feQG_}PorǙ? R.xcڂI ާW56 |x}:QS'Щ :U:oЩ7ܲ/[C@3ob횘)FHʂ E!HРZ7تXspG}72 n !Rg-? y|3f@9r_B%LY "vr-"LN-VTMf'Ww 5DܜRɘ691jkG;dY?D$Q yl08%t'qrcnc!J2dҴ1%A̺ޡ %/oJBlF v\܍gXg8gX:!jm`zG$7%K3,@KsK r&8 F_ >(dЕ|pYb4 q-C"\Z \K-)W[@hAtp-SH,S#1-Y, ,[ QX ͓$g˲CD5XnX0-$+ʢD$#k F[# ?G!o`/ .CqIKS)QP,:I>4$H@F]y&UWKS ΢ToPlrߚuF)5i7~f44z8r a΋Q?AS 3Y YVg<79?4~l|dJ?G1ɈGr;%k͜ي @ki5&p rmYb9lѴLb bJTWA9X*QPg<6c0}pGä57~>62jS%ţ B(-4.̫ߖ%.uVcun ʮZ,꩖DMK=뢪(%J WN"4ѓ-R8dr il9T,R+ܔfzÒUҸiY\R:I [tœfQa0-YH,Ov-hx'M4P`GY)kH:d56s 2 xsy{$ 2߆)rU,ow,АǸMwBCgR+GI=hP]hdYt%CVB0-ӳ8+RVu R $UIJiY\n]u4oD^ey,P 0ȚF`iZ6-T JxU)R`  D(Qm*Z<X4媮iY g)4􍟣[&Y. oE*iՔبbHo+ T r Mj2_C*ro!(rO|&)SjXdgZUX@bZƴ$@ʲ'^9.`!(s L=7~V sB!ma$§I(iA$!rd&? d~1[yo)hZ?Q~zN.كEϓ~gG,9h؄FPK_Z.x) B,UjJFtxUX4%*42ӲU[dQ5mWt}> Ie> W-Zc^r$<"@tGҝSH*3'2UX<)Dp7~bW.3Ş7~B&Y6sc]oW%6e3$ٱ!LsL\L?@ȩeG^%.9jԌ eur5 %;D@Uq`*)2L2yOL)%2E zpTsHH/8g= ^'87~֋D)k "R7~r3^ƀu g=f/1>=WKZ]+C>!p֍4K 1XBF;`:5${U#QHS˰%1$%ˆiŕH4RIJmѴlUZF qHtFZ_?o6o|CSÌ&e(*zppAF,ɢlP!6@2/;V D!zP&(TT5n9d],lZ Zաq(T[=;&{:t!Ă;|t2 趐pg-(^tBf?I&[ד57~,/U,U_`K6qbF{uҔSA"&%. 10+p@٢ZgX Y5-O"0-uA|+nɖ~MI%IH-A(ThẖjɂExUbk,O,׵dU3-C,!Q7~qTmqZtmTXõXaZFV-O4X\yz&f36X\Ϙ 9oO!rB1q/0s-LB,AMHo,BEUA-Il#фMꍟ8F:Ţ}/F97~Bde/y gVFЈBJrD˖YkpًNVo|䄔 9 '9sprN*?i҄yr>?0uwP^6cp0|xh}Yw@ q y"W↬;lTh%KD8%Ѳ @h4YJjN2( 8g~ÎOO0 ii"D1x 0%J$Kڦe-%Ev s2)F#RfVh2L%l$Go$ゝ oC R}\TSDe+T *?'LuHo-햡L/Q). MHDW4eZ\lϴd[6XpȚ7~N&h!Pi푄 oTdVd  q9IS1Xex(?Ĥ+OdcoD =o$G^_@,]ۚ `eٶdZ4-WS DV5ElwP!IyIF@ $;FA#>)o$3!e,X9Dwy܈yu*!U!7~c[SB́QmT卟$(A%ҔJ!do#I("9҆4Z 4jt4knZ"C[o<%q퐤BBEYq 1NN zdKC7~>ō,FƯ , h0]uH,4Z3-˖kq- lQ? #~P,Aƪ?')bd0Š==KUx%TK [ThП@$1<`IJ80ǛZm"D>6“/ bxJ{\϶ @xS]Ke5ϲ-W5;@N풅rXUFt+KWe%Ӓ=gY [\*" \o%G%ܷ&+5ِ]8C)  `pA쯱W^a&R4Ob LYYj92ȦmO*8J֤i`Hyg*DQP 7ύ^s ]TPe+F$ !"tOyGAAñ4@ 欕  800D2 3 a 3 {QC̓ %'@@#Aq :/C3@_š+r֔FųLFmA.[ crD8Lo8 8ͱg ˹AhjQd"t\{1 LCR;]j7JTF ̝J U}{D&4cB٘mObD8Q+޼C ?)+Bze-L9yi]'הLJSJR5%Eq@\4tn<@<bTB3Hȩw$\Eb&:ZxTH1rP*>tS(W. %U"qd+| hE`T9hjQT/ e#=5c y{"˪Ko *;>`TZ}tKQKT>qDVPQjeEDEITG^#un {.K ZsĬ.$? UJ€~"RՂBRt "ݗZ,'PewgЬ^iřtQyV򘵬L8w]}7@}*ߠ/۶]j*R(V*-1 RL/Jз x 0'6AAVG0^%C2&^Yd JDTN*Xa'B-IƧU__g9P$T.RGi@k,AoVy=/nDOqI,"P}msyQ;`L=-4 AM䰨+b]cO˷5^%jQmuDuE@c嘜#YEZ"xEg å K{t@$2$~g{VnoSi tP)#ψIkqi(LKLib('ң! 16rtҤ\'s>#Bqsre.y$vZ;\ _0mҎ5mcވ djX|2N91'/t)jI$&)&x콒DtwARf݅r)dPy"+Φp,~x!ȥd## >j}}pr^j|2Q])bl)Qb.sH@P 9 @@ܡ3BNń~LQ,/(=ge$p)+|&XZButXzLҎEkwL:bܥFo-V)t{ F \?ę: =-̨f<Åx<.K 9%sWjJcQf9-,- 2.QʽgRV|VJ!U]%ȥ$c\w dЋJ4Bq d3e2F&3k-r/t^bqۗ޿RꤒYAGTdJ-Aᵼd!RXr\%#C7g+|L\"*Rf{E^K1caPoq*RhAcŅ\Qѵ QƫȊم pVQ)!dgLs'\SZBU$JKfL#ʕۦbԮنr"c v1ȇƵ&&[H U۔h<q {C0S&Vn?P1ɓBu%X:YNbCRV~vLaoL@m}I.=e<߅jQ J0L;2/pX ն1tq 3 nurmՃe--p~C8_=]oʀ+*dMgڅ Y<ݔ<,u]XP-Ƕ`SEqvvǹP=4Nn"dL:$@A[g$,* ˫+H[Y*^4)FM&ܦsSd @*/tnDuw]`~dYK#AliHi4DsX?OCά]I0-|c9UB06N_Y/|wVg3hNE2R;zPAj]/#~spy^eQR6~=N8RǹHG 굔 6DC[c8bQb*U*?p]2 4U=8$H~u6aD%y+r'?#$ {O-svr++Zzj )\\ \Y qC,}6n;6!r3_>@hj; Q'mE(+3~J=4YO"i ;[}Ypd "cػjs [.tGIHug8M0 !6@h˃/bqQxTo-b".NFֶ{qy6Fxїi")1~CK6?Źшa11 Vq dJkPQ |\ډh)fˬimvs:e΄.1׾9{?SPW,0X6iu}>?WSCm?C-뙷\YUSk.U=|Wf:Y,)Z@G4B^!v@QxZZլ[vxS$`{7YjbE;^FT|v i-h3/Ѽ -|jNg*EBӬd9XFU #%0lΡ:=[I2LCx);] ÐR@)t7oAOc,QbsQ{%eAb7i$f,AKC.٥{K1$kk1/* }矌di_Q[<(99g"q*wk $_)`ᡉ6k 5A@F#$値kW4j*QZfx$(E!u !J|(Y` gebM)-%v#>Xsx'{ClobQ?{;`o@wlϲI;Sl6u*^I U)8l88_gyAD0 T__bL.uS ;YT,ѐ8CҨaD" zyWB A.@`gH Ut`WJȌ4Rvcn0 t7h(0\i"ԹV'" ݭU:G[bq3G$@eAUi,Sߤ@BH%s5('2驑@!.4,W5t*$(6xc QAʃ ] JyI7x?/2%V,HO3ch1m:;$EHt.q%/wnr\WFs*U9Fk6PXr39˦xI8_S^A$ I)W$4E3$A7x7gǍo^ODAtH#!LT с$ 9 A$8yj;ΰ0C`ʸq 4"38\?`j̀!'j Ԟ7#A 3 4@8pC Zz' vb.83 GrckAFKpڍMV@yYTp8~=o>E$ۿ@,踿o]N$+ggrQrc_j`yo5q/k_yHȍoxCOey()k3?n.F0H :`=]nޛkry;&ǿk3?+< 4)X_u % G+v|A"L!q2I7B\^.5=4^h0W{} A/ 4LaƇW{m+h6SeXQf2.eML|w^99:yeĸʰ \XSd>9L@B|as 2RD(=ON{^2dDs^@C yniZ14ErQf@=𰃉ߗ]}v6M~<Veas  |hQ|e+@@8' "v *]rݣYx5,bAJ_pNՌ;8%h6tvC8ԣ : Iޑ,(6}UL԰-EGK 1Q=ts4"E}dyosIY@tỏ^K; 4A"H)2Z\tM `a.FL-(xr0(S劘L񂍠`VfL yٕ{ǛCsY}yO.sf]A@8:97f3;\q~ ݅g7E$'^|O#/,65{P\ \X޳,%_7(Zn[j|hf#i0j<+X0QjAPjPW{-%Vf0CKQ@Zx4b2"đ'%4B]jjiW])lEox9gv^K Qix<ЇTZW{Ɖ"(ᢥ:0_THBRHHL`Z]Yivfy2g^oͦoEv"FWr3t :0AÜW{-5u+8tm) 2@#[`RkҠ W;f?6 t͕{Sj\MW Fе者W'HsѺ6᫽Zz [z2M4Q}hB5fPkgP`5֐@% nrx9#o6|+ֿk"LRא9Կk): 81lѵWTjT0Ș+3\Qj!H 6aտk 0(j &_0,9T@h*-Z O)(/ZaQj´Y85-RbAP! @Zȣ DoT (PÑ"Yiz8)^4+0_f+j`X hb fG^Ƌ9=rEAv961+O=b$/;_9rncN3DT$Y|j̀`+h8baB4PkedI0=e7xG.狎ݾxۣ}+ Il@Q"-k~^WESN$yr{?|A4w(77 0?,8Xr@+O"vIr4K˗$(z"Rloçy02_reWmݐPldaF::b ' ,JYŲP9J}278JIfEN&>|0B6=?^0xY8Ayܐ(SĜM<M1ߑ;_aW(q `PDAyf[&o|s77xdþIMXr'# ⲼGux= 3wC~dgb+o>|;bϰڞuw(84n/x4CW:D EFNt8brIp<7u2Jo;QmCKюŦXQLHS7kEY ɌT7;䊚᳦IۿR6JhS,X RM,o}\L z9rd)D5r򚹒|b&%iɥ{](%O\&1&(XI#cR$.̄ k2rM2ny:K]',uff'&('>>>@ 2,Jze51͞^MZhMi' rrA15%1!5\Lh2'&(%JOMH&Ji)uZ%`O%dʕʢhW5]q@8b,&&&^xj┦ZJ\) Mr)yՄ&1ART('=PƤ-_ZS`/RL'"諢kGI CRO ےr r X!Z?gQO?JYC+VNZN%"!/dBkL7RVJe/pcEN) ANc:ZRјV)OcjE-,&'Kcr?ŖYf&bI)PB2"7:D,! BC,+sX/%(+壢P$\]W:U,/dkE-?d^ՈD4e"JUeZ.d+lahEVvB9aWdofJiSVL,mՊۿbIʊ%؎aXFju` "DH A`Jڶm< U1o\JDFUG5rX,YKcHra爀Yb7VRSm7 CnJԆ#B|x+Ou>>=Az W\u輮`%uEsZi#)vёOQS,֣ 5ɲ#0CtG9]lxoFM^hѢcvphF=`#<-Q̳cQka2o`y%Ȩ mxF$e'Y0QyOԅ kIYZUI ϏdLT翐=cJQ%Sj2F*A,& M Avq.PЬ(͘@տ$Ȉ_|Z8ؙ݅`=z]meyfЙ2#w,RKn/_s$&g>Beyz"Hw9b<wQqs}/2 1#[v]ywFE=^Q,UN&2sZp] W|?,o8,q߃8p =4M9h789 09cNcg ^x|kC 4X*Fqdx̷;مY j{,;"*aqY^񢡘fhnj&a 7pBE(Hzvp_  .s>򂧘oȑ L%Ne8^:Mb0G,'x=z.@9yL%xkwEvYvUT}b$v'q;w?ʰ"&61ʉqr&ۅw9O,_ cBrcyy/0dw#%/srh\y?vx-%PA= !wxg^bnDKygxz7Ġx=<ݾM//T xᢹ tA #$d]zP!Ih =<|ch$l /lѼ63Z(4)#͓B4,oͻdgzd'REE(}CX!lO"zͮ/X~w^3p8p̙3!b?Po!VmuFa8ഐvZMĔCmm&#f7bnNX6eXM'Amw>YdK/L"-V,v~AYSz*)-Qzؖ҃fX [)HIGϤx!Rc:{)F8ؠ-`2uJSjTSz5wh%cZPiTR%uʔMIقJ%2$ۢ'RUYONFuD-!oԝRl:)|ꔦfHRsT$U%͖3Y,I翝5yIM1&4=^J t*ܱK!-I;c1\hJϤ;9-^ o76&2&D3gΜ9AhQCrXiX H0b1B*w ?gΜ9s!~<( bP @"G ul>-jۈ<jlC#ԭ}l{1~OS1#rIR[B^<['z5፴B_#;Z s_7s g&/XixG7O> < \ѕ484Llxנ^TWvpǜoZhW<^ S8m*gtN8- UMsΜ`ay4!TOt,ng:ԁ=8!ZΝbfnޘyaWb{5#vz ư!U$V|Bcd+4iL_6 nE&j_NHOܫkY6\_\yгOڵfͼQ]y_Bb9QIҸ"Qe]OhqkOO 8!e$̇ENRI+bskWpPepKXkuvT ꨑ`[ mO ÑΝ͝T%17UƳMAFf QK1lJpfE6{]8?dl:k+zRͅ}Xg}{v\cϳ$mfUI}OUPN)7 ?8d)T /Ʒ/mlN h4]1bOZW˚Cw ->Ѻ'(]V6ً379ilH#:VKȃ^:<W [^ll3UK9)M AݐMOK6= "RyQMrVD)w Λ8~,:xLΓ&~"PcDN fY`p Vtq)0A5͈$?:{%ns`t9$árLW*;w^dJջz=q54$e u@/=H@2{|BX+ỵ; iy5|?{#qi] mJFepL=.r|R~L tDu*!F8P_0!F,$QGNXHF?f"y*85oāx!ħ$]Mt? 5S-/A%™_>nf)9;:n,~y;`q+7^_'"+0,oĘJ1&}A:\O&d&p^iG%8d6I_iAH5Jĝ9P:hBayl[,uh湬d +I&$={e?)F ,ʨȾXZ W~qӀ#PhZ?nj9$wy_t |2V;9-v%L9lXvw08J|H~{K?cz)%j/~@Ӥ1D44AA4#K9rGRi_rfB)`@nOi}36O=Z9_.=)G TAt`}zIܪhK7H7u&SxizA1C~ ʰ<]~Չig黝;%:ɡP8&ujHهΒR^V .JM $3}o$b'CVYxdǿ|E)7.稓%5L"bC94t*x̂1aH4#m5{6ToxJn%pt(d ^_1$SK5÷9J) x򬁡]Wx!ׂNa[j-m9$K Z6mU+!'g ^TbV)LL+EWPɴ@p 1:VAas0REGt?屡Ǵټi !t=h^Wg~ l|ѧ 8 nїQ>x%^0=1OƐ,xOE@'A |?OwGnlgTsv}2TbWv9 yVDjLT邥a,f|)LE;l68kF^DXmg) 5YJQgFTVE%5.i&!9H|LIrӡ:.vZ՝`In ISęGep+d}6#A&Ltmv Gvsx\nMKBHwlBC7X" R a `4s0,OxyO>^)x7SW.1*AQ+֎uiXb\ =aJYT̠M e9< *0Ρ@)id*bkdj2Sr/ow圐'!U#zyJ(}&QFfjzMm@:8ySBCw\o"r% |#&.IdU#qN Y kB?Z@h `jK^R`oE9|?ou #Bh\TM8 9(}^c`P䧰%5k̯MeEl²rJ4ۇ-qQXd-` |?}1|%2G>oh*ԸG{K텍(ނzgrZa/T^I.o i}1'9H{C\x^4 <\Zܪ ǭqty1|9,AW"Ncq7Qz7Sk[^U4+t<7ݩ eo?W?%dUjΪ>Zε/ Bcy(Jps:=CVlO$@p eeYbl@PM5 &8k>q^:>C!' z˜l8}Z!םќlw4?7~YrxSՍʕDgyFʕԧdʧAwf"^v.LUА\PA,K5OLQ^}OOȁ_8>m(94.'U7q].WKtFW)k) m!Ev`4IdɆ m/N *Q,׭m6o$u% Mq?)@|u@băs<~^KoPrqϑd-#Tb(xXȞ͒SeX"ct( 2fm-A1j{lR|jZ5S2f|ʞ ؄ ű[f"FPǀLs0/t̡vU놻u{r>7 \)(^b9b`[$,HCK\%gVZecE@kY$K (,=j蛥9}71VN]a\BjД q]K!ӹz< Fe̕? ^{aZBF4Ź &ys@ؘh.Gc+QTP>1PQrV( mY@*tdV[ >qD"_@ ])GyzP" Ƙf:61Q[(4 l3^'vq3v@/PqIo}P12*Z\u,}-cJG8%qA{1ۂ*\,ZBB*dh }F`OiPN#+)ۍF>-:Q2JCa9%-~쨘jBVWxS(PzcZOP(֥L:j tvxQNCRzb& s n4Lu&Oњf *PM*xi$BZ\tiB"}dL ftiHzX hߐh1gH-F` "gEF>>mWJR% %X5kr_XDT1L\,??&muEVDJb?22O{`DFΔȨ406 |Q/M1uiBrure0.-IO.-[bEX\aETKeOSCrAe@TJ}"Md'B:kJ]3!|:pR`\F(e7 `0/)0oa&%X)& Xk )eL@TJ}f϶:\oZr! .$ hnLK_z1>#<ߔMA$P̩?-.Q:Q"Dp8;@#8(Fk%mD.بh'O PR5H3x@iOM4%JiRjeYU+jW3єUhe.VJ~&nf,TLcNթSsiϒ>Tw$ؑ`'v 0RY/Yw;Կ~uKuƒLKe*YEw-֬J18 !3dgGN2[hDDfAC 0(bS$C @ G +ʕѸ羋;LŌk;,gS /th3/qqSB1]J0zDX 21>C8`on3 8Se'$Y7Bۖ`xnNF # E6`f? KRIHmۑ(,m{7R#i0#2r zcV6/\ZBMQF {z B2|dkc;iĘ #{M_$tvDA!=$ǹBk%)WL`'%CRs#@_x?sw)H #FilKSkclB)kniLeziS-ataDPNG  IHDRա IDATx$W't-eFKyz!F .[^`{2ӽhqt eB`[6HƒE/ނiQy%rŮ_ǓOVf|dDdFd^!=ʏ8q뜌|׽ޞ xǎ,{׎ @ @_nmuk @ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @cs눍 @ @@G V'@ @ 0f 1 @ @thu @ @c @ @\I @ @`o" @ @\@ps;k @ @uo-lL/#K;  @ @~^Ox׎Zjs  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&-IG/x8hK.~sgo\4twW߸K毚?>};;v,ǏwvӉ7ռG @I HNMH2uv/rrA`y\.رwϓI~Gϓ 4 @ @`Sl51 @ŋ^/t.Y7E$cgH2ĉ>u<)8vI @!$PU&l,<7^bN5߳`٧nb5 @ s 7 @ Ɣ[21'O~d_Gp:'@ @$w%o 0?_?N#7y\/𑇾6  @iTF,|_|_R~iR/ @ @`n? @`R%A>%!y+3e  @؅.m&k㈿f,>twuO @A$aU(D 78տ NN~Yw8| @% 8-&#S|o_̾g&Ё&xۧ⠓C+ @k$5t(rʯ^iH @ HoD@~F5kh@If^"@ @`s I yOBkiF8Ml @ &@鬮YOrO2{/nV @ P!``Y P-kzvNM!u`Y @VF0Wf-^z'>?ގY= 7.]K.͟LW\^p=vݳcǮ]?<6_>mgwӉcǎn:A @F/ 8& '$ʩ ,hw L=n~xqD/Sr:eR0 @`/^O yE`5s%!$ Ɩ\D_<1$EӅC:{ t;v}OW`~u2uF6H 0  i(  $2Bi(('DPc> Wg}o| 7M}OxS?GOkYvB2 @g AG $6Y?%%3rĉwT$-W7%$ZƥKŵ 6*9Swntz~~nu{髛L]ut2`bu,y2 @ + 8  0s{w"sz#u볜 B=gǒ벁$jJ# y.I$+6M,n#eLnޔowyz,7HeW,+h|I @ 0zZc}$WN 6b_Әy{,x$iX0ɿu#<N.]OY_t& @\WD pXjK#56#>oXFnk_Iuh\t\n>BK/-ѕ`$`ݗ`G @=U5T 9l7wF5]+0K7kh'wiuM ԓ)yX @`m; @`c4G8##\2]C_ܻ7xw^'^ߔ @d4.Fzm%6k#.Eϟ[dI03O~m^|2$ĉQ!: @* 8֖ȼo〵fIRΞ}Vॗ:O %!4ivwMw19 @fN  p`85?阛v'Oܴr\^C ԝZ @A xͮ@N zʵn:73;6?uh1˵ʩ}VS8e @`+^ry~]W0KfT)S6{+H6B HF LC4$hHb y\c[0(] Ч@~/ ډ_tL?i,N@p8[% @`tCށT6wb55H4#291LC{7H;5r7^C~ަO]Q{Ggaj4MH`keMS [::5`\w`Aۛ$quդLQKUqoر'v5yw} $UN~iGHLHog7>~7{ѯΜL"pUO, H.xM=::k_= `䝾ЇKCkhIMeTŋ uo2?1"$)-oM IDATr>ڤL>_1#vZFa= @HO[ j<7ŖQC] p[++f“Xs?ﳸ/+)~ߙ6~l(}nN >g/:CF @2@$@ =k'O~d-ɔ!cH>箘~r_{ɿ~8B`'B4 @e#E&@ $EOB닱Ly.TػiÍ@> /#'f"0@>?moTiLW\tU $ f$`WL @H s0\i3ɬ]3*] 8#$TV@*JVa?u*#@p 4* @ C[F\`ȄN$MM Z וWG]ص[  %0aztb,S}~b]O}@2ak+'@@+W6 ǵNwrM=C @#}q="  B 0~!ot?ڇٽ曕Ǩǿ?g>a|nοv҅{Tr*g?ǻ8(Ts,,EipcxM!G6i :1ӢuU&@Og~},DN _FQk>NnEK @`#:ݧ'V_|/IOy0)| @਀Q @` ЖVa^mrMz/g"/ʑ~MX>}'$`F! @0\0]  ЫWsJ3tSv^&@`;9wim$^xM%@=ܳUz@ܼq V?yCu{PE1 @@C$AȐ3 e1ءm"ƥKUvV 82cvV/&@ǎ_]!: @`6oQ`9})ݺPw~LT9Twu>r=>!h3Ne @0ø*2/GW)4YcHc>hﻮ#@`81_'?i6?\?R2 .Sh%1 @@I`CXӡh#N?M svK/d&Is`IOkj6;?}mf[Z@p-`ؒ7:%lfhO)I>Ow &`M96Y:'?q jM'Nԕ+%˨ 8x-/?>h2Ϡr,Q}B죯Ө,E$gLǏ' @6:Jo #,ҏ~_Dm }m6 @`NG;[4$%5^cHE`F n׊&@ 0n q Ы@&8dtTs=)hXCۃX Q: Hv:$pĉ5 96H6qױ6=٤^!@ 0 )  ГGOYO%UsW4֡!FmsE]wy& @1 HUDr*Qb[j3ݶjSx/m$nj~S#P3u[ @$՝ԭz'k:/\;cwNn^sEUKjE[2 @`ɭȣ_&[LOzx&w)fkPO N)8\D) x@Fa}-uo\A@?ٶV9 @Jn:St= @^c_ {٬*EI }j%Ent۶tv휡A)^[ @`xoum  0J Q6 0][>5$9xӟj/gI6ukPئ,K! l C6VwU@p-#. (SC}p5 [t龷~/f+6lzlSNK>տke  0 |w5__!@4$n&@@gs5?[f|֌9wɓ&oosLDRwΏC&8\ m{5'@vyhxѯ̓dtZpɏ2eGۻ3'~f˃)@OW\$, ŗ؉P @0mO|{g;oO=)'IreD`;/1#J={ ڲӦǏ]n <2`2}]vmU%x6M'>qV$@ kUPT $NٷO+*xWߙ  R`b z8Q-xjm1$jls?12EZॗ_u]7G7*B=p =o`#@@@X\p]ՁICDKrQ&%Y=njS,֝LM:w N"iuItegI2>;v}o5ƴ7Νe$v^ȵLwߛ0(z0GLE @`mF:T[?^`O0{Ӡ)+(<&闿-L뮉uAG'{~+tbb> $)% 2b;M~HO|[aJOumcˏ}N'z1*0pX_ @`mGJ% Xxt௕<`5ImMl+$D79.q1ɏU}nS~Og]7ڬSr${U%iZNWUuI|Ō:us5mƷ~/zbQMf9حuFc;?Em@9Y(WGyWm_\ɏo79tL?,5}6O4=r'3g>H%I+eTf9%2o:'\`|2z[>7>;l-מ+my?ȝuVZ됖Z"{1񚾟9~$۶  צjDr`iG<{G{@?y}җvp4m.Y75~):SK܇x~!kr]ha&0i̍xkmGU$o"@p a @dRSrk7yyN`v1)ѫڷH>R=yrl+5KD #-M/ @`oC5 @@oe4&OF7$q5 Sv5 9JKzkS$1u|6Be,QGD2iJP`~(ڰV#@  O  @`6ߦ n$_gwF̻~Si]lԈ+%;G[҈f}j wQu:m;U;LO@pzm&b .n`.# Ͽy"nQZzӻ.^}pHX\O/r*pȴ$z7V`\Cf(۪9]l;6aue76іc "x6_ה9HݷpzF|x[ȢѾywygu5]1C@p Vc ~0smXFE(սMz&23Ok6Nq"Uܴ97& ( 9Yێ9տ⿯,ϛr0DˡN9}؛Ss$s_'ߤI`~L*0O?7i.ϥ*L/`Hع@rlsM\,71m.PvMkٚ9E1vڔ_F5Nu.\~ ciw_ג?ǫ_/>  @`j @@(^-I_Ï~By8Y2'_ج$&2j0würk^ry<UΤ%/}*wʳ}޿vuҧo1>ݬ6y˝yk\IΦM:w>kٜ˪rZrǔ6?>O}dm^R|$YJ&?׺oL׵՛漷N;V-vͼ$ϖC_/g3?%Q׬ڎ @`{o?&/#v8$ O9tMKugNL0 !@ @ MH,wh\tU @ܖ @@|: ͈H 8 @ H  p6'6;ӂ龩QՒ @$*b"@ ӂz7,ukJi_V- @SRk{"p;ȸndZ;v}Zرwgꔻ& @A@p @f?suw 3$d\w3ĕs-l @6xצ+Z%P~q=٥K$[_oR7;v~0{f9SM @uoz ۑ^ @ @+z/vN1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7 /t IDAT @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T HVK @ @`n> @ @1 @ @$'|'@ @ P- Xc. @ @I HNO @ @Z@\ @ @t  @ @@` @ @&- 8< @ @j js  @ @LZ@p'x @ @> @ @छO @ @$}%@ @ 0i I7  @ @T z @Sxfoj{kAy晵տg7I@pL! 0H 70nMxg?.ƌ $' D8C #eF˴2 Weث:Ko]OC;p;Fa\%< @!  e @`r-s^.#<Ɉoh^V gξM-`F/r ?>>t鷯 @`[n-i!@(\O(۽=Ԣ9:Ň~xLxF5HMNYOsZi/S̩Їzq9 /0??n%RWߘ&SW1I˗/Ξ=j2 kKg'}]OAOoҟWV;c~Y.)ovc}b+oJ^kV>~c>>\ɩC^h^o:mRlI~ 3~񻢏شnm[O\JRN1OTV琺f>\uOxo o/m"@`/N>l6[wvٳgז_o7|oFV|vl],s3w 7TamÕ/GyG̼)_|jP^g[m_pa1󺘮Ya$oOR|>܊c}h~Xs=k_\짪JyMwܱ~M=smON)M,fybLה(Z5Ŵj8iղދEtn$1tNggz=#Pw{}A2m&|sMڲiml>j*t_XS> i쥟:K]ĖeJ[V~v&},eģ*mR6Zil[X' NXiSmD0m[jO!]Z.N9Ȯ:m,@v:+-[nq{7G2V$R@G ϥ|FIRK]P>WIfc,3MXO4iRnbݤYXNu>PIe_P>XvKڭ~Q>}w}Sإ.m= "}NIjlzYGdDrdzr]Cڭfy?ۮ|ZJrSuy=:.uS>_}5_}ƙܴ߷v}sXI籏>$]E>C|l򼯺,o++?yC~l)6l*  Qϑ9/rúf!9 q@ T*}D긋ܬj¹AǺ&#_ӟ1fH<}%bJ賮}{r_P}Ӯ}392ϤRm7[eC|c>*.Sm9q 0y& &wAEuSFfN)3I9KfYϺ2?ɈDI%69pB;p!ULٖJ{dW~ls@|̙E{ĭ'{ fBڤ?M,%z7uNM&ll?vԵgiU^ܚ Sm@>-vF }-O/4uSNZ׷J9%e[%6ͺIDeX祼!Km#m؊iw{ԽIo+~:>>_0 J@pWK $V5Oo]9Ν[yg ?$R޺).uW(',00N/bel);gOdj22/uM\IwJKJIl]+%.(s?/$鵪-.[se~Uɵ[˗/GF/oyL3X.2k+I2/ugfM,c]5ٯKfS~ }n=S^[,7$&}j9R^P[Rždb<떫zվ,e7!5x]X>Ӌ*ˤ}~>1Z5{[n92+T}pd?XrI0WMuĸO=[pvMӻ@of{ok.m{olɔ|ǸMMVʾ~Wꊜpel)3曵;w6v?xmykchS+cM1_.cC=Ԉ2Ɵ}ҴWY.ƜcO /T^p&MMB-)~M?d_V7cRV>MLgΜiRM)wO6XlV=6mϪ}HXک֪X{Mc*۫5_XW>_Mr^ 0k1o9kN.e+`._~OK}Tu B,W_5`]vrnQ"%ӢH,uvnB~Z5R1$VMuU=/;rzW6IuU٤?*Ʋګ'mtgDM-c>-|fҎUHK;WMO63u#plnW5R*uR׺mu쏳lFok6,_՞UV}v2bjjʪWU @`Sm9q 0IsZuR[K 岭,_u4Xmzr@.ICմ]AYWw0T!K$nf_ʫ yԵ_Y7Ho3]bj).[/H1uK<\ުuul]0˧7֝bZ֯Kf<6I8.6Jf|nkI~F]X/t)۪s@>6ZF˱ĺ)| @`SoA K"/?%R&0'yl.7:9x]V\RV]/.uru^SCQlgʽj4oKܤ.MUq&xǛG51o~PJB*嘲loMbrܺDlǫTeMu?mڮ]7}׭}!: л@2/_F~%fJI59Jmz?Uv}su1:TY|w.y1-zz]K֓MFpI]bzz~վ .ѪZ}$oPV_}"Ե{rc-foDQ¨KբKUKE]r5D@pOR5S$WUkEwq[ꮑ;n:%yZwJS~gaDT'yU5KW>u.UU#E]ٙ_U~Q6_]yhZߪӦu?ww{ՏSA~kܥ>u-I2uQUUXw?ۈUUJ5I1FUҡ&~köI{6O_OdQUI]OH.FC{L*XUcUMuu5Y..wu1cn]u]]>_b] I HNM`.is^AKunMzc~ϺK}VWF] %s`6)nT|<M2P׺>Qߛ1rI&k:净CIuuN]?( }XNg3}"_פUw\z[uuB @`@k{@q4*m]WZw=uU!^] ,]oðWv3me#yMDHAyreCOSiîɿԳnbHl3wMl$u{Si#@`n? 0q\eU&>)3ɏ)pGO0wq*, ݘ_n &О @$FmC@@w̙*eg?er +T.c&6u1'6%@@O}?%\ΠnMkc%U @`7qUD 'WuMF=~7"pBeԭ{禱xL+w]7C1ǘTڡQU_U2.eF>_}|n prеWylAHf(i9zp]wPW5B`$ƪluEmX IŪA٪un^fkmo՝V7rYQê}Gk߯~hRd?CٯS/zgWk/ ؿ  pD %dwsdSM]EY꠮@+tJ;V%btI uM,-o.XSiESu@Ky=WK> UJo8XCJwh^UoRۺy.0T|g?mmj.U.A@ZY ع@E9怳%u]:omk5r0Uu@UuPԕSrIky;M^7~YFO~ڄbu)5U#+G~ u彺}A~ h(v1Jpls_Pbcu݈74H&>GķIz;"G<ȁd]ȸ$ AB躊~xaJ$ѦU7b'uK2Ŭ`2=I"n; [&ןA7ܺ%a_R\X^|گ*j]Wo[ VOL`9_ ʾp8X{\ cuɼV\ަnԩŦ̪|V}nҮe;|" x(-\ u&6)/$WJ\Ibu+}(ŕmd4GGne!kbe8C6)+9mQڽI[cԫ0Qw#$ǫe+,7iQکCڲ|&ۈn_~6DU}lSFIkgIbmѝm.]8j2U&,/S߉y@F i6;eW= @Z kMCl~]yg9ar'n$XMJms0|`>pqA 21I&ɿlh&ݪD`9(JuI%ژY6guS["%uJ~{'i4=pMd֍Ɖw[5#q&Iдԙl{~>:-QO+LZɾ nEթl{$p3/dl|ӷc>)MKI6* VkRu"s{; uYLK:LLEືS v qxǎqok/]L?^_)?'dnF$Ӓr}\L|1VU믿^{P j IDATTP9p[&&5gƒT]&dr.$>K1%ƺ,Ae9L[Դ+U6[\6VU>}aq]OR~ >4wNm/i/Hw>i]$i6qRWKU=)… e+OywjZ55+Mk)wbe~Xڤ/ns/z$/vnRc.zȨ$?9Xhʬx6?]h$ 4id{9p;[.'uy'^Um\~ye/q%aQۜPhK\cMgW72m&enӥj[i6骲ʼ&Q,cH6I4v/(Lys뻬iLMSV_iY..IBWn[Y.r`Y1?I|s$vU @`NZ ɪ&5t?C6S&ͺr?Emk^&MM\U)cUB!푾T›>i7)I>?}5rOﭪ[F6m:j|Uy^<|}QuHY}~)/CMC];r  Ї` @@K$GmG%l9p"w]8_tʺ=Xm$vmO]ֶۜ,_̦a6cuN|]]b{Itu_[]vu_ɣ|?]=>}S9}.Y>_|ߵ}KB@pUe @F Mڌ&v_$r5"]/딲(R֦ u[?x>T_{%ص$ 󱪮_tM.*{ K6~ӒNZ-}7/h'ۥKh\[Jl? MY'>J<ˏe~^짻NcZ? @` !TIG@ ^'h';W5=A5kI'总]R׌,{^{- d%g4ZV)3M[,imy\":166}AI6/le:}A}쏋&DxqQsmJhLɏI6R\sl/ dUI;,eh_*  ts`'@`x.\&1p#1u0ofrguSrnJ3.p#F֕q&O믿~z&9f\q=rP1~e-~?i˶bu6J[; 7O]ӧnZ?/.S+>[,wSC7X5DR&pn*O% 88$$l^>o[R6C#궺  @}ȨƩl׶Q/ @m\e  @ @LL@pb &\ @ @m$hY @ @$'`%@ @ F@e  @ @LL@pb &\ @ @m$hY @ @$'`%@ @ F]m,fΝ[؍7޸v p8gΜ[++ П`J"@l{w;*I',D @ @`tk @ @O@?K% @ @D@ @ @RI @ @F' 8& @ @$T @ @ HID @ @? ,D @ @`tk @ @O@?K% @ @D@ @ @RI @ @F' 8& @ @$T @ @ HID @ @? ,D @ @`tk @ @O@?K% @ @D@ @ @RI @ @F' 8& @ @$T @ @ HID @ @? ,D @ @`tk @ @O@?K% @ @F @xyo=ܳO?={뭷f7pܨ)vv}|ܹه?)i~6{WUw귮^xٯs1h#3?c[C;hNw|?XlS߿u/#!;ڋ DA"oOMncz"&Пu]7/W^٨|\y7*cJ+ _Ûo9'k9H/Ar-զSw3wIMC, ӧOϫo1$Ӷ9j1wUoY~<”}gtHRgYHoڑ^T XcWr v׎)oɁM/C%O]$" y4l6HgqJI,7eW//?|}0+#'֭8?# M[ B.\6`oݫֲok@u3"%i2*<.ajTK ?t" - 8tiq4_Yn{e^F.xNi$y]$X&k%QS|WNx*q~8ZhJ$@[Gl[(92X A\IV-A\_uз^m /unb+۬^vYrJ٥~Dz\ueRN2m*鶪({QFSL}%6xGm]wmS/#[|M>.wl,W\a&lolW>q鳼RVz6z쳼RVʼl_(v},$m:2VU)j^Vߦ4]CN)Sץ/W}XwU8 CiiX< Isy|$JҰ|-/MG`=ok2%RnIXl'f̪)Wb[uߋ6?ϾcΪKRcyޤoYOZ_:g[e*/.ibZ]|Q^_UN?T깸ĕ-U__2T~%X^{iǺx֕Ӫ_S#ur1).~excii딓i]{.[N{6]gy\o66~^loU[emG&(5Y7,ݒ$cyywϲKrg{{"Z@XY@!tKZpxVzϗ[>OE'~{NmVN6jhбgG9>jؕ6?]SV{)SWdyvm9\,`CL-'D/F҆zmʉ\'_delnoeD)m׸6ʡ,~~Kl|_Ԙl/e/:^imCʶ8l\KKb?zcs vK}ysa͹oa;)7Ie;s?J'׿=YgP?W={_tm/^xn1?7<?|xeAFmKul5KWw}>OGl~xo;pKG}@,nLgVol^gK^t2PGF߹)b;J[>%|mOJok=׫/Rw]a{RlJ1]Oyxz<}Vov;bw<5>{}Y뽔S':h/_M\6ʩmeږ#c tUV#1[Rw}cC~9jSGydOM5.~ugoΣ\m+5O<= F>bߖcVQlY+kh[q6U&w׈k>w+:Q鷿Y[xu-1[t<=vyB&w{Ŕ|u:dʩFrwWYӟ^zɓy!??Av)f~Gza/Oؽ%qU`;r~瞘cpBwN 㣀M$tb-w}tMEؖvK> RM6.ߩ+ x=<Ƨ|߹ ?:6̟UمMU^U×|xd+Ws3YpLBJJL^lW_OjĄ~|c[gU:z;Fo}WWo-,%^$tCկ~z?u9S]o[]%Rʶ2N!篕60l~z'38)q~߲3=YI ?ԅ/WM䝳eDŖ1H83F|`!:ҟ#Rl^XtZYy܌%c'mիWVP>B| o]cWG^ u-q=dzpܑBVLtt@}w>wԔnf/g}!wzuR(u k/*.H?˻믿H}5_nf=GNˈ^=Y>|]jd{Go_S_{=ʡw'ڗ'zr#y>ޑ* >GYȫmv,R~V9zWgS|vf6:zD̟UӣÎx?ף'0^2c;Wr񎼚r%U{{p/b~l2çW_W`d^۠GU.#;bgY\O<QO6x}JoLrkĤ\oq'6{Ąa<7y{j%_FIOA>s7hX'jp\-\-kEM[6y2 vL_g` 攭m@zQAW{\'[*7F&uU{: ^'G|U^\ՋǙ9 V~A'1{zӋVR|WfKϿNȨ/qc|ǤT/?ןγmwQmڧ;N=%_VٽH(k{Xձx>ͷVu۲ueD|fMՎ5^#sk{6V9l1C-&qOiSRo:6jCo+k~;_ŖH1fxzm;#Vuf}J>HW)7Z;m3#DzKd=+a2>WF6ZǸVˣתUO _}wQO$y=P^aH-5ZgR-cɏ s%tzUQ5QΣ䟥ӭ6{Eʅiy5oo5J; 9J, (+xm~|2Jԟ@Y^{=(_cׅjBp/3jWޯyԋm~pS ^<ֶHYzWlN֟?b [uv<Y9^ϊ{^'Wu\;zRk[乭i|nZefZ"7p.߫o;8f:ڗ~M8F>s=AdT{mX%3[^hTχ,sNվ^};Wokܫ+76|g7uu[{a\>3Fsm[z~{[7y! T/: 2Yx `k>f *=zA*GW8ؙID[oض{:l//z+ezyV嫋mzak9'ۭgoqѹP:;Uft&_Oٯ^K-ٳa1 ).ޣV٭|mwm%LNIܳ7ZYUʸCcak+LZ]n}q#j7:>)u:^lAme&R6lk޿/E/dkLm+X}Eo5nj̵m{|s=tN <%-u:[܁ jDz$d 2i'mRkn+c)[NaU^eRUW?Wʷx5?z>W9ߋge$fS.gu }G7Y[wgu\\@J\\ҟp>Dʶ?~Oj#3[o5^/h՞UڇQGm+U[k*sf7eoU.7\;6\m橌k_Ry^k85vaCбygyZHrs86[+eW`1,zV+gdQVo_;} %QLK, H` یÒ FݥX.y'^E@Lٯ߷pG|8n:L<b#=ޥu&^߯mԃ<1v o#{`OέrdAs?3Ǟzj?_fOt朼{[++`5QfXsolv.kV_qyz-Wm-1^鶚W;%h*C'["L0и(1Am ~ IDATԧBx甋:i^JKM/%Z$WXjKoNډ6O:䂞sh~8$#>&œgϪ9#OQ>I<.nW.BΩ2艟Hc{ 䋿C8ɹԧfGrprZѧZ9uoOC6|{xSv9Y}[2=mw3?b;E׌\dDp*W8 '2A x.8wz y!B.)ÂB.)3(RޯŎG7lcrP':ā9ЕjO5_jׅ^9Qlj$h+[$}m[eԧϓ,^nO9?k3|T^c'9[8'Л6x>ۣ[!C?UfݒWoH=.n.gHkK{*k^zT9۾|wn7Weyz'i%m%:6l핿Vږh3ўyJ]GγÌ5RWOUcVnnl[e=d3(< 'c\Rcd `.i/~ 6%:b=F.b\ȄSs2kϥ-?$_GeNP׶.rg;suMAy.F==Ng|C.XB\F.~m/?m/1^ef5v"mE +:~ps32{WncUOLf PӞۈQ_[d*6†K15|KR Fg|Ν#o6O$ӗVȶܥ︔-6 14k_?X&.r3N G5֒ߵܜUC33[ S? ~V={Vzc~~[E^(W_^/_>7t}W._~-}ݒ>]G mݽ|襪G5f*/U;:lzmE/M~ 9OwjE't)/^,qBy{i%&S'{1ej\I3V\֘vT^{ϩ pj1&)ͫl7K=ݨ;OM5֩we#>gmaG߶EǴVSr[v}E[3G+uS'me+N6cEF^tJFそ+E/yޓMLٸnGԵeWTfq`k޼7qWLj_o?lRO7/kv9՞Oj5mtVk1컖+|{5vwrj,zlV/wyzz ݽ^]1[\Dͭ>w;/TεDgxO,{tw9 SAfN׋!Fiԋ%.GmߴSnh/4-yEy {㗊؉F}:~>>aG1&KA7CEi#)h=+fct_m=Z K 6ubvيsjԗz]}m [ Ts`4/)[f8WΥ(yу|0kwsO.%xU)ud}~P?"=<202p]J7.xk(xRr메wWwG'~9۶}^  {QcT5cm%+g5+c\ &] rˊ>e[ʯrn.]o l9 xRp뿤K@$  H@%4 H@fV<ǫOab`]{yj$  H@nA[P H@[D:ga/V8s"75')a?!*deaŹ?t2I@82 ȉ񟀜$V}%}7jh$  H@xGO@9'$  H&B$  H@88 z$  H@$  H@C=,+ H@$  H@$ pR= H@$  H@$  !z$  H@$  H@ xp$  H@$  H@pp=J@$  H@$  H\X˂-۸CTsm8 k.B}mvI|<-M裏'GiI ܅Vw1J 7y7# Hl.΂<pmy:Yb]Ih~쫯zN%k,q+;~~^n?'//5@Ks+w\hpÿ_Wƍ^`$ }\8 ^Ȏ"yS.uy$,m?p3K@ H@x/Oy. Y' H@$  H@|p o<-)sE&^} ]ʎs8R[uejߥv^RK@5=eD[ᶲL۹1pm#\[Fr{u;E何.}/.2zq6;vi>w+6ǗN:R>~{_BNl˭*|S7б0'_׏ۏ?wKO^dQZY?oM%=T-g{szV+2:#2?}EvHًC/Z[aclg{2?͏Ng? K^L~n+ߑ,Ѝcgܞع6WuBmcȊlWe8 X;UhkؐX冾nʇ^jc9fSftf;JuJsbsT "7 }z]v5gTw.§gw(l~?T㮗Gxz_2nOd%}%􎜙>iԗbKvLٓQ}q9"b_3;LQȀ9:N'v[YT_>V;fcw8f^Vimh˜=.1l6^"1E"~[-ն{0zzvǭie` V%x3| |?l ?}ëW'C_x)_ӫqT&DZ>?"y /,<fe}}=mZaI}ϟ?o>~OpJI//2|=V lG~6-9[(KuzWXߏDC5J{J5ߩOco۳Vg'w?؋K\Qָ1X7p믿ey=ppWp`'0nW?R]/o/ :[=zر'[ݶDY}ےZUQzxߊx\}mq~Cݎ+r񿖫||ߓ->UY3:l1ؐ2rۇsM+̐3ϪJ,Qh.Kq216&^򽖯#!.=Z[:ߊbйևMk1컖+_~kr]CC'>ӟqאNJ]!y<:v.(췿x)G6U> ЉwO MёK&˗][Zz);ȧ>k/w^x+wkѹ pRspϱj'/~’$es眻MHT]c۰`Oic|5Qti/FdT]7WF9?pX> ?RF1Y[[CCS籗kݪkLjeb-yyz'iWC-ue'8[tXR؍MpkSBp8I-䐗c#w/ɛ/Ɵ9/d?Qބ,VR+!Vs s)x N3>y7Iݰ3O3X6:#cXOY]Nm?so^L`{f## ckm}嚀 L2/UȓZ|_`6㰍Nؔqm,[ \1=m+_$5oR}X)g}oIz;J3'㏏uR(./w>)StsgwG6z[gB^Y[|}e]bme9Slۊ7t OkG&Q;ֽ';jdמ;;ȭYﶷ`ڦQXk߫3XWmJ %'~K9ϖzZ ?=Q0[WmY|'bzT~_~eN=n۸qW#F\QϨNܨ^~#9 ~a R1R4"^jzȫGcg#ckg}bʂ{'v[=~v̶ټk;Y~c_2=U7җlͻY_[YTz}\epN0L iWz}7GyTY<?Kyd<:0K iuĦׅj=WI;?Ʊ[혘H_Ps^ꃶ=\W5kvqgS٭WkmeWۖ#Jo5?$tr.Yܖ}Y1#fzS9>~zg 2+ IDATKtI[v7m{>Tye!ϖeKNߊs:zs+1YcM]&{ԇrU956o$ ?31GziLL&3Vb,m`&~R|Uʌέ85,Ge7)s-OtUVNY;k00;_^iջ;1'jjկ"BF@5ʳu:g~>[Uʖy GۮvWۭyVT;fc%kٜەҪV=-loIIwt jw_e-: S;jdk#yɟ()Ǚ<(]tbcd== FÇI lW&=9Vme=*w1;&wÊ}؀LVZZ"3`Ƈe˯ʯZ5_+KO;̔61yZU.{l&xdO8,H+~'ߥl:&Wl&(1_O+5xl=Նje܍>ٮUDN>q9~]qsOʵ![NCsS3 mi✘9F+y"-&uw^?O#S)eV<6aOĖB9dx:t9eR9m;i#$B?PRwMsb(R兎S&}7-h<1xt VmZVyRrRm[},{ܔm~0؞ߴu]U+}޺h8z|Uy#jΘqJ<8?5%>N-g~ UϩO+[|| *EHֿwX$.уy.ց/f򱕷Wc]_ΓY i ˪j=6&U3j/@\eZ ~'c)Y{=WjjG|gEʞ7i_-n/")؉Gmo~eQli,c4%{q}O9l8>B#< XtZ< kRېڼ::vR/oVi۷n;cc7sF~)r-r⛶+zk[y|G25qk5_井S'?y|YʓzN9Ǡ'N7:.NMu"3DFnS5yꅋ@`lm5fNjO'hژ6xYǩl>UÖ >nJ?mfqy>G/3tVsӷ<3ώ`n|`5WB>ms[:lCzwcY1VZUox?ø^T6S_DϱQ}OލȮSe[maMT=io}&s=K[_[jܝIsv#EKlWc nYLѡd31Qt-{?.BFu,.Ω;ep9-o6:ڶq⢧cms[x4|q7udKYw(W5qA097s궝f fwǣ7i 38nU}Gk_Zvl&#FrY([%H6NGr|ktKN \Ŭm|}WyF;.Hʔv=b :mՋ^}+m%Ǫn9o ?NW]e\N˫WW"7Gz%[ _gCy>i7Ա5)?oXbwdRW;.U;j>ttNL|Ib{XuE'SpVXO~3fg4DIdK#/*űV5:qݐ[㻍kcgįSbYcYq,Lކ@]aߵ,X=i~psk`@` fJ>Qj;#fcL9?1l|bcj|Q>z >{SʰeQ^/&Gur3bV8A^Oږ=_U]zLzucVݩsғ]UzW[cdz<,z ~=VDfۋ6&I駟>{MLWGGgdq)'V}uѳ)F jW}v':Y_XƕY:؊y8DMm77kmY_/c|EP_>3*E<:^X8οxzcɞ<$^]uK?gx#8e]+mȇ ;^ )h}J[Յra8zd%T gĉׅ&5om{&̖WxSdFhK̽lj=61x׊j9W|ݳ=NVh| 6^>EOB^s}==^{ >>k2Gx[_y]g5EX?kp̰>l=1T?Alm1:{1׫'>2Ȧ;'FΨO>tydlVKՕzRȄV_Q_9⃑?n+D| #ce^|!2؎N׏]6w[ tlMU2AT]BSu߹.ڸ )sʾ_+=Xu~oOT9{շWVR~mW'W=z^=cCʖ|YYWjcU:$XL>ÿY+:m7Zc߇O9|~q_ ZLܰ·X $  HXdHbd̞9JdQdkackz:F XP]iB^X_GY zn=-x"f$ b%&515$  H@&n8 Ibs>,է8?;z]x3ƢbcA.@rC43$ px>Q# HTz?@EU$  H-#XxW,ԝE=^+΂_˜|#"D'E{7yzm/$ 🀜 ȉ.'F 'fJ@$p0,d_O};?Ǣ#R'<կ^7VG6 >>% ]>& H@$pO܌ I==nKr]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H\._K@$  H@$  H\+~+$  H@$  H@u x]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H\._K@$  H@$  H\+~+$  H@$  H@u x]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H\._K@$  H@$  H\+~+$  H@$  H@u x]J$  H@$  H@] xWV. H@$  H@$ p|. H@$  H@$ p\$  H@$  H@%u*]$  H@$  H@w%][$  H@$  H@KU$  H@$  H@Jr H@$  H@$  \ t H@$  H@$  ܕ wo$  H@$  H@.W$  H@$  H@+%  H@$  H@$p].^%  H@$  H@$pW.K@$  H@$  H|nJ$  H@$  H@E'Ez%  H@$  H@$p.UH@$  H@$  H^\y땀$  H@$  H@ ׿'~#/~$  H@$  H@,c },- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  AIDATH@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$   vI@$  H@$  H`$  H@$  H@MCG$  H@$  H@$ YZ$  H@$  H@&ݣr$  H@$  H@G},- H@$  H@$ CpQ9 H@$  H@$  #>~$  H@$  H@ xh$  H@$  H@pp?KK@$  H@$  H\<{TN$  H@$  H@%  H@$  H@$ph.=*' H@$  H@$ }\$  H@$  H@84$  H@$  H@>.gi H@$  H@$  ޮIc y1IENDB`1crSDSD00crSDOfiT#Fil(/Hm/Q@RʒćW||vy /@\k՟o)Ye*=h!+/OB8RJc6#"|jS>*pa«*M})BkGR~C8gnzKjb"~>mعdܕ; I~9=sor1yB=KƯ "(e:c$5u!f!aF̰ULb(=2y*]X$&;fW81SfV#s 3AP"Eq%RU3q_2&'p12*@I~$Rl L'򍵢%1Js熟&x%p.,02H&h\꙾KEaLoK3DdhhA8+ $>npܣDqsMHy HPCtg y!4`?!-%M"Bļ+ZjYSqÑUI-pB_?FJ &T0\T(*r_ %)K7ׂ!1lK*9Q#`&"׀ØKˆI(<04NyrDMrPS۲劗'mGaHA^Fq=zzߗk<{ȊFT}|HA) +Aw ,\8)IJe\Tw¹S`- 'eǥ-t zЏ?TAՏ?\ Abk &Q 153Qi)N:H˪Қ4F'fZ߇ kA+odLeH;(+!JҚ#ȰXbhY}jؒiN3Ӳޣ]\D?eUnךB4Kw e (̕ȊٚU {@P~Ι&PI&ZA;]؏ӁZ_t$NzV;嶚U+}#8܇ibϝ^אbbjb(;Ѷ->V++c{erτ8Ѽ'N !x%9vq,ۤ!oQͧe9#}~]JC|~ҐB qC>,fnZ66/v3_|~t spP 0Mg:?c2^#IzL۽2^/W4R(/^t_C=d0Tcç~>2kW PRW;G|_C}vy8[_O!|P(D-wTݿъ~ 5`$|!r wU(dž5o>y&̠$]aXԿRm,:xD83-̑{ÚOYluXQ+CV$ `̧ B@s,P&xSninT7RwiL[ "XҁS$܈,{R-HI6$AI7IDE+_мS*2Xmk'FER)^`[HsYCK]-Y*d$ H.ܔXt ħ Aۖ?A3ź \4Ecյgd))qi#*|X42FcKsB{A{274bE#m0QP}^Y8}Gjox ~("]I\đ^,c߬6GwiA]Z8 ^]ZmWf#WփnW\?R@pk~gN=e'#Fldit,?[u[bD>BG27\zu^>b6y= N7*VRe #BռT7V5֣^ iysVc. d ۮ(:Սeߗ XpPBGlT7f,уIJ<"'X2U1"_Ej]zmwܵz%.$"k;E}]BԼc!~v G_x#SoIz}BtWP8 'HԉFHutݬZ8(ϖ#Rƌ1ybUȋ9RvP%%nh#:7$/]=j.؉>(Hv,E鄬Vx|YU#eԼaB4BCt`n"%.G o^Υ<{M߳\t 3Yg}(bcXѪIFj8ۯHG2 X|Q⦬ H!ex>MUe)tQ\4y"Zp?,)c^]'J;! GӤnkE?0~:9ؼkB)z[u.T6@wi,4Jh2UZ] ;y9iO0ʎMoTwiEsJh%rWw.QIuKR $(w(RLЃ>ߴw(7vГdUsBdY5SQmE̓@G6W <5c6f⦨k ժ wNUۊ_M"u|bB6!jEPwiA]Zs T$u#2H  K`(%I( !cP'6 Ref^Kx/Gڹ"]>Goq4M& ƞ鎂CQqh1a9$?7t!n興^xp;ZH;rAqe4,j-F ‰i:} (SXv3=Emu4jwe]+O8I8R}hYP|E ݂:}RB'X&|VMD;mfG,-l$09/rdW8xp:];ScT'~38RM` 3?/9X3h505ߘղFn)?̀60@~CcI{k@7FGwQZ $,wO"Hy I7PS뼂QRo}@tf>>Խ^뎒ޖjU>1~'ه"5:Y}=i]x5>&X rFgl޼B za*Tqu9|`LD^`A[9\FbMw9#?RP0y_ 2~ZZ߻tfCRf$ f3` 01h/! 2 `\cأWP:_L{4YCe 2pݍ aJkL @-t_5r} +@"f%lyqp.&: Mnm6K6@N (>(39Ȁ_l= q9,lW p+V6ލ+aϠ+{eGkLUAQWFaOP8'.Un@H (3P I /`[wqz%I\%AՈfj-1{IPġ ( K81kANr  ##j&jpyb}M8pp8 k[3#Fil(/H%j]HZ@HYW@?oXln(Q^4}Imeb0\ڑc|ޱ+~y 6EŇ`Vs5/FY#I·ѯ"u*< zrDMkNLXKO-ooFcA:J¸(Ȓ>$^n\a\`vc[gWpSu t۲&ſ3+B;35[, P@3M +Rhg s+TAh߿k>*}po۠_U?a'BIҐv~/MqE ԥl-V74GjHeﱐQܗ'י::Z.oNc;;+}~̦ά3r;:S]wS儖x>!dUJ;]W*[m{Zٙ}C8V}aϗLzdTAE|R$m[aEw cŒpG`Ğ1ccc8%!% SʞIN{zˀ!=0ZaH&(%Z̸`DLD2*J, @f$AT%$Ђ0(8p4eU '%c+l0@u`̕8FJf3gP Q@ j@1-bp9yda @T6iq06?1"IXk7*Hy jLY 9`< 8x!)-XrBk4v ;n0 O$b(p <'tЁ #cnftA p10@Yt6+(C!33e%5pI4j0@xXc(Ne AEqiN 4 X 7IpA I/p8" afVc*! k p¸%lX ܀]0D#S2){3fq|2D 1 IAAX({uD=8#LD貰10bZ!g&e7*h(6mn`rB+`q 17e#[L)h&~vYlZ =t$5UZ@) t0…8F|A1'X tYjt4`Ϭ`dI<@nlj b& (D%*\Mrp\;Y rϒ ?BIז :h)d-  d "~ ˎ2If /#d&RQBRbE;ʼ^Z݌V1G a?)cA RBxB-S +ӡ(kv0[(L+8"wCXazocNdB&^<R(50oD1~2"ٮFRx.d(3qxE*ŝ79luDR^frVeTR}ypmG8g6/B7mEOWEkh,.E͵X1/1ZgGtW[gO_$ 'U!\Ԃ uҸW)~1CpbBSiKf\inMcAzx~p j?E`YGۿ]3p]< ""-\TYz2% jJKEs,;~o]^w/̀zK{ا=z3:?+FԂtv IqŒ}i0r2N LmRsKNQW3-4uh~p/DV-oK*qBoknk\'oz >]n4#EU %Ύ ֐6-Sn HlgrFgOU&!dHvMp]FwۉR"[}#f)"ѣ(}GTs_wn%#)oFv,/5:%|T.%ۀ5. R'5:?X5ZɼG|I*r\ 'Cn`3DS合2SeCodU`B6~:rR}Ѐ"@PWk?س%?#d"4F-LjEW#7Qﯡ#[Ad0, 5{2bPE@Q(>% 2cd&At"ƨBB?E,t-+߮u_1((Yo28/ "8K/?Ȕ{I^;$-!ÏKquO[RⅴăfHChm#}'$d7XEeI~,U~81 folcEbʠ}2YLLuN .9ĈD-y] 6Zr3%ױm*|IGE ITA4 N۶_^?Jd\A5V!yo¸ EsSZa^5,ti[S5!T`1zU!ˆ0}"+ѳ=GB %Y 5TĊѫ735nJ>hk;͕N}?}=ר$u&JћS817??ޏaN7X\E**i ߈?+ɥ/?3LTXKO#T[0u3t$^FE`ᘹ7ld6E)F2mb=5]%i>~R#vl):4>3P g:=3*xXA \9Ua|'@[l\9N4-#F2lqxpJm uU2:oF gBjC~oY"k qAPh㜾=y^BWԼO(xAM:hӲkY(}M׹R"KE虒PMʌf`nD=uZ~_J~Bo ]3$m6@rg!gh]gK I&u)9fxA81ܩ'ʂ?&DckK$<@d,Mʌ~=mK&v6Q[_ u-bE0l~ 1 wM^YKS9M-B޶B,ޒ["ž_̄V,4J@5ykyAPrOiY0/B&NKi6}߱& (N2j1!!45ˎGJTK ^_TUĉd }I`^cPl+Y7DU$sa#bkrl}})]GL$݈_YPovLH5Bb0i*OW,Ae0"vaPɂ`8N+ aɩ`Z2=w(\t+lg:r'stRwMC"-`X 3XXdH` }`z~ed 々g y_@P{vmޞ-@Jg90̛ x]K'ҋCVQ0 %+B>t Y!r,(80VdNƑϨ*RȈȔ I A! qX8!C@Q5TUY8``b h 'qn|pZQ C1#)i:V#9iVSEz̨dݍsH#UAEɰB("(]poM¢`W3m%p2z ȃo&9@Y8 O]cOWډ"9`Y8OBܓab?z/ܲ4@8$;k} \88۶Նx- Ds K.Ho~_gXyFYn ?Z(mIb~e:Pa KihksqסX]Vz@5ǵ1IS4=WmT$3b5tV*02S667#qti4;ٹrFUR TwZ' vGrr9 H:`AПZsw?Jb+:p6 vm`24~0wr=="^QCsF'v*,SЦM.vW-GW.Cge{gQٺ*/ ;Yy6V<|]w'GmE{!!EIUB&9osAAaIo7+!"(f^RÑo1 K^zQ(T$KTL4H0qRB5JbrF9׆#T#_ -D<@T^4v1]z@܊<`)$<&R30cPOF ¡(Ly̥U^LבqB  L#ĪbɐAOuk8h s+pGzuTVB f NɀT 5UBtEȴEȴ٢g9OQw|@89Li.QKqQFZKc@x`a!MPf,i. dSaKqӕ| 'L #Fil(/H@O8 [m,J\B%T)XBJR*KRP U P` U *J,JA%T)XB%T)*KR*P` U s8?6m| s6y$#Fil(/H@OP?}#Fil(/HxOP7@`>)eLWj&A#Fil(/H0QPiI{7T+ClG BD\39址w8(MIB(p37_Xx 'oTOhՀ鿌,%0X&C} 2#&v"F}#~ *ީiW{ݽRu&_IelWЯ71y񲊺ZS5&7SJ32V󩐿) z* Tm:3RlʻNkkkEWoP{NwJ ETLKSy"ޟd^kek^P?{{Z#H=t?eHwBamTSqAUqq7 [a ;pjU a)Ƴ/+qnC6XH|%H%|(p<g#FDN:Pb!`C?zVzCg wv &Pg_,F ه*A-urC"!x&3cڇ)B 'qC6x`rLpst) tZUt4z?(\ ]}yy/BP;,prčen?SH`Tx̂:8*ѱI/JB&2DrcY 'KxYP~T|c1{i~XTP9{σF\diQzC K@RtwH"49 AJ *o "*1T "՝iN"W%::bLքu9ҭz#H );PMO[߲r(RV}?]֣4H-m!]7<- ^ƸS[)OeͿ-A^ctѧeԖD -V uV9sm ݲ?1uXޕ=8 '2>ʕ+S#W pz&QGM1);y3P?Noś=m`Þ {[ת&iFOa7K;,&! G` [bb\LnV6l8Wb;5;8Q^$V+5!a{)ۣ/O/h":S9?ٿҍWt㳊x7 g%xX6n oz'0Mc)rW^]tTW2_%Ja >&1O5z?Kf2E]{=%3-Q5[b,STH{J B8(P!]I)ڶ5UTQd<4և {6VfDη q0k6RRJjq_"<*|uؙ6J4"}Wo'{Bg?j׎]"oxU-JrEVR*-^茑^^/7 c.U- #RZ">]Ah胀DZæ=I┩ ֦DkXqp1혺g?-yba BD2)IINbEz3¡4`TQ{!-;lAZ Jv$#FS+bZ: BOId[:A(QD&L nb2FNDXP thg YYs@b̈nϦ':jѴ] BuRXN=lrB.M@5Zwkzmw޵**=ɗn1lZ`_ Dׅp~R܅'>i7 D胃z0]8A oZ`RD/kUjk15aPQ Ҁ\Kxe%G(|b`Jg4acDUX@!<4B5.B1d ̘p(;⅚ W 7(JXm jy޹]>qZ4PՊuGuk<}rlHs4umL>dirV2Vߠ٨|-q Q+@<ݼj@`mxFslC. 1B)A3Ӱ!S|8:ryp_-yF^n^aU JS E;Q|~v:~AMV$ЏuU7,f8H<d9ȴb. 7 n(ʟgJIEoA5N.·LB]EF[C#+V'YO"7ؑ+&!4kup!,[{Y.V [6_.=jH{9#K*}w]8% F oiuAC#x=Ynᜒ3JN]~-( \*p@BbK C6kMɀ عY| ; & UZ[<KȖIbba%0{ir/wX#M5Q#k ~mZN@td%q3լ~y 9_Bc l FQk4G8Rh|{l~B?/=G90@Dq"]TԭI V-98=hUiSZ!kE0݁J# I~ @Z*h<+#mA#{ּ~pg=c%WH D\4*3)K؁$kA:$H^30#mC<cΉsiaF~(J?.:*p0)A (36Խ"8hCmpoqng$N&)\F HoaZ^Bsp7g"u~r;0S|>( @ ȖOdk08M% lUĖHk ȰHi(']u$:R"Nb(YH@GgB vh)Vs6Wm3ILx/Gql9v gzb|ثlRF+etI~QAxA0@+QHyzÜH +jmxIy%o8i4xHϼ'? (x3(r` lkne) ~;sJ fH-᷍6ǿd q#Fil(/HJ\HZPHQW@'C P3\%.P M˽@BBgifF3rRXxb3܉JWAPF4 _WpLhK^qU5N !RzQ>n,UkHETDQh2@z+BkP q_!rgD6VMghWY **[t۲GVWep˦>K`KmŃZw4u†-9q|I)M %2D4y%E,˻vh}"7wuRK\ X%w!swK[fK8Q梅zqhnkc rW+Œnj-g>|ond\V[YznfRƦ}ljGRQfDc~9C:dwJ8ۚs r3wNA7rGs o(\HSXmop@ H332l" a`Q-[T2͂sQBF̉0AŒ?cJXs@**[D z8-@:F"\tBh,Ą# ,@)b| x/Ƅ#ȱaT 9a)Pط!)0O,`l,xlwb犱i.p92?h34Jb[hr# OCE hمx3&4`  2eA f-h Lg$f, 0.Q\@153 % .%!(4H!Pԇ1) 2``@ǔQTL *@%XjG#p%cH !̀!%` OgTK20TlBTRF~01T FSb 1[TvAr3a8?%hbM~82^+!Aj``?US֛aBJ<&iFs#3fW"L)!,"8iXe/d|ٻmhd{i1 C-1<.3EB8 ƣyL䎰Ül32],U$"qU"b{tU_H4wÄt3eQJғuK-rbP<-=4au=,/8h[+)^ڠ qB5ddʍՋj(JIM+M8ygX=|Y6Jf"Im;͕&V(>+Pi0jhbDaTyZ7˖Pf,?H\yI" :]c($R`SASc^uO.͙Ls^dZ5s|^4htY%+dzw>l{wYK,d{ANeVKw:=%f!zrޫ)!6z[ ՂDH1G|[Nc}|qP^#RpA6Z3Z$,XZçᾪ":ܣ8lq,vȟ(*>?S;mKБmXSk.ٝ넚] UNV֬otQ^nnt*դR$aqk)!b.P*6;lqRdr-X}:rt~0%#ц*^<]WJBa* ]=ћ1Z=ϺdE_7E&eԧ \EDe B;\;"r I?k  78qe$FQMcPe~ E[[>&I,Z٨7juUWKu'jdC8jk 3rVѩ斆/ ]DV10I"q#BbuE `gDC:XׇL+=1|$Ek4Kx׺b"\C}2K0ΖF|ʸZ]yRr d8rgL)1},ɐR|:cpX9T袊.iPG  ⳏcIbE[ ikIV5~˲9"> ߳Yj,9P <5z46 Uh1q_D{ 'T' on-tf禚MX`0>[LR֓BSUg0@3!M@|XY)Y?. YTB" (12LEDžiwx"9ݿ{p&_~+h2,vX{aV{Y,Ʊ Ǥ˱3=+1*8=fq? bpǰHE` {rс#tڪ$j~T'蟢%DLڡ /#G-VND~ }LO2EK跪1ן- shU`xiƅ#Ĝ']!a,3?zF~j ʸJL0a.9O>l%ysJ/x{+fW%;M*$AFm,%w]7j$m{: z+wmHlEAD!Y$a4;R[SE}C=hdֶ7Z3CT3-XW24ro(==i k&t\DoG rJAճ ֕ MeL*dSEH\4]TAUR JWx.[c{HQ3 8IE.5F}Hzw͚QyH:>Cla|v;^@?e,>'mx@͚a( ~nNE.jRyd8٬`T9Ųi mpRi(uN/6 %H푌\w+A~X:R)⪧8ju&vVX$&{8HUA/&pЪc=Kbdk봦MWRU1 ^j 8 (,]1|*@ <X&%@St7C8@7}LP2 IȲ)}ɍ)X=HLk=17S>lb;*XYy~l(&l%#h24'/ &ݓ"=[huWv{fj AUmۍy,-jvQ BbQJ#鿲y6a,E+"o I S&0Aß%pL>RɱEe5U */)q_T?Eb0:#@HZD<1E$L*"ġg8xVy2:\٪nZ'yB(V8WYMH 8).\Bp҇5sE},| W)n@z56U2c_O|D ~o؏=_݉pP+0-dۂv>N2!'%c'wʂ5B+[pŪ8`Ҕl0m'3xހ>w锨ڽ#/`2-st7l+ 6д6KQ_a!\ʣrLZOw@+oJ)Iɂz(*gҐ,dւ l%7V日/ftrC2- T<ΨbJLUK]@ u$`B@<adü-,*U] dm+:FUP^Xx$Z7pIF u@3}:GzTåt|hhdGW>%M@koSd@J޵ʈ&+N3*P<[[ y/Og"+n$0=UV^V@3/ꁗlMIL3+?2 0a?i<@%8= mxS G`Б eh |ϞP2Á)` 1/"JDBރחp +(8/Pkz˟ L<v(E \ĻuH[~ ݅dug b*9ݗsB vU5@pfb*qj #ш-D9atB3nL  [AtOpj1tF{7\U6Itk.'H?!Nx-Ped)ܠGPNCo` b+ѧQõ,TN(P\0y-G8Ј-a{ B&+xv+!ު7Oc_VkcN;aaKpv_j>ҽbFGi`׊9 0¿ϔ+jInD;)g(IT\l@!\tb g@IA7u=a(s%˱,X pdmFk<&8aưQ7(w1\] hpIЏ QFʓ[X;LolB{*n<U-YOAo|W("L =8%BnW261@[Àn0 0q_Q wm(eP i"xqOt9ze|܆39ܔp1N4,sH6:+ >n…:>w^QfEEo\laFԄ(8,Če ͍bjo͏y-م.l(Rw:m칤V3HVpgߒ"a!yY-)$yb,O^ ӹ^/.0#Fil(/H=cH.  l$l fJxs;Q{#W9{;cd+b9XMQ/[EbStzj@% A?~]R);Ƥ1D׊ aIL-n>/l:k5VI1nH -zs=)I,P4(9/jANzѢY! ((oiQFT$e M :Z,LںVh U.IhDe&+\Ѿ*l04H f_é{Ni  ,A1JPN'>ZfN׎4}0)HфnXc7YR{3n>e1t~!K+Kcl@jD'i#R"=3N+]^ ],׳ٛ ĔeW2.\1`ƴXL"rנ D{SL:jc+sųd> f< NɀT 5UBtEȴEȴ٢g9OQw|@89Li.QKqQFZKc@x`a!MPf,i. :O-eOWJa&S$3q,#Fil(/Hh1 1 ?x ,>K7 ՇS@$#Fil(/H@ P?}#Fil(/H 1'ҀL Š Jm`a7Hq@#Fil(/HM0Q@Pʒ [.|#'y| y, 4+97AXDSלNVbN|lXSjVS{VLeiJ}"vw[σ}⣠voshU+zioT{PC05i㍒z"-%o|T{/kB+>md[}U/"+$魎;Rg6יw]]N֖|#řo*ZF\zC5WVļ~0Ɖ d (B d6򠡦A-:%吨I@@5 Id;_?vJ_lL@A;21JMc^wm`Y7ޓ>q2pgh"~ x~,z!2K?~˅^ hp$JJYJl:arYi nG%odѡ7X J]$)#$FœL :xT@j8Y#!2 ]@?)XDr(vL@˞) aEk 1}+zʢ:BNTz&xmDLe@`JA'! (1$Pt^11(*D 8-)DfL`s2$ZoB)18F8$/pS2r@WB.D &OˆY.ǐ@=&B  n6+&,Y }w,Kr`= DWJ22C8g5)8ymt` }J!Q;HqB槨x&lhX·1`p'- HNDT[h!,,F'>Wc~|SEΕ(c 5W.}֓> rj$>JU ;PT4QhʌQ|>d[֔ܒڛlМ?]@G%[.S9KJXg|Ob3\qǹTRR?8d^Nyk^?XeOy$tǚi6Id yp4^̨JKjG[.qQH[7A#;l0C&HN&^ Cٿy*jz֮GRמnרP,߭|)?q 'U ?MVǥ$M]XT]\_NO&zju柆zT/QPMaG4AAu$#uow ?@w!d^7&˄IN,衞>heat{S<ljĬ#~shY!h*>_,"+PW @N0M!'O!bFe췱Á TLo| (ou|+L@yl*̓=ou&#I3Uy?btY/5wT~n!m}sHi=boZ'fupW)>}vtǚSTG&?Z{\1wI\QΈ1P6~G5vFK:; f7ꐰd&PG– Ďhd 5(=IHU֜Q"yP|<Jԥ<1MzDj[m2:ӍNsR b.G>)RrfOeˑDrGą۶vlT-!!B>υې(N_zk[ l KWSS(?c͗hisޢʞ?>3:@ DfCs9f~D*xxdO= %4?f[[ +:U֎KӉ>V.CZ$4m{>D~j8jĿ_YNɶPޔckU`t?g6H\O!N로琖l DLj' ?U@|3$~մDI>|~'lv_Ep~e9z gXra4 n#:O!Nu^Cg1YN$6}\ $͊~W}ձtCB2̜/5zځCMtdK)$CZ;:!-5)͚I?bu42i|Yiv¤|o)[>d8QlQ o ZewY^:ƗzDa Oet_5-{uc}(UOf9r$n O2}I)F]FF|gnOYnDB]JרG$B^*2oѿ,GAh?5X+hTHRKӾX 7B2*虜1SMEHUhhldΝ1@#i5.FTR8i^Դ;DFY q iLHn:РNDk b2*OWNË O&3m?˂PPx"f6}U'|93 Wƭ 5|*ՍK>"釺oǃ7+]ÌӤLqE+@6i ]uT7,\= HͅNF&6O 'ZL{ewWF6#S۔ONT'EPvh jZ0YR?W˸!5NpZō{CZ3Fi{rsLI!fy =M`t!Y .j. åhc琖k OiSYJA -É_5$WPۢ.[bDM/fB>8t-V0VSo\rk^tiY&0HJ1Jtm{j4 [e9K1 .G͘9GX p"& 3H#" C(Ic 0 F ZH. 3<*۟(g vd8pTBhV6^ &H pC@iZ; A;K@"OP@|Q v!Ca:|r%i"~ Gok:?J:ZBU v./@4;ZZ 2C]xxUr[:[Y^{UZH;vs@2IhZ'TY)"D]jIJճZY G\lO'Mky$/Mu<<cyܘ?ߺNqȀoj^GըBC@1KHk@Ȓ^ >JoY#i E]MW;U 8fOu8d(&׃7 T`j`-X`SOaJ@TQ}j T66dk Ƴ5y k7k/q]||(v 1pTE'm@itf#H;vEwɴLh.aFEd4\xtT2VJE}T24ꀙ+0+Lμ<~@ST^9r;`?hΨ}KG["BBےFn)1 zCNԝ+w gqߒ>w4xX|+vڡ;99wH3&a'0 '8lrcwSOV$=p:(B;Pf Ǻ)C 8I) ˀ VfKC#3__\eHQ?ǁΏq2Yh]" ajĚ)1ӡXx)|7; `9HH N1x_ N%HaX4B,8] xUB4u:v@5_Qed_ R>%S ݪK;5k} ;`1p P(Kp6=WZ3)zWsP7; EKd(i&hӼm+ &NB#Fil(/H5J^eIV + |P(DI0d/ַ4ʭh@ۈYE=+_! 5[%Oh62D1D'>Yx9d/Rr^N 1$]<#q~GCĩ}EHtՕvx,۲OdWitH?PZ6bwEDpv/uŢWgtҩ "⻵є*iE\ S&ßo#.1E"7]ƥ黵fKT|$wk[v[#O-Lc M $]&宽zIq;K[P5ߝ{mvժЭWSz;WݎA(Y2ˆ[))+i俋E8}ok4;zD~S./|tG+"*q6?T>Ոe6g̙TJ3-Q2ۓ)f:1 abf &A !HXUcBjacV5@F0t2N\P@J|,̋ɂ 1P?@#)=`~mf3k8"j%"F6#s̜ 04llIPUq/reGf[gO^\ JWq%ZL:ESQUcz\kɧǥSE P*^NU?(7,8׎e M,p WR-n*?qjCu sj EDoEH/CZ.4o.0zZQjtxt Y%kA9 ͊f"KIdT;rP rL"ENyt\qE:(Ukb؋"ۙ^dqE:ӆ=PYA`H] ҡ'.$]NA`!zaU\R]ABhE#p |~HhR$jE>UQOgeh W ޚKrQծߪNw4UŪ9 cshT[a> rrY}_Nɿ9P{(=-șAEZ.Yr8eNǤ$(dEwh0']0'/Bћ8&0ϻNVV @ohO rۿ]:ܰ 6Ѫak4PJ$}lQ vG@#a ǣW}C5.n(wUWb4- Գsa5M$P"zUD:'TH<0m{ix*B8j}lABi& +8VkܫCt!&0I_Cgݣi< U75nTb "y$Hdj!I?XՍ`_JbWsEDHL!e[B\I(?coms"A$CxR.WQ.$ a¨R[%g|:/np{O)tEZ}W-0tŞWӶ6GudcnCqv|V{t'E㇮jc5u~t)_ UDG9q\zr5?UyȤ@p*:q ьӲGԐuEDE9$j-nX>U"3P wI [ZFȖ<׌tbH/K9BB"PQoP\K#*?9"d;RaN+Z{V(7$å5D"5 ⭄!W͖h2z\fl=s\b1хIYtBdmJ-'8<\4E5MFo+4梸U dZݽբKUVWZuŹԢ_}Vn>֎W<';AMIqL%|G(b$ M6I:x:KP HBנdoQn?]th9J/U quqՏc)N& ,nlKLyg(U|O¥xRnR9'T(3BOJ\2<~%MIƇ_('ڡ4AbDYY+ZοlSJ i1!9&EO%hFu2^(>%I3_T1}N\V4jHT~XL[[T=VE"ѿv 2" >̄?ٳ&V`WBsiB@Ab2_cA0͆Qi7_(kI(jב ]Yɪt[NQ[[N}&#g=8DǿBUȬ.[~ud?M s¸P~Rgڱ؃|X)/b@.J;:1ˈ` 8v; ePƆ,rFGvu"تCsч8.eqiKщS(z`5ѲĨd|uiTP.ٝz4s^)KwҹL%[()r6ݭUEE+)(ܚ;R#lHRĩB+~}X^$"6ivkۜKRv.FEiOGR7=td!}v0[dbڇ1RȉST>Y]$KnWN IX4EŠ%RCQ7cUiO4hK)FmBE/" `=5mRq"gِALxhlp4 DHV!fPTDX0TP#k9 P6 ZtoUMJ X]B3B۔㇋}^+' P4j,s:(7E{0K9vV=KfӒL[BYՁڵQ‘N/:M,~U!K@lU3B/0ro4~ ֪D Fb6hY7V=0"{n! ?Z f }]L@x;Ip@&Tǎ4e,ɲ)3%LӊHޒNTg0SϹ*1RTy290+l9>>,#iɖi|yc?M+sd3¡ kضG8]օRGmjv ā+(1ɞ,=qf(jI{LJUE{cfP>Lyc(^hekr$ jZu2͆TF? bS>H=™_E"Ǟ0= 7B#=i>8.T5v#ꑎ „!\J tF5PҁL[iVpj%"EӚ:M-~a-YQ,tmRxڍG?3JvuPM^@U~ϏG+KEݝvud痝NJ8Y梏Չ=F7-G /(AC}Qe;N`| ݊FDYGJ½Cy%.*4g ;%F$a`x4dy%*>0xpw\t>˔k.2T(bYML;xmv%Âr`Ųz4~ \ 4Xߛ Ǩhj'@ O&xTݐCN1y@^R歄m_X9 ?⑌ @~N|02pN|/O& MzpGX=2F,B9UיִL& q: /jƳ')=2W.!*oCp"` -,Xm 3 `[r 5tmu%l ?*,k*r5T8 ZJ ȥ:9s˶{S=LԵtUlf7qq!Y0}r)@yT1RA肨d5S,/{,3%2z Mxz:`dnܼz ހI6p?n])$?ĉ$/5DcaZaIP4`™N)L}D`P3\X+%kjGv4* *#e/eY K[SpN "1GP^kLe"MHl7 c>~ÙuX=ÙMG#\9߸h`Fu 52q x9 XpxcзbhK_#gJ!x,(: B+ T8C8{:@U'^"NB*2'B<*Q+3A:``:f2- v}1p>d8<t1Ǯzu@|\ (.L` !E l yP<hˁP}<2џoOT`hfCs@ũ,N)ڜE<Yg.ARim m!j=8BjjUDc87_Ɋcabì.!)NP_S T֬R0e SꨪŎ 뢉Ld m,ZhZk14Qa-R~@q!hQs־t#Fil(/H%H R;jHR6\lFsq 7*+yŠɐ8AC+"mz2ڳ Nuzszi$9^f +z [J-j>z|/- FH vNIJ=[dvD!JMnZL 73EFFLHҡ!My4=K[saJf U]|TuFvrmhYLPಬ j&) *?EPoΌ&edzp:Qo/4LdLI ;f*UV? U$Qvj%7v| O!Qk(c[کxG֌\izaL@_trF}> f< NɀT 5UBtEȴEȴ٢g9OQw|@89Li.QKqQFZKc@x`a!MPf,i. :O-eOWJa&S$3q,#Fil(/Hh<-< ?x ,>K7 ՇS@$#Fil(/H@-P?}#Fil(/H-,<'ҀL Š Jm`a7Hq@#Fil(/H0} RAw #Fil(/H} ?,#Fil(/H0?С,l RS#Fil(/H* EB7l;Yy"vëU@ N v*ё:C0(H#?!5iXzddMS( pr1jlKhe8=D3n!J -&mZ0noZSщ{}4ԛ(faKJv PoUcnw$4W$b\(EOb>bӈ5y%KgzVR߱cCtEac|f(2 )?惣D$jw-Q_n_tB/ `6M~L Tisɒ!0rC$S_Cmm)d7>Scf~&SI$ipf>:FLIYj\5%B(>"*҇TapE;]KAs|n鍸+tuF kg Ҩ6#e4Hłl%:b쫯yi-o#FL?,n R.39uɈ6g%{3PmNc>5#3PDS,|%01Ei$-ORX#ȼ>ʋ[&=u^?rF76gEEUмBRJ ο190E]~pؽ Tyj.DmN2 S.oaU&0_5Kߺ;x2sҼEaA"muE7m5\e}'`ۥ04Q)p,2JKU Sn[skxx }4Ipir??!#QmDos2 ;{ M<]zxܮ1 U<^@E]\FP53vm{ϕ62[yiѹ֒*|HAя1%8;/`[D jUz3 ʑ@ 96D,^Gf p&bm^; B˿ 1)5O$w?)֜>Rl~j23D"3&-D8ByPAKIUn2%iU!} :(䮫H*X< kYjĕ*KF`G5z"b4Nb[-Sި´-D*(eBucV 9zV績O#ĺ8}Eb}ZI1PxlBQ@`*,$z*>C[Pf膗(tiș.SjZ܆)H}EH8gmOĊ+OltLiHtVa,vRoZVZ' vTA**fyiM^(n@c/ &_d,Ҿehr\-MǛNyEMc6R|% 8ۙSssm3R/ X.uPؘVT8<3zse_LV ݨu5!GYi<:4 bA@W R[Iɱ6Q ݟ,6{`/ŭ7YF,2rUOe3U.wbvin6)"nY 'TD$7 >{dPj$s 1։PLGsQ)ƣ,:8<|7A\̕}̰Ѹd+dlAq6Z>,IbwXQcAJ&tcd^lRa7<:22%R]F@D 0v{H|" 1H)Tr_`H4?T#5lǵrlpC<1?f2桎?|5&r2WSw(C|=-1B@%Mʦ|H 퐌eGA{~ca|b|^Yg&+Ga?i.u9lwc;gꕫG@9 dib[hVZm8 `I4ڮ/Z UuLT(H 4GB' $ʵ#t#Fil(/H]J!B6l;ɝ Y ({ im+FR!Ubn8aܦ4CF/z'qx0beQE MUhg݅,#/fg Пh2'88I&$C>_쀢ߖBE-]8Ts{=Kir[HN+kܐS3c'T䦹7%[G#'SĞrJXZN7`WRɃO0 gգVP"Tel44_17ד?rrr&iHҢbڶ, ~7b5w\޿O R:4/x(GZlŒ?5;O/O_.KZSU=VAfm&g w.Ǣo"ȟWUU{*F4?SKJ8wbHW7W\4(*α%˪F/NosXTrs~(\lP/'$vt01dԖf$ǻŒaIh^p@!YF& T҈A'Wb 9hh܏:u.?XUv8>I ?og,yCq#x'UK(2>[C8uL`#QZߵ]Ĥ5GԎ4;f RP1ڒX [EUg\ nDTaW}Gg\jZɼc:1J jMMljf/snt5C2A>3"xr9M H5{}?&t}`4D{r&n( gD`;N0kAx|>l"kA[3Mu3(rj팼Ue~] ;re,qb!6n i@kA˰ЋصX\ NzBaiCW XdzH@I͡P@-BP 6=K?>#`[UZ_7uH#+Fj#*2U7߉v"YGN3dDd70S&l)AZjh!'n!CS$X.ɨ-H5jHŅ7./Y/bOWkŸ߀i׈hAr7f|z6%X_ҍ? 7u2N[TWXnt$ ? ?1h.s YȩW`Q EKV 9O.ЊZ)P mwBzw07 O=|(S»U";m-IM=`]'qp^ *ML/ue)PㅻsKiA#b]i [o M2=qk%Cm*6ƴ5G[ 1gj-tyPW_ l0/TYk:T,Eh@p *Dx0bVIqGfbi67:LgR-tS:<:YI  |za;.y.VL )忌imn֝ѝʂw;ME8BPZE@ÎZ8vi6vӤXЏGAV'-s'j).dEJe$t@+} *ArL[TQ@N,0|C ?h1JQO0 x*!Be66an majbmomboaP"oe]Xueǵ^wjgbfY5s N\`6&I2(вĔLswNe]RHqF+ _K4<@'I+]nK?d_3LB*=rZGo <*Klr@,52&+ח.bBv~^(6'ս( ܜhbbLb+: @fq|;j9^M 7܅|;τjh/f舄-4k3A{[DZGY[3Y 8Yd:6N\aXjg2cKBadح_ٸLޣ#p<  t3f6AőCI<D.dEguHvY.0O:i1TfqnX>"z'622bOvj@&pR*br_(L]25AB XΦ⑋FUr%%#'78<<.7>^ #h x 3R8T*]]5&qId@ tcp H*ЄL8~μ/A{b?Yc"ڹO3ڇMI@%5j.!ɑ I2GJ6V(#@ < XtmR8*Dc?OS+ӝ9ɴwVs~ ڼ.&$4M'"<2=-x29s ;^V /[UuL[ |SL+WeʳP_UQDZtf>/JYYxP]~ؚYY!!ϝkw`=HT".F00fI65+ y9U"um'H%9*29fPrT5zQl,HkQO UQ8Ox3$* 77阂P@?f+-1Q( 0V*?Y< hwǿV(EL ƘUAj?THv-t}2yY"!p|ƌ7&ʲ*!a404T_#4hXX_,(md8DJ?.<"@/'Vk!LýqOԺL^ً͢,;mxŽGar>TkB{ރ;&wfYm#ϹY=g'gmH}1Iɠ,.`'9He'f#XC/<.mWZc!CY8QJ2hT™ԄD.c`y!F#`) VA(  :0ַ^k 1Îƹ0#IʬVF&eq S g?:r'@WT-']^8{q4dMs8Rp{XN8A67% Cs H̲mud,Ⱦ [hwȠi0O)$tN3,? <=_6dԚr.O,%nHj\pHM/t Z s`mWʺsv\_Fbmj=~h,̏o.!,,MECi(5zJںQC.|S R΢ GںOίf`H?#k쁙 s!ݺɨڴy[lZ5#hUC:o-EUUz:$g57f5!Z)́p5MKlDo~3̀'颺(QiKf/ED`CjhCr#Qsەe0F:vf-)(L4= e@7ڲa̒gjղ5 U(-&9j xןfۗF41&V06770o 4M P23P0 j̜_Ǖ^sjibfYuB [^Y`kP*#eXN e\V-xxw3DcK* zR0Dܖ' 3 ˱uϚzQEjQtȬB,zfƅ |!=Ѡl-Q&KSs S11$[dU]lFj`2X ,'g%7ȘUEgE.;X{NC+@mvƌ?wfmoFheyFւO>Z!wb3k"|Y\W涡S&4+B`}'36,ÁuqYxʙ6@)t?0r}#C=g6pU 2l &@ 'u]fodA@jə5Ŭ'IfZ9Y/f1Ξ\`f`GEHs!;uqCPyd9 ]":I8 ,CuJ)N#O``d7Z{BkPs DmO$Jr]p8,wWTA?| \#S5//nbal V sa[HWqHE=(bF l K5{'aԪBȄ^;`,t IZ?'>mbq`ʕԆD64Ut6 {tAF#ڲGk*7GlZ(M )|xZ|vсS3; @ *Zp #T9 ',z^m@]d W.V5<ǽu(!kmcF0q\}z@q4b&/PJd.ptsU,1`Oebm{g w'a>k"'lCY7{'%r9~x˖AMRk%b|I;o1{$czTJL VM8!N1J "4(c (`0 _ -c_n$, `кFȔV5Z18V#QcگUiTy}Kt!(u3“.MYQ12`Ѝ  ԝׄu:Wudߥӽ9Wrh*ZYE)(Gs[_g-3Mp詒gƑ^Dv P+Ik_3upܙv*5a )۽pV0I:(-W%L=T$)1erI⪘ʬ.l4/?#Ϡĕ(:~WD"6$z Î aGG9WƊ).+<10+P O0 9.0@0`@'h'%D$YhڇJ/X ¦M #{fWKޝ}ї e%Ėhf"ldP( 1  t8{ &0f@O50[5?R $,`\!1 Ι`uFixT챬,'vn^KU4|S ݀t 2hnʼn γ"/34݋{~v] - Fhp`d CW}bjza 1ЬG8#Fil(/H<,-------------------5e(7 8e(k=;[WwODJ?YyGx\ݵ /t,uUi$]t}R`jo;(w6OtD4tK.2qqǂsZ}w%gYnZ0~Y8cC} 5b1j>rrR}jwL=MAW 0=<:z U\xaS w9P 8KL)6W% a @ ۾۾۾(SѾ۾۾۾;.x]7F4Ұ~"U*!&]@0OCFEõprEŋfla51qYD 'Hl;{&֩"s)Jbu5 JXI ׃5r DՔ8dlM0Q( S#ź* 1ڤ5-*RKɹ%mhlpȉѐ v"YZ*w3/+[F!$#2;qJɞRGߣ!eisժҹ#9s`OrAѬpДP+T3..%ap}i9B!t?ҵk 0Ϟ, z+}?V?L}=_;0G, ?:cCUV|QKh Ԫ1; ~M2+DeϣS8s-"y)e G SUҖ.NL(,gTE oh_'/k(7ڱ W:d ><B9npSGa=|O:JsGKkT JLk9 JW{SvRL 黅Il& kfN2*eנOa47CCKR%3ױmZ3۬ A(Ӷ9eŴ4kڎ?;R. :,:iZ\w`ꔃ>0I-h=CIkN-@QivHgPR0`Odj~H3[1i2j{hGD:Ͽ֫Frh #85`eKCsAseuV͍Jk]\E0pmmmm xmWϷmU1A֘'Q}pG+9BV"0K+ϟ~RyIm7ATrVSIh:'J6Hbm3SX[e_ʢNʤ 6 T\Qƪـb d.}, 4XS? y6= D!hFl|'XDy"6 zυ`ӣXfs ol65u ҭȉ䩡gꉶq"b+Zn9YCMYD:AF6]QaA.fJbBAyhCXK̆IDkɚ=i/zFd4Y8%åRveQ3Avs;P)ٹrwEAtZ[=qCL 6G=7 nA'?|0 @b>Dڿvяʦ:y "r0`1n#IΘ쟖y&_Ij5Z ]=9BU7FVm-' Hzx:;4lCB7D2( ۱0aO[BI/"N%N-%-I]pK [p8 ['lC?8QDf#Fil(/HMDs5s(X@Hk32CfIyhP끞*=xKS,I D٥3 E\ǃG%Wċ8uA;zdo?a,B 횘5vzm3}~TeJH8%*j|٧PgbCA@ȣUXaB^os5F:'7g 6]\߃HvUivoy\ 1kooce\Us/ 8(&WoE% ,=ď Q~1Kt'RiJ|<82ap@-#CDNܼk̽c;EN%avM)60\r7[Xp -]Ql=dءm[Eo[EofwtjZwy]D*t;{vTxuQ$0EHuǎy]8K2b%  `&Pt {hJu$gikb` HhAWR"H,eZG53")el 'E(vgx=>+ `8>ZI!ӴFJ -ՍkgplMX$#DxCeFMAPGk|i.KIŀxItdnGHΚcuuC#SmA:9/BdB+h+^UZlY =:jvZ'vWW!EM1f 2$|\7`K8sM˒eۿ93>2],P9'属nݦ@ dXo#~&;uUb*L)0V}PIpaiUUt3P2'NB8Q&OwYT29N(2{mŧT:)i0ʅ 9N^l89דUY[=PE Fe0ᨩ2 ~5>qבcv{I}3^-l!h-98=ܞ\Dփkb wju AS,MaAƄDKKP55n" 'c|~D4yi:LIZfqlFW٭N ѱms ~ݾ6۪^VvfV@0+2F,{JiTդfv X]\M)%Z &VyB1va9dzh蜇5Z"N/e#F'QldEmG%>@zBnH[q%hb~ bA U^J6TG4֟eӀ3 Pq"0̾+&C''mR5Bڪ模BfF`P@( ! C8B" # @xg teV D '#Lz,|8!G"^H5O_(yl] %5q7{gr &~ Ϣnzj:WRurQf5W:.bߕH…A+9Bnz%/6>ט$BVu Fv`l|m UfoZB>0Ec\tԅlف<iP Ga0vMĭX>z |zlz#AB1$Y0(C64LI,a۴۾۾۾۾퓸Yּ`*X鳻SCGŁFaҕW2. G)\̌Y&H%3GLL5,xN'$;Yv8b/;l"{FdIQ/O=jog; F$ %|BȺJr`φc74PX.l+8e3m HP6B NpYk)&~p(DjL&xF +uFwMȊDq$%<ܪEDѥ%iHؖA/$XO55[d+'q-!Z~e>Ί)g{s)V~7EъqK~% ovQ4\MJ,z|FUWnȯV 131jBCWPâ,bߔk85ɭD_Cg"thUP(FN1Y9阉kCbF%/ u`#qe@_b}<`.b`,Q%Stʨ'(etDo?:BC!`'aS[ХBSf̌dE.x4ȵ0)ViGG\55f6} %or@ܭ0 #؄ .ET,O@YDₛjn.|&"}_ޞM9 uy5)bgZwC1SYcUPxjmɒY2U{gh xy B&z_luR##S_r?-. 1=u#i789.L XFDPE-^W o=mmmmz5Vڎ_YT^?ឳAuD!JWn!oOVХ_VS*N"&& ԤBaCO7}"<3&(U>}W8j IzK`^uRZfft%mVUF/PP{tF {|p̫N Xٌ(V9%5.Ѫk,=k3Y=gҷO-3JSP\L^HT*! j:KuH`S h9AJ. L:*e݋L09 ˩ē8DkIi'? DEqw Y~_5~j;5tHAiw03`+P aR,U|jZ368-cӁu$)23 A @(HI@L//ʇq)6Ǯh;nl|캱6Ǯh;nl^NhZ;r(yǵMq@iÓmD}@\Ul"ne,Cx.513^89A,{LWM"H 32zU@A:-H}B㊚eZ:cdžYU͎쟷Čf505> &駊vv46CuUu,еΝ%KS?Cx)[֖Œ=̱ 7/wNPpZ_ƪ&+t/8jD)vID8b'kD)vCxn#Fil(/H0/СX; /X#Fil(/HD)Lw3 ;|Ճjw2W3Vz&|f0\U~)ɨsU=\L<WNض Bg:?pSIJQs4X0t@#u;+SL晞}g1s:M*ɨqm?%x2XX* tJnԝy1b~ӧw   |#/K8Q=';.< %:M9f!{-&: @";*)cK;lͲ/,K0 6غ>,A0:      ./ $,  7-44D!    )   /*<,4/ @) 16 Pl BWG@~X0Y[5oS$79X(  :FY0&hA2)IR  (3 N5QrqI/8D*nҥmaK=uSO[MzPU$o`V(0#Fil(/H)44''FEJu2 ;zցhz1U7Tx*ye4[S|-ʥuX<[O;YQԸ @f9>mRGMTv}7V/r@!x?*QK曠{e5q9P}.ʥtl>${1W[) rNl՚|0`}|Ԩu   |#/K8Q=';.< %:M|9f!{-&: @";*)cK;lͲ/,K0 6غ>,A0:      ./ $,  7-44D!    )   /*<,4/ @) 14< @ WV(l|+N ߪ*X@ 2p&`K PHp}iDLP@*F``S\5R%Tu)5.cE%!Uz⢞6E=mw(pB]U7+ #Fil(/H)11%%A@Is1 :xg|0T;Su-xc8YQz0ˢx[;YR:\Uй @d7=lPSFPWx{;¯U.o>!{B*PI枡yc9o8S{2ˢvi=#~0V^( pQi֘~/_zzթs   |#/K8Q=';.< %:M|9f!{-&: @";*)cK;lͲ/,K0 6غ>,A0:      ./ $,  7-44D!    )   /*<,4/ @) 11< @ WV(l|+N ߪ*X@ 2p&`K PHp}iDLP@*F``S\5R%Tu)5.cE%!Uz⢞6E=mw(pB]U7+ #Fil(/H0WС ,,@ PxQ#Fil(/H   5:#78r^}PUH`h -@5'.Z +&I-Q0/ \  /       kP[ aX@An{09 $P,P,T-PPPP8THE)4#Fil(/H d,#49"57t`OSG^k큕,?4'.Y +&I-Q0/ \  /       rTRpPn/\_@PAŢ@eY@Aa0*XJ@ #Fil(/H t) 38"56vcMSF\n탒+>3&-W +&I-Q0/ \  /       rTRpPn/\_@PAŢ@eeY@Aa0*XJ@ #Fil(/H0kСZVO? #Fil(/H +0>d/51Br2d/618IoBYKtd/71fz BU&8ld/81E)Bid/91/'B7d/a1WB/d/b1 NBvN-d/c1# Bd/d1 Bod/e1! %fBL,d/f1"%SB֠,(d/02#"#aBBmd/12$",֬vBld/22%%FʫBFd/32&4)HÖB d/42',#Q뚑BQsd/52(,;$+pBEd/62)-CvKB[md/72*=/Dl2Bg~d/82+0"'+7BQAd/92,0MvBd/a2-C1KB']d/b2.1hxBx8d/c2/2"MGOBod/d202Ht8BzYmd/e212H$BUd/f22K3HpBI=d/0333!pBK7nd/13N3 I23B^d/b5^96!PwVB&Rd/c5c/\d/ThmbӊƊPNG  IHDRŐg pHYs+ IDATxw|dW}mӋꨗ^].88 !I$^y!'!l ලjݕVNown1Zj#mtޯjs={H8 W  \y"A%H @A  $AAXD  KAa   ,A"A%H @A  $AAXD  KAa   ,A"A%H @A  $AAXD  KAa   ,A"A%H @A  $AAXD  KAa   ,A"A%H @A  $AAXD  KAa   ,A"A%H @A  $AAXD  Kz y,|4<<9r 8o?I,_oWUl޼ۍPU s'9\B,Tr9N8}bdd]P$(2.Hd8gQ$W˛&iӦ`98,˄B!ZZZظq#۷o@ @mm----466i Akޒ 8=wf||??a:V&:tn(XnyAD<*.*SRu+|;SD*Ʉn2lrpΠ+Y` k+XVkSeZD(뮻m۶:l6~^/[l{u^ pmY@WW_/ DL&ir |>߬0 CݻwC2$͒f) 8HWi VŧV@$܊KP$i{|cRyT~Ka;Cڴ9s0;] [veMx<|>|>HuqswЀ2L{Oo}+?,z TUUqM7ܹStA ,P20 ??&NO$dI²m}p-# ,~~q(eM4I¥He"QQXtڰ:p˛8$bKn;l.v%x^*++y;}7-2 O|G'?CUg߿9YAzۉ'8|TTj>*\UI2w|>EKAx2@" HeI"mřu4YfMM5++{y;hڵ{wݻwT$>VRgqN&a/~7M:t5UloGe b6G1Fr9!*9-AET^[[W)L|l"c1nvNvtSxg{Zlh}қ5X&?5TYQ&2C2;hh;9/vUmCAy=XE:& â 5IIPdVz;O2M^UŶm*]2eM@%KHIJ˪ØaGsX ( 1Φ [#]n^as Yh Oz U2̕=)A,0 qEʲ9`LaWf?1.zy 4W,1<%.W81[|cñ<.UoĶ:Һ[i2aM:/q'zS,KU&}-CCC ^3A.EyN8@t,Aβi X[S=m[qx4,XzaUnY.C~r&96ɔ~Nl2_H,[Un`{KDNv| :{V 2\GOwyI:;a9GA$FFF=Akע t]ȒDw.ϸaV6E ŏ;NqM> LO… & &2nޝ 6>g#1 p\qp$u۷&RI0980iUdb< oӶYVQAc n<O$bhJ p ,0 # $%ɛ& қL%xZ:o> Tl$S(> <.s&5T?<3 ?"8dOc4oR{7ckKi3DZ|yWd%y -^UƦF|Ơ^YAXX]`&X q9+;˶966q,nq߿*b;0BgRCw`sSʹm+*Y 6]ɇ8tOpptӶlk.q|Iw pZtӧRlC4>NPgmŽ^ r)FS,{kA?6Dwx'3S\)I;LLhy6^:oۧNܯp,*7k!K{`Smb[CTѣG^ µmwip\+d:O¯qo,O(Th2y$oZpk 븾E ~6&k3ivp,O-+ua[duryrV7.Yܙm7O`Ϟ=b -[LQp:͓g{n7?n&Ebu@<խ>wrw|0}E(! lnyedò i2!%Kæ z{9>68š<;vL$ \]p]wo}zNOcyL7nY}+3~/%K}g xf:'}>Qf ۷:RYr_vPQh(3i2onU)XOy~`Kx?yE pe,@UU}Vyh._!\W|Plw+,1R-H{W$/qqx~,%e:oHcx'T:"q+LlYUЇ>Ŀ˿PYY:A=.8gΝvm@qnoVSThm3}WSyUbU3"8?I4?0htFI6U{7qߋEJ/H.dOVX/flll]z״AaQl/~G?«He]H+kL= b7}ܾM[VqumÏ6I~g?{j87Nʙlm;7KnY:/\56YW> pu,, F+ܨU$3O[-[W6lMS;Q?ſ23usqr ge@KѫfaMG#}. µA& ˳>{t]ltD@Q*y %ne6!MB U(`;Ep.ĭ*f2,'GP_òUc/vxt0tmm-yZwh&i3Ym1Ҧ*޸w?%- pmӣDomV\P]w{*-PiH[XrܯU$DWae@qݰ83(Y] ǁupYx `?H⪆7;; .9 /~&+0-Ènq0^`BQpKዟy{ˊ+. W_ )r#g9]} $i•aΞ=Kí+4zT d \`覍aYi;XeZ,11E*Uxߥ j*w'Ә{h $&,^n]E'si&g0 -ݛa$o1lgz%/KxUPeMq .E)x5K5c{Q}A\ q}Ң <<.EaEMZlR(w̻WS,S).d; oZ=D̯,sfwdx?4=sn;|;}I,^ھ{~ H,5 y$jYHusV"@WWGaƍ*RdF"dϲN@\hz1^\ Ӽy_$.A:ir0.,U-;9'&O?N$-m+AzS t:9͕VTMO|]̤H>NK|Lߙ]t%2V_`fӳn8_W΢;Ʈ]m7oY=cST1ɏam_oٺ ő6.Y\ߺ=\D%Z7@S旧`YuL} `r,Ua24Y`r98kh wm~z={x6[ACʸ׈9΂4O<)yCcȲ̺n\[ׯ%;:F8t(f9xv<~}C>U!,Iܼ#U>o9Wp6?;3ijnjĭi~Y^_Go|'aNM ˹2T^q9e޽߿oyN  \9 >bFC0D4%ϳw}T<\W_EC-˛GSMK7-9=g Әvq@Wh I47w1qZjjx+^κf~}8G'7W7sc{|TrGrM7!ˋ.۴ ¢| ^@s9=ȷu؎Êz𞻩 Q'P f8l) )K;Vbmk+>ûpxpScq7yӖUOG?o~󛩪Dg|idž($8d #HƱYW W-\+TW:q :e")*!ɋ3cYFH:f0sil]UWU-PŢ,C~|c#=z>?q֗׭1bGbfik{,m>RU0g6UtdQ=OGG紿$+W.xH#]'O=|#[vY_A.q>64Ib_3Po3c8cdIӨO WG/ {We5}Vm(<&=M??w[YER˻9N:FC"vtd셈\*jo;@h9Mջ֮kؘ3;zlDpWRZV HĈ?B"ZU 7CՖ8?cd>9RZ3aX:6NA/VT/ f@o";xo| \>)$`yukO3Cֵpǖlhm=]M[++pT^']ЉesR |wo^ƚH%-~{ہ$w11#qb}G}0WӨz– ʝ7=]t=3HO4Z~kۚic4EfeK_2wUQ~/IHZ`?Dzz'n+-:Uj(n/pc܇//$ص0C=>s5,X,޽{u[7ac18ˮ!$$qܳ}ۜg3"+3 ҺaYry3)ɲV1йTzTEWKr_Ɔ6(,#KZ( q\^'NS(H:)]G$ITY596.r ٶr~)tv\0G| 8@42SB"V8rToe&CIU5?`W4A$B+JO1uU !MM-M5Qvkc_F:IyjY/j┊Ab7Ev]I^ҽKV6Uo5H׉(h{>EOg^23S l}.zX⮮uIVPA*m]SG~lϞcYOq;d9mY/yɵ! Z oZ'䱮 .F|[¶m)$I"pƶmh?Ǟv,ʲۥ eHH2*@qC6'JQ0 °,rA\c{s#2>oйy3_}1]cѳ?MSd$RIlۙfwrlgYUլy.˶ wٷX~tt={W/$+jQ\ZM7kl`_jEuJ{ $Vs{>f槭q($3c#1mm%;t׬|ʰHK)?:cܢo^Nh9e[t'׌d#9ԾB2V2'- $oK({+m###㙖s':8G]E׋KU ְ % _ eqbtL˶iin dzKE8rstPJ6tw7قH-Ͳc2e Ft 2O+Lh*h"A:S8-\=?Уct}5l_M̈́Wo*FKf4ogXHɁ㍔U ?2E].Ysh[y H\gg=P._^pղk"2E!ruA.c8Vn]ǥ ~ƪ*Xƶy%.&>ƵkinjDUU"55g~tl6KeE_MJ0 hBqEa㺵rm*߳D>!6Q3wCqvwuqzhD6aZL}+yrPLZwtM7݄=_3'H9x|(>?멽^;چ%y7x1m!qlk]es%U%|KQ%Hq2@m6(%[lPuf̗ƹW}@H磢#u׭*VB(}dqec:6l˶M8t̉b-UQ|,pմG"(<85s]056aY].6]KC}T3(^qtٳDL6GeEWAeY3Yd_S]ͺ5,S]Y+^v#ɰճN0(ԀD=]8D:aӺ?hK4J ZBu~xqp[SR'/8W6 t# ?:賏!k.B7Rwr\5v[/7-0ØEqYӿ+\zt?yK ++\ :c#II/f;^I)]'5:iTìZHdʲ̊˨z@E(A!l|>O"B/$jhmn.9c:Ofhxht*y8'yL"gtX&aZ*{Y-n.EF/u~?A?Y*lP(D8|*Ii};('z9cG?Mhzjnx9"|m&tB~)IQV }3-.5ubkՂ |>7ofd ,K=Կ!U(Lm A,&JsL7'Cm(45\F ^  Q[SC L].?I=$SihkiϋΧ5k8Iax_iMI§i4]ZqdwK=L7MSWwei V/|O=_bfRs>m;:賏Qw=4f<5otfA-e:ƥEpUFaJYۘl`-yQ+a^M6ЛHP(_Y$Bn7 tLm;,]QRi2AEeՊhoYiA::  1L&KmM5Dd@]$ƵxvJ(bXHb+lYsசy07/8tlkE EZ[[imm-NY>kNvLl$.J^ ^\_47=O2UTȓ}c':'xGǧͪXmSl^/6ldǽ$YSSOՖɯSn[ӽ]Wt Rb0msZqltr^Kˊ(zno.,9̤0ss{v\< ۃ஬)9B;̤Jf,xZEO4OvTY9cMMD oPj|n75 ]'OpY^3ܱD!lۦ*觮2aZ GxVguuIGO7V|.d1lǶm+KH8M_K^EdU[uflJӌHuWܼj9%J=u3TOm؂0)e0iYR^;klR>FdU{i]{ܰ% ??kZW~ˍl?qcsZcg_4*[-L%NϼDa430kOI4Qd;UTXE ,ǥb&g{{I.47 nMvq򹹾MGsj"~7lӲ TW_٩UQH'gmxJf!zQ}APfhACTϣwlرzNλZd+3Gn]ȏ2uO~lh+kB.&<7Oؾ[b133H\pLYvYHŏjY@KK Tc0:CxXV`mm lx@m )$ kqk*Tݘ 2w(oRFe5DTh)cM 2bY] Մj ]3=@yq]޺ybkqs]G˒O4cx=O^0@41YS _ la^/Ӛ6=\<7x6WSXE[Bψiیel/Pr]Kq0X&o63L+W>Dgm\[MKF*dC"c24L(;r3_b+֭WvrYu0: 2}/G>Uf%ױ-v?3snQcst56zbt~JfmM_=c.[V<%dž8;uռxṲ̊Уm4MG.xRKK;J?}^xd86ÿzc+=3ϑTЊu%)^?,G~|+N~z}.{:x3.ykłuV|>$, -YMw,uY!eeU Mnyưm. u^M5 Gswvs\q s1`Z4MB>5yc$I*`<"~BnEfmm*S4)ONbc]ZjYc 5Յpw<.ro4]x >՛DqUT V86HIt&{zf銵n\r#MT)ȏ1M@ Gn{eR|mӠ2ÄVmSۀ$7Gnd-x}Tm]mgca\5xZ5cä{O])>>J]V>GǗ~egg.-_)ZoX;MvCg(aqWףO&7w?4m욑5k W:ztJ_@^xt^Sl2c,Q a!F}KsWb;-VPgeI6"Β9p۷lFSh'/2-C a6=gd2LCU)["U'S s}S#/^U?a$shxX.Ǧ:VV_81QRIdYf+[^RB+ᩩ'?6Tr;Dz8'}d/o1 ._D$g\PUzH'8̬QAܕ5d4{";d?vcO=ம#jK7ZZ,N=s~mQ~Aɡr3#YYRl3w•H ]N @$ZZZd~0q86:c304| 2Ko;q@%|nwT~*E&?0su}r,eۤ(O_TX_UAM~mre3K_aƚ0kx5Dt^2d @("Pf̥Z7_ִ[n&j -Tm'ܴU;8ӷ,)!BKJSP*ן""MQ}ib@(H$R l6پ3So;?nv7ٔMaJvfnssCis-?h7A߀7gUorW>s7-tb ]A]$[u#2Hx^rr٧|O^f:,bמ=4w4aTU0al߹h,f`aWQDj>pЕKU( 0(PTSn RX_P6jrN\ IDATz0 =q c`]+^EK't K$YWXS[K]C#.Mc쐁hdڿ~UQhMiJr*Ԇ#aw# <묳x_TƤ1srN$g$KrO9LEe E9Ɍ7_м Uaм}ȇO3ufZlE9;;P13RxxrQ]_pӹ\.v5 uܱmYYVJI2Ӡi4&Ў 9>\LFD+K.p:zh_ÝMZPrN`G,&:= &;-caH ¶;|~4CdzCθNCN9j:#.O+0Nh3ƶ TO_:S3ϧz ;n26k( 8'OҼq5+ލlD5acSOe+Twz郆;qUS?JS(PpzzkdGB[Rm»771cQi`"BuP\νf#mp ]D)[ʆ0G m]KفlJİMa(.\x w&ygLǓd<5+8I'$afƐ,v|P]þV,!(J10;@sHv6Fp4N1m64x8Idd8f`+Dz,w|$:zYQ5+fs?w( 9YY2d3>YC!(/+K `{u-!2n,;43Wܴ ǩ1mA|&c6mNM4͛>V+Eoir:DKc'Ώ0P45/ׇƝէ $tHeF݋x|h~?.z8!l jQ]bFk0թ4Q=4-Fx NJınTwZC8;e9 }U}~\LY}sgχACn+tOMCuQ''W~aYԠah ߤî1&z48-^i]hj&nhآl^\ݬt  gu %yٴ"Q֬3'm.*R~yyWԑ#yNSr8}xYf M VcSSPK nMcPA^;p,i>/0-b:\yYSL(gj641|x#\$/'NCJ3zXߣx ƝsV%GGE;=C>{>WZ kfVwһ\Ӳh!BvA~"I/9V\w&WlS2(0ŝHA u)$jiaEXx4 \EUUH.]U4Q477h&ggvRQNU ɾr-Z_UnS5J}4sw! 9D"!lVNl1SO)Ə%'5k?ee&iOoNKL;QF\7PUUL˲4^Jni%:IH_ !ť $aZ4$xnB(~A;*~a5L,!z< #2H؊n!8q"~WD[E PmlC6$»_Hf-NYFCa9s7Mq-99 `WM=۪jض4Q ٣&ѡpһ4McȐ!<|[nwߥ{) 0$;ɯ#1´mXi[쪩%rAx4lt=t) kS%,I ÏRo!p+ Y &TF&x-TԺ^ZLσ[Ӻ ]4 >^LM]$fg@zz:g#{\?>>a9Yd ?|^ݞnW4Ztؽ`ԦeY,\H$ŽݻQ!?y~>/aàU7D%tvx '=[S{D Zu@eSSL\.f͞_̙3),<Dr"y]t%uE||ŔͺI>D"Y "Wg`VZ'0d(/+qc6kijfrBGizu}zB;!bqTUNtP[g킼I*N!-h5t*Bt.M#apa>iT56 %j&fXXB0p@~_qszJ$Ɉ-ꖽ?f.!zT3.xІ_u]TVVDTFgRTVT5M 3\ϞB)( pEn ▉nɎ8rQ̻Ϗ_Pui)!6Lj#ºǥ1*7'PضRM$FcK#&&r( uu8j༡E lil5ax())~ H$5̯ ꤅>R0p7p8ܹsy饗X|9z"AC~ˮx\.LYЦ MQ\XB[h ˅'''> oV,%'1D a(.9"-U;\_j:pD"H~-:RQQC=ĬY1c99}c B<쳼Kڵ`0H4`WUGVVEEE\r%r- 6O `ӦMK_F%Dr"q^{իW}v6n=z4cǎ.;ӕH$')!ibFQ{eIBP[[ˆ ,/2aj2uYxp fD"[NJyW;ٻw&uۑu|l3(흧(@]yv%-!0-iMͩ b.bf̘ĉ9r$iiݧD"lٲ5kְh",X@mR4 rGFT53)s`H 'HOtQt\Si~i۔rUWyh+K$8̝;Ӵh`h ) 0|pƎә5k;CTTT`}]֯_ώ;Fϫi DdEG"Hz%{[zGD"9,N:̍7'; }Uٮ&?S'^{߶}]  6Lw*u44&cQQ9[ZZF=QCan.mBy ls>KNm8n |. :[..2{>^)H$Cb& 5^-NG}$t7w NGlb n$ n @za`#PU#HeTU[؄Z `wģGS >ktrs@'Ǒ p)%1;hBD";N:رcM!˭DzqD8xc7l0l!荻@ 4 CˬYB`9W< i=;x-ip)xr+ nܪ[Z[Gs[D ι]s4;wz $Drl8@UUE_['Mʧoܞ4}[I-M{DQ)MSq*.M\x\.4MŴ,vz6p5 D֩/|[B?3?\p_hll촿ض O "n` AUC8d%Hz,nCSuoT](޷Uϟg}oH$ɱmEu:$=wIҹf߶x-Nq~> }~s=yQ\\X̙3Yn7t/Nm[0ҢBvUe_}C긄q>bXlm latH$ƍ`H^EN|=XOtL8K: if20- #TEvwB7--i|q3@Qʸ[ws%%%<L84nMCdIŜ r D"8lA( 'tbtvgB2 ^!LmAmmh.Iv=qW裏RRR$%iii\~L4~⋝IP+Y>G :,!M;y@UQQU'£i<.=n2^>Ƕ{'H$I'Fճq_}mm ~>Q \iif&l VG;Оr\r|>@Yfӟjƀ3g̟?h4iE@A{b GdTr*.U!j$,"i%3ٶi9 -D"8twgAss3~M:#xUQPJ2 c¶@C6mtwa SUD~())3zhFͨQ?W-[i&6mDEEc߾}vJܶXw"q(8y\ .5uݪiC¶ѓ9,!aڂ-Z\>'B"H$Ig(,,Yn~ ELgTj f ^4=꘶-[$GB)//OuÇL-2ԗ瓟gVjjj;wyf6mĖ-[hmmM@nCU<*dӶIm u'*"̓E^:nޘE̶?Oaaa_D" [naϮHsӠ[DL!)PydĻKvqiX 4;ogddp3m4:,ƌCFFFx⇁aqbp͛7|r.]ի 4UrܚF4y@¶l$)yTvEM9s>}1D"HzI'8_י7o~ᵙ;*^B⦕ 3,'IfĉuYL6cǦ|RH$l޼K|rV^Mccc* H \.|n>3=йve$mA7O<񉻆Dr2sR p2q_۶QǍ혳5UE$snYɩnCqq1%%%1bƌèQ6lXt]"OuVطo555455a&. ќi.MEKL,ɱMf&4 &C1s]ED"tBV\3gUU_Moi)ȑ#4h%%%SXXxša@MM c޽݋i)) .U6qbA|+_aٜ~$&B"H$m$ ֬YýoeYʠA6mӦMcɔ|xC/۶I$ZV^ҥKYl;v0 4MK.gܸqRXI$ I/ڈ̟?۶S?Pr!ep IDAT>/vRI$@|bD"H$#D"CH$"D"H$)$D"H H$I?D D"H!RH$D@"H$~D"CH$"D"H$)$D"H H$I?D D"H!RH$D@"H$~D"CH$"D"H$)$D"H H$I?D D"H!RH$D@"H$~D"CH$"D"H$)$D"H H$I?D D"H!RH$D@"H$~D"CH$"D"H$.D"l!D"D1E!??8L"=RH>q444r*~ ?]O mr*vsgRVVzKXŚ5mv‘=EܹVa8ӏsNlN8PUUMSS#P h4(.)&/7EQw%'8UUhoY3/v1f\vIƝʌ#++8oQU3mTt]'mv֭@<g]O2h@xԿ?ǒ. à:߿ ۍiA0 0 ***I$x^I,tx<!a`6xPXÇγow,jv܉Д,  Q[[KkkbJ_}TF#H4g=wkrp\XE0$*^l<=ƎUUtݵmSUUEEE‚BJOk8455k?{(*(JEvo)~Oxᅿh4*Bu#?ؿW^y˹ 7~I N$*+E+&6ӱ?{N麞6Ib߾}]]]]-Ɯ:>eY!V\%>{E!];p0կ}]_^X^չl/u:g>$ *JoﷻK!SӟivھpѢzZD1>3vX.W-Μ<2x<`0(zj^>q3g]"^~sfKuurhu_ť:wwW+>}(*(%VUu:YFt+>{gv~3vbe:\bnkYԾ5^ץ7~&aÆ.u]Z*U9wxs!C/ݱb Q\ڻvdr#BrժԾۋU{b>w`Rg!B⦛oE%E'}N4%F /!O7Y1tHQT2PpM^q/~쩬W_Nǔ"nbӦ]eXvevCŗnXfMpΜS䁟 qŧ_bݺ[nũOyQHOOO‹//D;v(Buu=uG=| oQ[WM1۶oByzxj܃:<ߒH$9HKK#b7n[#/|S9u{{1bF>0YaOHeYWf޼0UUI 㘦I(½;vpם? _l9_465>4 EVs?nmm>^H$oGML;Gy. M}]==;dD/vKaHx>e?~sͺqYa^ۋ3iӦEKK+K.套ሾʵW_̈́ NxwOu#sUWRVZ-l6m_BeY#~?3g^Ĕ)S΢7X^oe/,,⶯}XI|']`(o* @6k׭o عk7?\ͬ38SYY _:iܵ_|>Z|>onyܭ흅(.*N6m %SQQ!bعzx";ٱsol.=_YFş暫S={*S5W_m_ʿ_{;oPٶmK$借}NBA"`֭,yis.Ο1pz˗BGؾ}GR5b3`#oirvQTMUH'`{4d ~{)**BQ?+ؾ}7mNIw}CQU'~>WM$LQYs]O]J$fȢER;ظfyy|{KfvSJnnW],Sp߽wSTTX3^G]iII1[SC/C;]xAjTXWWGmm,\[ڿz;ƌ󦧎dggCH&^{ W&K6nL 1cF;7v$;4\v٥x<^EҎYZ455M~^{!C3bp֭@$aUZ[ض};3}pr4h4ÿ%cǎMݛϟA]}ˀ3Ro;gss3Cs>;ui?뮻4+GwݙtDQy8mxjz{!h}5L0˱ǒb-l~tם<;i9{yx<0{ӓso=MѴ*\,rJ9?z '//kk0M.>FLèv۹nSt:bY?|7RZRBAA>yytjm&jI˜u4rrruDBG 222S"Hm{I)uew:zz:SN&?Kʲk.wӦN%??/h*Bzz:W_uUjͩkV^>&NHu8u}mNkk+B|>=3iRy쪪vy'LfF3ΛEUUTUz4o!H+n2.8F*QQ~?ӦMaذ^^xN˅Hm ]ʎ~wGjܺu|xߑEVV& =8B**PP߭(+)mWzj OFFFgh%)O"H5lʺ pJcG.Y5MÓmFP{,ѣ232OGz#==ʭ}.;"r^*M4&-ĠA:m:e2y9a*TB8fŊ3𛑴h3Vgc\\\Ys\)7,8; }V;xĒHONІfq{*u074yV{/\DŽ ꔚh3;zAoS:x#l6xtr.[^Edr )?ޝhڃPíavw+n7Y45|̊ji3ƒQt/xxNiUI~wDŽ:L db\FFfdeeqc:u #Ft񃗔0cy{yvM9;vaF>u%Ii:>kzjFcBG/?s 6TUb>,Ï>F,gǎ4444O3Lp~RݤqB(@^ UUzÇcoO9۶46ߜ6}"rRL0octwzl+%:bvϚi)S>0+U]co˲NTGzx4i*W\~ηI MSSŞLr5W3Y'P2{L.|Dmq=}Vv;xzr8aȎ1]455q~HscJy7wW4f̘|X,NkkkCUz}|mIRkB}gTAnO$4I r~NEdmT 58/g fL~i_21u~99{F"Q"hێ1x *T9jtU}+y bh5כJe& ]]#B9 TVvyN'~wOaO?1GqXISS/6qԪmɛoz_Z.\ڽ{77o;1M7x 8 ==n˺.kjjXgxr]5kt,kSKH$*7\֭]ǼgsRR9^\8y~~>ꨬ׬eϞ=]ٻ?Z8|np܎_u7 ƓNcS#{* S"?^!+3jAu P2d0Ɏ駟!0x`b-^sx#1l(wbst)+-HiOQ%twYĨ#2d H 1fT`J^n.Z-[x1<>+__ѿu$(By֭غu;<8SP_y&//ٳf2d0Qjnx9lu=&.>Zo8Ǐ{y^ IOO# p< 6"=(uu 0۶Yv-ߺ-fҤ3x?Fb~19,a ؎TUUQgOJtD" xNѡCRZzZzz:SLaoaY6ܵ 6ӲزeF>c0}PWWO;ma^^.8Wq:Dz=嬳dR}A(N?>IM9ynLw]%KD"Ï>GSULDu 'ǹa=_*.zU|!lb~Gm=ٳgt 6/]A6HiS R`:n*}?ۃx<.))qiqb\7n lW\)?~<p8̾}5<驧Q]OPTXaCؼeQN=jS&Ɏ;<3Y^z\.L$r~wjE\tх<3YItrfm2ILO| g,_ں:yfbY6pkS1}z2999|aY|!6mIZRX#bQٳfo~K4g> {M0 08m.Xx Ē~x!T[]}=Ý\ f^|!ᄋD"?dMx=^N,JJO8ƑO EEŜsY㟯SG`Qje{v׿ҥ˰me+ذqsjpeA7.P9.MӘ={_(.."'犇hlj"j0L^/yyy8o:_/g2kEE3BD80q{<}>|>!E_~L:4@$ .d6?dgw Z HIDAT4mD444H<QTX׿n1{LN=uLC!ill$tKӏ466R=wŤIgf~.>JǷrW&P C؄iddd0bp~_t{}xL?ӝ`0H}C--;ϩcF;;SwAQgݳv Yaq]40 #jNҦ!ִ6QcK&34^Ҵ͇N$MGkI3CD[k wz8r.{9jc :DQrc4~#Δ8?!%- A³v'?kp8t8sR0Oa {j5 !(žv#"":]C~GDD#|R퇷^OS9汻 h40hJ5toa= .>K.':+ϵ'ߘĘ#"*jZ-z{{ށ`|-"ql x8̺A&LPH2\EzuvXd ?xIv;FM2bf\͛p$a˖LTj={7wgUۍ/r455 }EbsFČA _*ܱZ!u:z{x-22C:]V+T uvnRSSdVǬ9=}B]DQDauܪDN ÐƄ yW_7C-|tDFV'}V\lAyEŸEZjV+ M{.t: H}DI]mZZZEDGE!66F)hnił`\l@k`XҊ6!:: O/> }~8od2Vлz* ** =!PxVC[hhh@kk+$ HtػE\VX~s"ڑۏ;3---8oۢ= P(0LHKK#(--Þ/ .x[dϫ_HHl](@R iiiCRƦ&B$cu">>~Pii|7%%e2žuoؐ25^&|S__<T*դ(*B;V$6Oe](BI'oo!רI['0vvT*' n$њ0I bSۍfCOO4j5f^ݎD=,7F\G%UUq!?=xؽ޶r M_y{V ejjjg~!,l q4bcgcs4A!"N;8NƬƹ&U06N1JN@ԙ2V_ꉦT*'߁prOT7+T xرG~83="3hS*xxM,#Ho=ϋ`J2!$8Uo|ˮZ A#+W W#}F\6 STU0QQRV!LQoR)`%NWmsWoT*dem-3;EuMEQJ%L xW|I].:z 9zGAǏw{~YY9y;) XnĻ|g;=/\wd7̇eqc` CJJJqR.***ށή.H}} ŋm[")4dڝ;wE\Vtuuj!$8;vi6e1fUxP^^y]$, Œ%0%& sf$&=z7)o׮ؾY"hq\?j7xJJBPPШ3%IBoo/N'DBFVM\.zv#@` V:n?7a ""DDD4dHdHdHdHdHdHdHdHdHdHdHdHdHdHdHn3JDDD4xHdHdHdHyi#IENDB`structlog-25.5.0/docs/_static/structlog_logo.afdesign0000644000000000000000000033262515077624341017731 0ustar00KA nsrP#Inf/oʅHeProt0#Fil(/H tG=0DUxuTuDTj0C,& + 4)}UII" Lǃ % jw W5E 82 J~ۢHgh% ET[ ط%N^l;3"$Qxl:BXX mU)d7h]Zq ð DK ˑtŐ}s5$~Y3oٲgicRQKV,xxph@QPZux/,Y9@VٍM;Tc47-T=zz*tAI,MJ+HglQuu~I+&%# ' ݰԡvETl3Jh~Ǖ}J9?=퉝l2) K68F闋 JcJatFVb#T/e#)fpɪ*ɲ+]ݞyeI[3؞نfݔ4Byr?D&Hb e_Wͥ̚&MI|(Q&2_K򥴥%K3 F~ ` DvfPIQdon4atD5͗ Y|KhKRd\:"&gE @8*fO }[]u3mif FvlP}`%Kg'1|L+`L~z&v% 1cRB~y+Ku TY4qTCtͥ%UElIs F9nxd&l(jVIRk|Ρ!iQfh1EX\)" !._#65^qeQ慈 ah+sqc $hU5AEy 5.3Z7[ X6@_bʢ&fQ<׬P 솣Hj͈JA^"/|m 6'ʖ$0* z NيAD @斦ELOR^C ]CP<ْ\Q7h+jtqF|Lq+G pE Euή{Y4[}R!Pmf<1_ p񈠄DH^hXVDo\&m ЍԕCjS pW7,P 6E5s9:!늬+> Ǝa P~9/AT ]U殺l+֜r!o W( `T&Xx78Ѐpz2\M*2fcmIޤLE2Tβ TV%K]:ohflOdOa0Bu)ҌePC /IhTsjەt3劊?IV+FX}+)!ӣmmekn,:5REZXJg7iֱƔt J0u=zUg}lxXk)#uTLP@0V&q3V3˚:VIBA%,ilf\U2Ewjppj(w(8{ :2Bĵ00U @wE +8Z{"3Z(Ot}r\_2u.GͥX,Y M *6M̜r4(W}_uA͠ьΧn<:[!@N K>?SZnv ,kKꬄHCζB|RkrC3IgfWU0$ m%DL='CR}@-]5OruK6=ECe<A}[1d%È 3Ô``v+ضY5ݝE{c3Lݝ={dglZcLY {c ܪ5#ݽ]CIϚZ^A=cdX65 cMȫ)Z?ng·-3rwo;o&5Ce~eW;CtKL˽uL˿]̴|Ud0,th !c†U ^_lSxv {axw^g{rjܱeffow com-f{חU-l0 x34xvRDa'HPQ)-hJ}jmY:"*KԞB#!4٨K˜be&b 1,W Zm#^5n3w.cݽ‡IY ?{Ctpgo(Yc h߳Uskjr=I Qsmk݃0rqdݱ15-wwhjT>ْ'Z}B۶f݈bHGXA6PȂ`@Nv*򡞑AԵ=wwwu-kZIS4%c[ba(bpwQvVv_gi7) 5%:^edL?λ S525Ϋ{=BhJԔOxw=Ċ l;UdkȊȐ//3%u^5:`l`p´UKXibR,=.2-FƆؚX  R[p j-(zgYy)swB#ߢpwߢ|ULa+;+[Du4!k<>b UpZ0_=}30J|P;#+[k]54%1-1v)rwL-&jY4!k=ǘښ/byIq/}KCG63JM 1iG (c]t,__(krH9U= x`lE`~Au@ )TOv5fUѬAUgw4(/}˅XGF)jPH!lMe; AiiHV!=ҽgȪ7XيIvYk+gV5+!^Дyȱ=7rCmU[vI0ovǾX2AX8c ]qb8*f=\⫲=U}ɑ N.m򀋙߯H a{4l5dW]U{˕SV/E4g}e&.-H jEi*S+NKsҽ' ca#ENjH3=sF)멶"xy-Y5k5y}Y1)mE@ It/ +3i6]0;t̶D#JIYlC5*f^MslWcE! Y]X#- ҢFt/Ң)lZTVP B{4$*pJ"3ҽ#!cF#Ⱥ"D^G743"Z RcN)%!ߚʚc)Ȗn.f|rto.&t(P(L t c\Rѡ@ADkĖgR̀:eRW+B#AW*TWkJ88^z@'"ꤱz%궣 f}̆gyUK%]IQAT܀b",T([l:? Y0a[jHUP hhej\,ye0k*w)],r.8{m@\bɂ41YNǂLJdR?j$eQȱ71TO,A=z!iiL0veS5b%N 4< hꫳinFCQ4Op,0DAȸ%j<~3o3a coD,Ԧ)"!Njz @aJ'3r&ve E&>IH-rB'e6Mͪ+lI3Ej!<0FM7D E|uWEs:&fuu˚?-YV(l @cn^qPEI4 LPʺ5{(~N֮MJ*tY)We_uԦI5$u6VUeђ]j+:5舥%NE]5˞>:jPgSnVItEǾݲɫ}9_ZG,g^uB u! ҏI%nlIPᢐZ{]eM%aߥ&\h %G+km̯LGWu!M \T;@ģcmKV`9 =j:sTY?N ^HGc84#!9RgVmuQl{KLQ3J5a!'A4Ւf[yT[RYzEǾԠ|-cǦ`Ϡ:xǾֺ$қUG#۰fi*'d$S 42@YH(U!N%=(U%e̮#K j੪'/f)hKv]K]{edXy<ۓ,MنwM 6D ii!S_pJMPږ%G=ɕUӻ?f]P C;M%!"Ǿp{}lxrS?k ʶ/U/RJ& !c[{l+!Tu+ab׍3}tج2dһ?dJm9Ra޹Jxe\l$$wHp#ju=]5t]̯9TE߷=˾OWDRm'TPط[c'mWGrRO]8v% `yǾ߶2cׅ'Dnl6Ys͢u̧:hj}]È&NScqv}. j!eG>7\$Ak9m"~.У]GXMw B H< exǾDK}GbGl.;m"4zZCժ4ko.!}\{T"ۆ"{Ky 5ddFY=EǾ-ҀDP)ޢ_>¨l7"Jg1[ ވ3gZbcq`Nv2Xdqz"?~4D|q6 ;ST)VD_Q O0T܆C K6D]UKfW.\xǾ蠁"%XN+XvxIj}wI.FOH. P%dV}Ǿ0 Զ'*zǾ@5h{2XxǾ(/i2dAՋ@< l48zǾ4Xrj!UN yط܅bz1Ͱ 2d˚#A!47X}Y䉅u+V$fBlط\V6c̈́$dc_HAmfA H!E]1~BbP&'!Qh6MSg<üMH"fߕ ϒ?'F[Шˍ'=v8C޸0|ѵ4L!I҉oW&d3(lF}_ܞOw%J(PN}yeDcߑ(6ūw[ ]pV8'mĥی1 csx>THiTszܴ#(#+-Д c4YqOS3j,u.Y`8!~Ǿ "QVw X5kmf {-&o*nB&0d​w[m & 2x}pƈl"gcy #:e6Uu.S#Jlk^[HVMׇDo*jGR gMRc޻?6ž |pNzA+FMG&4;vN%34L!I+ݝϑ>ebd TuEjQ$Ti"Xq* U*b% P2ffV;|yn|۔w6ێ{w+)pU;:fuUM68ӺC$uj@UF: `DC|ѼQ=3"Wul3'`j "ef͚ 6Nl!J*q0K1=8fa|": YD53]vEQ? ^"J_;l!ؽqIsYDq̣Y=ENp/WvP-M̺*X4E?6lF3X!c21yǾ&I!#le%ɧzMݝlɏm JPYS'Ⱥ5O]\Q4W݂^3 UkhcPLgـ :B`cT4ikӽQ#ǡw $.}=GD,]\u `I}7ԡ9dW5&Hj4lZwJe_Y6|}SE o͍һ?݀ȊXE2@?%&fmߜ1ږTǾiaU 3; , :cbbhHQfԮSNu,ca/t0v$- (yǾŠQ䀥kSꉝ-Bw0@l6zryǾhA '=3,(]zfÛhߝ8;3yN5$!/<TԽc`RVMq!EeLw7uh^S5$iCgҤiNG٪(lvw͡|juk}U}!e")2{A̫w;0f5["vE^4}{ah.{ M.T:q9(LG kU7`<>1/~B@ǪjuEv,E3#yf4u&Eh.6ui&HBdɒ{I;Dm_&Q{Wl, dC\4L!Ixukh,fV**aFQf%u]qK?nE }iJQ%]$ ܈cn/h㡪iM'w Q܀b\E/xw0}WRNjO qs$cm,׾so5 PVWV0ntÅp(]oF.Kj3w<3:#$M}eJٮA*:E}I&ޮ48v1Wsl/`c Ph@O;{fV,_V [}vzm|x˱L;ѥʻ? e6ުmS طܜ ڰ"!ʻ?̝NWMb ϫ]ʁݦ)! a'HmF_t̤ YEWW-EUcj{ ƵѬs)zǾdFBhVT.~d఑BUzǾ@DǥaE}}jt$oѩŁe7O/(;۾8RN(w͈/(y}0#(!3N؏w[e:6- o>ZB 6,ȄQBP竲KU :fRIӇ ,Y}0dЦ}'7F޼chhg2}sHxU˰-falӔ[[.yy3ytw=C E+,Ke3jgfMWQ-ߌ{": D9'>]@ٺհ!,.KE}#Q' xǾᄨ ȍsIȤ ƻ? F!! xǾιd}n0ȓ»?RqmUǩ7bw<`{fTh**,طZVR 6ͺ&yǾ(Q2J,/ {Ǿ09#NPe1Ǿ˲Dw׵D".8xhB_*ޕ}At7`fw]ufU̧k&z7W]yǾ,17>",yǾ&x! -TwZ@UNGB9jlY]'SC፱NӣoK,govPͯ꼂"M)-Z ďBű Ւ5G1xf u>wQTϜsè\$[4fSuA쪮؆,Xxx UWvͭv쾧hy\O%cYA'v tN' aRtN/}}( }HHm\κ\wI ~L:Xھ5~ή" Pp-I55{W5u.SS|CW'ON<ߠ!J≂a֪f :hfrCWTDLAZC  =OpOmɼqb\h튾g7\C/D}qT!ƈwHItzmEaR!pf1YS\_)؆cȆ&sh~[Sgl,Y wZ~DUǻWp|2}qT^-Dcq]ԲbT' L1ǻ?=QBiӾ˩#?s1݈2ۦhuw",X8aO]F-JG|G$`!.*g{f ufGSq5Ǘ q+(!@ FжQ d!n F& 1A \g"BP;ޢ:=B|̛e;^ E"UBڋ#~ml !_OoCi^h `$,t1`ShHiGPx-O&*bXPD=TQL=, uQywiE@9\<*(G:75M(F5m "C )vR76#4z"CL!EXtbpU kῸYBVU`{1r*AM^oIjVL7 v`Ō侄:>&n 92}eCR bz?6& (vv$xJh$KY"D1O%jhFmV!e-zEjڟJ*G}2Wp CTe^ "4}!O 9aG!>lZcĦU8@+C= ZBE7611oY􌉪圕?vb"Z?U(-ϒ 5 :KP."""[Igb#WodE vF ;f \-jt+0HcӝOWc@ҰTGbƽLia[ `0NKb*xJOǮyl 4PRЫk=ϕ]R.ב q (ioT7 2,CTLTt r yb$XHJTM9yfΠb^!H(vQzRY9e)28B1C僵MV@a;VYBAD!ʫlKN0rkA HBC!Ù^@,, h1[g b.Nia@C1;ͦe,f3+WdB 41'1("+3hlbv7ƫ\B9HUX;ԒզϢQxH|MR,< PKQ EZ!A>3jGVirZ@ *:!PN+hut=;ۆ8a!Kcؿv峰 Ͳ.tA4Mt*3% Bs+tbmZPӂ ET7 86y ߂xY|? %ޠЍ?m&9δh|Q' e|D.M?fY|xn ОnAiLm F` ynF 7R:.7KD_IM{3G ENMBrL5gZ,{9`Pru|g@gW,߁*IqsA4pTˇf0.;$!78%UM4}c,i&|uO'YN?wRc8Ny7 " ʀІR!snU apz`PD JzY{ <_ kf+EzoL)GYqSoP5/18 w!kCš3MQIvIBg9zgg e|I;$`|$9.*4 _zuDKITʲ,SdM;^(#7H~KZo$D0oy= TSG|,c=(=x_IE,꽉: fòy|B*8 S`߅I- ʲ\*2_-@7>vgL{o[ֿKWdԄ}fȼw# &P(kQ <$_ԛU1ʿoJJ=X7 ?kh%[&PAoxaP-r#Aݐ]@JoܞZnaAg?_$Ӽ߫tHMw<^o,DpuQPo|f ʟsr{t!Q4[ITx{?1| ,)U]x[E|A2L4;W8twI2eg (&2H.S[ ޼-i݂, N@ :[ |ɝY9 (>xI ?@h8&=+ :;˟,'Qtx ? [/z)J ޴[ ۟801(/f|Vi>#PDkv⠏xtP,@ss,T$^?G|;BRuw:0KY+ fT <=ߟ 40cg˕ }O\oYTXiXH([pkňz)w+)M*T1 ?szEŒE *K J'-bg|LTN;9@I=|+.MP!+s:#*Y.`(|zym|uBZda$*kBi|uhߣDzT,w (z# ߟ +y\Q`hV Sn?YB@ '5xBo*VY2/=!H_:1z4@e/wVd|g hfRl3*4?>p(W "RP=IG\w&>kLi|-p_({@qϽm?>'@:H1(! Ȋ. u? (nxWbAٶgq7o>(8^(Wqॏbe+W;ifQ 聧Jw ϝZ$Ne^$= E yմ(ҶQ`L[ezb0Q=ANǿ} xzpx U?v@gYk{k?Ě:|?3F iY өTK]ԊH J LB@9Z$$F@O f}%H6x̱$Al]$/8!&9d2P`<2Qsq,q XSj #:3%2y1eÛ7~x:f-3J1T4!:՝]|awguom9<R Xk $q8Y1`X2+DZjQ}&1[4jH!@ p}z:f*";)X R,lq eVc;b=tn0p:] !0*I6:>k@2F,( .۶n~[%}*}n,AqQ\$tB=I%@u !?}޶9mڏVTҏv۪մR$T40+q ߍ(4k$ ڡ{|yfg]ɸmAj@/+ a Al lzBCEY[֘YgyǨZX6YSj s \@Ȋ%HC@б6\R҃p|Ӝ`D} Am4Qx3<cC@AN6X5х?X =V鰞ejأ%6sfe}UTwq:9dxZ= r9 ?x F[d G s.ͦVaQF159AN./4xz:^s TMu4> &XC)8zz`Aک\V_շruۤcԓ#!q?踌 K3~B)-0AeD;~I +x:&;7nX#h M,fkm1IFk_f\ֺ? GTa#$sU`-qt\ " ܄&(R#@x:&fAykaZ兓:?̔I2+6?>)B:&%PxNnܟl`ki%* ܪ2&5kZo.˶L ¯)pZ)AVR @γ^3tL!~a;\7tlQ4<[91t`3"dPK Yv'/-׮YGwo0UK)%*| g jS+V$岭Je~flV[I`B?8M-VR g*d ˁ.od9j,ˊѧ|60L@n䶶g2oIjS{. rGࡢ95p@w1--Xͧ[v_w3RmB `QMls_3}e3㺪fҀ>Uw6_>a9cE# +30 ,nZٿټk_c{lG$oՕUsORk4۲itń-c %sб 8\`evYR*!R<2cȒ% q.@v)DP&#CP0<2k@eÌ$h":TjJ),$X]`sڍ;-qot:RJG4t-2-CCF28:Q Y-. Iy:<\yTf 4`y o?l$p?XL PH 9D[}FP$U0WXtxT0$AtM",pxT3X2y:eYM9ORbۺ͎/'go?K) C2N7٤d@B4 3À R@tl'1(a;n8c:б clGҜb *=k"/;@P:9J%1G\0W>#0m?81X}`;a((8AZCPWD?XK:lDYw*Dy: DlLxx!xM^.#ń!\qZkFm-o{7kx4%xPGhk1фn xsj -;atIYs;st|$‘K8JQ\=g$KdUU(RYdm&w/ԟat|D)J,wy?XppBF<3$EnFg&6?j6}l[*b=DpNP<\ݷ6v㲖p075gRg!T NMDN"Wcrvg1LϿܝt<' v \,Ac9pϨX dL#BJ$Df'k:R,s˹\0a>]"cCz:CH[YpE#2źΰ{g7}q-9c9pb"/z:CM`ƣjOɈKŮۙm>;]AWLi&%h'cKx@sXҤ+V^2 ңCwc}k-wqˑtDŽ!< s ~$ځgQLJ‘崊m۾~n7ټTP$k9GSb&Gk:S ѢhJX|j)Ku(it<xZ:AX!j`-e]hp4h9%lE`;C@5-tF&SfSOwF> "P_N c"ޤA,F{pZ<ciH CCa[BrhμͻeWuҧQ]%?7:=@sG2c hD:vU`j=oŒciB"G(7@ f`qukwzqgܽXItBD*62Xz:v-Kh؀ۉ?5Ü2hc @](J:zR%gScW%<$ e5eu RD]H*uҀSP=yă$0H(𡂋 cհ,_3fޙ1fiAǮEDA"J):0a::(+Pʥ`p! ~%$j = _"G R!%𫑖.JiA.-!|) ĴU? 4'1%WP:cIL 4(ВżcȈURIƒ9tle"S` 25xPx:R #:*ha,{Ie"L VPSWRI`(6bsxHG8 (P+ȡHaՕ R[ViK_m~-gX1(O*4L1opYvDs6n^Z9j_ט_G~7i:CD T'jPd th6I5 {kndi9DnRK4U!%F\Q_l5 Pܶ]qegܕAfsUϗ 6A<7Ms\iFJla{ewwΟM_R[PrQo\muI_%fɿZߴ~{ֲÌb\Ƥn='}e-eV Ħt~6Zrj6<oH\|Rl KⅠ2295mܰMurYev2bFص 8LF&LF\6syflӗܼ_9f5,UTDEn؎~v}*]i! ]KRE!E<0 Z7Ql1j$&iկ]yĨyc8`P]Xtt: ~MYKj4=KҨ"X"]QAHO6p/8p! Z$@ A$.,+HrwvMKkإݙ?w45 =іrvmkF4"fc6|hhvjsFjި!:T5ްn;vg޴uE:A NKfάî+Etl s S+DƌQVoLҿ]쾳Vϫ?F<#M H E֘{{s/qY/?~tRi|Ym5mֶƖϖirgW?&u+EmgAX<O7+td ahAA? z4eEPC֞(p4( ~5gy|$(xB/:U( Rbu=Sֺ̪,UlkǢ AX$'=?@+h/I] tL*-8]z%s0 .a !tC1Kvi4f1;))n4p~)zDփ?ΐ訉 8 %#V6x:K&8*"isq%d)'Ǚtt\FBpsq|Ms'z踲B(Rcjѩhjb O;N95Mfs1˟5~Xj[Ѓ_5@%1PC . Lڇ4I_P,pB t$$Pzti,KioיϬ,7 ]<ѥȨ' Rp V@_Z Cti/9_zH%6Hy:*j`;a0 <c3<bW1E?+ҦR`z ib,xC0>SPNTSw1ξ4[o>0cv sqiR) = Bj !$d$$ HB` $< Lk'Aax"`2&m.h^Ap7cLp:#7NƁ2scRO$1(k|4`?r»3(T/_3(NpYuw1Sedm#x / ƺsilcK[| A`]v2Zj8- 1aWAvCI "KAJ(1_1qB Pȹ 'm4Y<60:'`zBu_R8t5 wg{ s8C\S}/!C,Ha8_|r4֫e(?488Pk>!PR9H\oř<݇4;<2ҩ^_)h"pت“(j A)׻˵_y=0`t&*~ʾJ_ hځmh fUjBedJ(j"g$^N-zd?} ŐIB6\gE}w g̒>p/<"FddXH|gme+h\<4K+kӟ>T,eDS ,C=İHGEp}'Z UhUE,2o&٠I')[>s v 0iq!^K=DC!OALnԐ/Ȉ?)H:Tު3$8X<`[)Zw$uP5Qi ^Jh$lfGad ]]--鵬nrBƧȊ`ݶr۶FQ̦e N}YS8į{ssO>z"4|X~մCEFnȦJ%E*Lp>"]Z43(rG9P@8%[α#! 9DʯZ&&1Z:gB'; B'!Uα#zߘs$bU'<r1(0}z ͹vQa֞ &n%H(D`V&p,, *]Дr65%'j,"pP9bn`Yk{}{=V[iRdВla'Ren;y$#to*F|+oޙNESk2I]#4rZH"^B񣒼m)ݏ{'`zp] =IADg{+yq*)WqDD`td$T-*~.{@cl1xjN$UaMiܶ_B]Ԝr_*GQN㡝颒hOMz}ݼ=یqS\ ̈́r4"ezVVhiSp{T|_vmÁe]\P$MTFMm\O͌%>*~aZ3 ,zuqJ],n8eW\6U–Ud!?{";f^{QIfuv%D,7rqeJ' FFBOuRC%FPP; +K/((K2PAbAqȂ%NcP͞b@KCƜK!k =h37ŘfAC|at"k)ݏ=*p11o\bzmbX P\D/CLz^$R1rgK]ƭuDc  ~[G֘|I^øc̄ P܁$Jd(r(51FP=kOh*bBr9ރPL$(sh21[aR2fy] >0 y&€o `Mzq92CPܢljRB32k1nXb}A?ถA:**`H([Mτk@c 6&Y*@o=RIUdHf ,:K:pkKe| 3J)% kDpyT1[GO[9Pf4_5"0 TL_.4hn*G(&Qdaȴʻ$FLP1W܋⫌||vLhx%W)-PLp9HH6 :!7zL?1:nИ(|'}-sFÕ u"awPlLCpP=`{*S:ǩr^jb(P,= K nHU 橋*{pXJZv=驇iücCW/Lˡ$|Dp$VH^php.1ON[j&ƍP<IQUf bL N!='[OiW /'qL?B}BQ~9\f#> qQQ*;#bcQ yW`(kRہk-5|OR db[ qEpX[(%C@wO(n" ;C=pVV#:*X#g^*1TS";|PtL+%\P!s=YBTRmRNxEA0(A(V ͘#l܈RrX[$ C!I3*&dc'dDvg8WH "D)M_) E,$G$k0?1FBNWa<P̩1E8]+L5H] yyL0w9L^Zcc=Aa"Џ^ Xk('8pC ʁCTZXpk؉5C$8gYK ,J Ѧ|7Nr&D"F U y^,cu3]؉DЏ 8IZXک{*,,+,8V" n,o 1dȐZqjLc"(:nfy}+ 5|ԙƃ&VQ$y6+$GMVQ6xz~jpqbI&#f++55|bRpj c79pF#xdu)siqTV}`pr.w;HN:I#_ I 7ޱ7f%b#wO>_XKpBլgPkDP>+3'mvFLV e_P6;zf*ǎ2tc8pt<_bK`Rb )ϛ_TxWl*żR*-|aIy3?HUCO_h=B]܆㿠k<7s7VnFMUCc|4Oi_Yl*٪EFNAXA_d |O/4G_h'O zz_ 'O @N)!]iFf_M du bY½_b#6IOXq+YIJ n f45A>ܔQ>]NhUuS%Y=c[D) G(-,"Qd (:O4O#'(3<QUcȅfGU7!UXc!ROX۩Oп!']5W*=GX:#420Xȅ7'^l9oU1RGoLYYtFګ4Xk)3#Zg*lGļh?ƓUaTdhAiΌ5+Ooz)o)빙FnF肙aDל.*aSW9@ozqoyG#QQ7ܦ6ralשĿ#"7K#IYJHmZKm^l9=#rP/6\oP.Dұ*rGlL޿ob2_'OuBP&ߖ7ONxߖWۖ'R7∔tc"t-*1/#D4A}8N&DP4@@/Sb-XN/;5S7&h!MfrNXSkpʱ}Zr*<7PW QֶBKFPjpl<7P%V*d&e*+,MU@?e`DjOà-3$?>AU0BNI*F*ŊQ}M?XsaFijLe0tI/蓎 r xV23s~Ώ_d>~CWd1 M0m@3n 4eWЃ៑op Tù}&, >\K79  6L4*onA(0 DT열Xh&@CY%5OhDb/~%ȚⓉ/k-ϿdKTJI`MPL%,J @fa'%a$dd< TLPD(AiA aC XTv N~׉s*;qvXAr觭LD>-kmO[`k{Q`5YŪ3g'%Sy@9ZJg#h4R|F#=ӈj1ѝ4EFӠhiLk4- J'6Z8Vtb5}'YIL43OўNl/ju,d] I/ra}_*DghN`ghN$^d8SJ&rL!g~/ Nc Ic$B Dh6P քCE!A d `  cVJ:K5%M6t(}lp{RcD:E`7F"a[t?$ǣD3\D+'[#] >c\U7@pdM0 \kIʠɒN롥.48maYNkHBCL\Yrc3+<_XDOtVw*#M܍/'&>W iO-? ips,9aXeFXmpR"bB=>9',k *UW Y*Y'31Ypi1GE@$WxL*~ūjVU{`mYA.jk4 -}|`Da pAKÈ "8ڴQKu*CE!?t= !9_`}dn[)|D)h! '<,,aI\->Lv9~; C|7aif?Qo:?Ӎ|;jkNJR@Ó7*&>;a)ak <ȐNM s֮Hty[:_{N aKykP'8rZM}E%9=6OI̱ ~Î/XxLgxe`0uOYtϗA--h  ¨hFm[L,Jȴ@ЬdQy+z)!q)EaOg?_\vNIݜ}(VZ9-hyNZ=%#G$˸s 'g=6޳BUu)̑uܞFUaRsDTLjAPΡPl~R:( HY.мT4cq.1,G?X-"e 񔺰5W h#;|3U " sN>̪)cEfӒ2Z|^Fs豹[p[T5 N*Z͡Խ۔^:T.ibi7 B%* n$$LYgJ(Ԛ5^Ҵ]ٙ1PX( Ԗ"''O~ܯ<-7mٿ45/%h+撣jikFG(ek5$~D U^]̛Z.}S#U pksV[?k-XC$a䔐]ʝPOM6',%18(kE$iZr00ZŻl[tA4C ((AE}%;lgTP\) L=}[DG*њ~jyGppUTwk]4]遣@럯ʩQ7w.RH"i64)ʿ1ؕʠ!mؚb[)[#Fil(/H040@pX#Fil(/H0 RL)#Fil(/H} ?,#Fil(/H0?`HbL#Fil(/H0x ,$|#Fil(/H0X;H0pN #Fil(/H0O ,LX#Fil(/H0gСVǀ #Fil(/H0s!^7xX#FT4ʅHe/o H~xJdbdoc.dat 7B{,d/c {[Bed/dUhB%jd/e #rB7_d/f4#K@Brqd/01_#l B d/11"n|B@zd/21"41pBǸCd/31ޒ!?aBd/41d/ThmbV I PNG  IHDRq pHYs+ IDATxw|\W>L/Zq勇4 %BPX:ˆ˲/쏶 ,aЖ$ݖ%[hQ=Q$ݙ{sD"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$G*<@3f7@ q_#H8b H /X u4EY{} {q }D2H@"7/U8wRcCJ9͊n!3y/#I:F k?3oxO@l>D"v ]\@.(V%@د+T{4= F_SęG}0mO2[kqҔaqt4xI090M,rg< ` $EH$q5qv5y7(<J=/"&)@W"d{5u֕zl'LlLf8c@'RܚaɿYSO) K"L3HfٽW nDW)\, ixTe- MQºҐe^Vܔ:|B5`<^^dX6`R9f iXq"cg4 !Ã@SyΤpyqT&8FGH`R܁ExN䙔WKUPYrќїT*=*enmM2P, Yr ɚ$ "Yiq"@$NDzr^͙2n1S#2 QGj/p 8O2/^cU E!0-kl;Rkiɒ^l(8˃^܆7)W^')]lCW 0 ݂Rׅ=86pʑXt(cf-{uƴ1l3R8a_?k(,&\ض]i6q6+|9Ÿ.881J@EȥQUFM]0@O7e2pGqnr=8 %n(qH9h6Kֲ xSq5;gm@zf AK78RՐ"PqRVlLۦ+WÃǟ|$~5)pvjk9(*aU~6yh4|BB5^Ƭe$'Lu4gVWY9[4 ]1i4w>4^r>2B$ey8D=~`4IT°ݶm>?,>Oʬ"7Ec p]V%2@U˅_j*a]ǭ^Ey2c4_׹UxdL9ʩ(pm|Vqem@BPRi4G׭өi\ĥ+<0}dCt% eLr)SmBrAHrxb(}_qRJ끯y5MmixP`Sm5<ɩx {R1lXa" GS2nǒ@* Sva6G g2S~apxf& 09S@Un+/°)y3",@YW.ǭ8IK'5gܰōU**UnkY7Gi( Heq>w ׵Z4=*w+5n SEtm]ZVʖښUu ]]`CJLʂTr\iY!;JԸ]k0OiYb-%aCI 6`{]8;3}5!xQ"𪂐XZY;Dÿ 0w/NGBխڵt ;^|m[@祍~&N9Ty5pOXFdth6P"ò8қJTU/ ֜5:^2dR# @'«(T^Jݘ' 6Pzb7 XMI?tlaeENdyɏ_;cOTUL&5X][YϭQ!cʥf.##sӶIW/"cM+y%m8p->g,%.^GWD~$O-p4M4K\A=&q%#1r-ۦc4`:CHSLN(z҃ Ҟ)\#aPRjduemslt'ò9ѓLPUv™#=8I" ¹AyJˇFc ^`8mStZ) 7QU=nD8߻D64!uő/$GʵU*|@:C vHH *Ϯ,LpzٙM҆i"D @DRvix%tr6} ΝW$ri8aO;ϨB7 MI*wFdyU)ye!~㫇V4H$a(][W ?LOX:/J\*y>캀TP]5ڿv,Gc&SUea1 n.2P8uܹ]ean%n_lZ.-͸Ttx Mh,Fo*WU4|Ice;VrqP;YsBp]iF;_"/&їrWWrO=RUU4 "ϔklbٿ.ESF3Y 6gF*^ؗQ$_IzfӆO']1rYǎ 8βmzcp[AΣ ^Dup[ |AST&78qBAjũD8u‹*tMQpq"Cv,R8e<k.畟R~U\XaWg‹CP4xURY S[dEu)Ȟ>3 YWpͻwofks;˶r*ltVPVU8؜_|h\[CkIxʟr(BқLWITJKP =B8 I pMtPpVea6Mcj %r+,N/|qEH֚v.E2ũ;Vܳk#MeALɽS$%g|xO<=;6Ǜ7o^b^CBg/pJlr {)< 1ڦ}iTFuAE_UQN4u(nU嶥mx4|熵axmKK$_*m3KW zt;K$seC<5÷lgM]9  ˩^%yMK&Ck?FKI|aߋ{ E0f(3&$BҋԦ^öh pk*[jg(q;9 M^6gltʎxe {ݼM.P$ͽOD2H6{BpϮ |C?=8޶ qLak}hBw說J`~ ŧ\:'I[BdH0^S ī$IBSnY#k|> ,`?P4C˧oU\l=zmXG~r7UB`MAtmI%('0,PPN"c?cYi1,;sdQt+9@p >G(cL,ϫkgf66Tq2O3ɍPs9?CI4E7llb"}}q ֟WY^҆/ڏofyt< 8["M y  TlF, U6:c=`AMMI'C<:\:ڽUetfy]'@pfM>o4A_zO?T i údt'm3uVUa&2Xg'4 c&DwZ{*錣``Gv属^,&)Od| ÝNW>xVl%SJ``$kwOPLaƭO6I<\eM~և]tEcs֌UC "^UfZ6)E+f:S]Yj+E0YU0-=C^?*v,'mZ|hOL ,2r,iO`4CM$~3(h*^­XO<1x4m\ dAJp%" PB(eskMS߉2Lg`gEU.˶9>\+o !ovWԖs*ipCOKb1  G@$CM?<ɅRB0m:SQy'xOo{f^K[jj},]h~>Ns")iL eq,w \ܲtɌdzY8>AlW[EI*]e;7hĴOr<~‘!p*S{8Z7oCLFAP[$!W4Q)#@;WDWDMl);rcY&%;җyo!89LJЗpl)uqGoڒ.ƚ|=eAj Ix˟99Lw2P4C}8'{55SPN+:Yƶ-pkTx&)yX[e;UAJȎn" S-δ$QQ'{eyվi ; Q7yB(՝Ne#q,kw̭[Q{@dj&20tfAk-ۦ˙Ex}lj=*w5l?aiE ie80CQ |IL8ppv}2LwÑ ?\#.)iL P q9ҟ!Ufߴ,>u=}@[ m^~iy?!z [߆*>JM{/vje̾Y-Mռ玲n{9:FW4fn]3`6l t>`EymX9+9~MTdlo6 536##< @@Sgiz->ӱi};7,9[L}z@$ޑ4~x 鳁CN%MWqPEsk %|w~ZWGhO\"!P3sBBT@hՄwTS6~mۦ}x;cX~Uf?t"enD6Gz}&ڴl^‡䴽D2QF| 9y΍ (@r5Lt ۂ\҉gsH;YsJr+}>=@0mfFpp%96BlO",n |U ANF 95D];7.#eZӁAF@by#InO?=)7W޾$Y+ND~lF}eC=ݙ,O^3]?Z:Y$-n j7oEGGhA$?%arlJn_dΧ}02c_dދ6/e#GJ͌PP[Y)às 7g2P UW$LF Xuu l\mqƫ[3^#x!*U|W_?:"=m'η#^ZnYՂk]@et 9k_ٮJ$sF@ȡ> ZY6P4[Shsw;ls2MI UD4ʟNi3?Y**lT/DKYq !>#3vnưm<— a~;fN]6+;?ggx6dd,+sFMP@ř Xs}6;Ӷ(B:P]W+^ة7?!ˡxIY (ILwN,X]SƽϽ[~|" 0w3 (`tܽ M$a8FSs6k &SxW~h[jklGPzfX`H:-'6p(?ì,bH:N =+qIVq)՝ɎW||+){CO;2*M:e'd_ 3>xD6OԬ+,Fű2E ^ZVJҖŃ6N1V q.UBֲ8pgƭKm1 ~t099!] QTAK{{51>O.} }?|\Pt- qv:̓!M[4xU ȩ?gW7tH8佴{̟lvЁ.=ُHa?:dC1FRӽw\Gu7.*>KMoXVU:ky 7 OhOOQ3&SX@)T=&4.M P AO&;t46TWm.GZgU{;/e[[ S$3x> %>zU/Oz榒e÷wFp*U\Z7kFQ2k5Y٧7CxTNhwqsUUtd;8[mMQO)*sM{* ³)߰,:ɡ!x{tbG/i>o{nE wY6_;2̏x\43/'ٕnyC.޺4aY#E4|Κ lLbSNwZKas?X͡!De<:/|GGE{p@ESY#,Ozק=CU%A}=q~x9sz mh0N'wC@XxwC6G{u~ݓ%ew|4#Bj4Xl炼}FVU?ާHύ|ciO瑁$˫Jy h({?>'K-п^֫Oǧmq۶ ߲\SB1`m [|BmND4&U6~ħ MϮ({ȭW,ݳHH°A i)]s#BDSzcQ|T\]KU888o::'0,ߟSIF6dbH`Āmdz9NF z˶y) pkwcB٧aZ6 -;ֳxOdbyǚ-Dd"c$ں y&*Ey3( bT[ڂT Do_1c}[ZOS{h۞ɰ,os7'94Us΍s{GvTYyus&y1]H<`2ef&ƺ䰁Yyt7Oѱ@Vm>t?'966T({f{Xe "e%ƃ ;!6]Ŵ,;NHIsq1HXxE3KX>J RT|3`2ů::I:o[rue="`UME, z$kӣ4uvTg{8Sp,ûa m7\q:񫂝 Ll", hX@G"=}4\g򢲦}8m_d+# Rs9~|0#ieMETUo^]VuKJ>)L#y 5~2&׶zORmw4Aδ(u);ZrecȞ]eیgF"muWe$kr'>Ú|(4g,Y›]>Oc珝t5!w,\ o pEHh>IHϧg*4S\U<=#<{y#ضe<:8<c[6{,v.m%ӄZs 4Lz5Uo^\jNJj/T{dp)h}`n 7jW%iZ|r*Lzo:O nXY23,Q[! Un׶p+:oPbSU_&J0o]MQع^ a/G-I=$LghΔ)Ev)@PS\ǀ:8-Z\/ɵ0}:gy$Υ 6|f?9K >"xɦ TT4a9( Z~6p)";pli!! $ܰ<?Ec*TOZ="B#71gzec)>w`S̥+llk]߭ce9_눢*^Õ-5=1L30DlKTW6h~v4X*?RZYb(Φ48ޫǣ|)sM c|?8}ʦƪYL5q/ HT jQQ58Y4.Os`Uen?-l:7V9'O |춫(y(82zd5|zUa"ʭiDWxY|q´ " KSH4o-uqgTN*\j^h`2#ԅu|;=i!aX3>qcnaQ^>x6VՔ@fӶ8]#k<*B4#44"+pMy.g"TH}q)I,g1Y7\+ˑ=ኚ2>tvjC~+Wx8`m mOR{vmdie bv0MQtHͰlp֢'mYQ*U͛2BKsdLMݫ*@"p)V^U>U <nEV:.AE8D!P4E 1*μn}F aqzԅmO8.+#& $A{VjC֌1 vFi Xe;ڜI);ކbRѰKgzH}I\ āAqEE=3p?͙"iի%ے;ŅffZ $oH $J*;;;.a]hvgι?<‰^6+3W .ȳI_i[XΐF2BM IDATCpRHH!}DADEdI@V^:Ϥ7lIEEQRd};$ "VY\R!836mۿN40k°h i/dID':퇻'|gq2N0L"=PQ* NZ YB"7t,4tuW8}|/--/IqfOXE;jX|r BϿe.%7pP"4 eM$yaR#[/I)%KJ4hfyB$]iOqWGcXpj>4D T5GhaYXw0M&C<@KX ("j S[| ': LPq7{90Οӫrh͗2i++7mLK$PsL@|K_E"HCziT2S/wj T=D ҹ\{2mR%wF>PZZi[QW@$T)91dDދ"*k Tiƕ';+ܜ f>7s=˝J,=0,O̰;UM[[p9y]0m̈́,L<9g4iDߡx7mY{TDjxy+}ԷiʴƊ {1- V(I*UhfK{:p8DJDS!R(wJkIC .q§v@(o?(޻0$|sa߻eCoa2NzXTD'bX &Ltb^QN(r1g ]*2.DhmZK]j4(taNz˽0SkYDuaY1XjJm^Bx*]aw1@;Os`?=27wT/D->qÚ)7a2IJՋT %ȢWq;sAݴ){r;V}73mR$7F2;Ӷ,E)ӼH/^A@u\HI-ݲ65MCCc\Pqvd;.En\7?a>ϯ-w힀dyR ??:F°PS߲!hJc,@cIzϋ"I.9_*N9gө$u{U;w m-˸IDehuS *%.7n7^EA9%.yO,Nq-Oّьx=S?p>k}~ӣfz?5,|r SpUxU1`S3.8do̴סPq4Zqa!*7>/פa- .Өvfs]I?pj^Z!\Pځ*ggۅb`F:$IKC""LB2;u2NEؒas$yp[;VȱEtPwϸYL_ وǡ;;sSk ώ2I17CDu6֔tB%bͣ(e9#Hu_r^݄VX˴-KH#T$"%.Eԑlfs.<Q$n]ɶr&53P*#(OR|(!fWK5wˆI.Rfk39U+ϱ {l)txwUYr@@[kI!A3Ӷd"OO٦%1͘rD%>soZYGʲ#\ 9Nc ' v+sH*t͵KI;V>L۲ 4;OkYa0WcehP p5&'EZ:Ȣ6nCBO^#SI9ʴnU|t tt[Lf+{ r69;=iA&vdt߃Tr<7@E~ܽˆ/sja+'ʱq5;;xB\3\5U#*MeE@ :*(Q?"+2fgO'є\(p߆Vv6W-0'B+v{;Ϗ2,rSvt3ɑ@[5-A`gK5=,)I}e i D.DAc;:xfDAF;75A´o}+0 k>12fMBjڑiV)MA ıW!@Beܪ}5v5DŽ2ග݋4\ -^Gܴb(G ܫp'b|l{wn̩Nr׊ijd*>w:P/c`rF] _8G?|223 _} $/kr/"I,[V+c~2{7ӣXR,HAw{6T曘Y!I; DMOh$e1^~y fX|^S"󼒄 Z]EWN~-n'+ˋ*I9O. 7pWE[Y&\C(P3}yؼ"#c?z0:;( kXSU.kl"1/D !, rY|~x۪:吩-F?yUmhŰm D^u/4zzi廷s}SUˁ~2~y "2ʹ-K\On'k'YILӱ4NL7H&ye[Zz]nuM̉aw_%>}M&_:z! bNTәN'Jz,(f؜ow%[13㟟]yͬ@:V$A!I8% Uem=ěѹVni!KN}nÄ456ӓ/ ՝;*ft2=H6m2MnYXv`_x'Grt`޾9߿x9/';$QMExLub6Eܺn-^ &#QbdP n(DdAD$Q@s`<ڲs9|mS'{Y_CS6P$ǦmcZmN1,ibX-YvQsq* ^"(xH'OkdY޾ 9-BkI!]#[F|m(-,4ӶdOCgJl斵k(p{"llj5]'I8``b3Cf2I/`jg=@dQ)@(Jㆦ*TYuYL{WKM$Q oFU 71m ,fZ3a[UN s  .'(e\C_57cE;?ٳ3֍ks˦+ʃtL=fڦLSo] A4 DI8d4_͊UhU/ܹBo>As3̷^8T<5*y#3= Ӊs}=(87jxQq* ~BUQp* E~?-4sF}yÈ{7scs>gM 0l7}@f6d\\]+_s+k {G '5|.]MUuA>S7M)X2IR0LhQN 025M\{Kfk};gM騲ܞ3/槝 0/iɨ2~254P\G%YtwPc_4䅓sϣU/ƆW 1ΰI!@+pjK]9yCmY44xo<(^]7D/ LÜ>CQDc6( "+nOбc] ۣJP8Ʃ);8pyQGF0-7uscC|'2mO&ȭ#0(9v)?#Ee0`e]-4H'2th/-!ҘA\.g{cB*z)\5(??zP\ߌIM @7-^!KjCW_?x1~q{Ggu#fڼEaeybh3_7F. ͧ:XY̴- N0y_(mQUvs ;p, TYFe=nJJD虜b2d8!܊̝V_A[͹NC7-&IO.cI ]e|>p\"'L{YºO>sxtk= *xɀi\Z1kuedžyyM`6ksf*ʗtRlW;-||D5H2p4x47q~?gy-eܿ4< AKII0-6G@ $~Kp~`g53S7\ V.%hؑ^ ˶9?1`8̩IN M=N>dr;kb61`<x{f"z,+Yp 56gwc<|#)ys C&oL۴(dږaA:z0$qUܼf U w2_/ E4'R LŒc/vȬ.* ΅3>oİ,~uv !1K`EU}NʛɶmgsK3<ľ3g̃rSk k0BBnk'GG2mb++3mǼNjӇ8>4x|o"Ӧ]16U(yy4 LQb1?? ׁS?7:EĬ[ eULB7m\'{JQEA:XU^N37Eccy97<~'O]TWCW}xѰ$/vA]0%oE\yL?VW/+79lcӜ@(]#X(nJ߇:`^ɊEE|^N;|obA޽5Uř6XQī*&ʴMAogjArژl`$x}cF3MAx<iՐ}+S(ᅲGN ٟ l)J!MmϟWmL۳,g %Udږ+ƶmAM+R!+l6ptc4#аlI+ P]P*Krin۰-m-{;**ʴyKٓrԫH~h-)&LqlxX'N)T[GT}L7Q2珳{%#v=JGyY΍]w!mUUWj3/~<®mh{͓\-m54gU <{_5fXN@V9սS˽DS6~|^},S[VLbIϫ # nuk}˼ \H&?8x'NG*ʅEFz=|[ov>>=7ڌUQ; Z&,'p;XSYw>4Ӣ[֮akkk_ RO uID%LY:v5%.]ml΍Osfl}t tĮϩR[y s#cs}nwmfuef*ޱ{bo 1bYN`XUQl}]fݹy3o޴O~1l2iSULF7MB!'87\Lɽ[PqYyUi^&dĮ$RTLoFf3U-r.ݱW?9]Ó|ej}`CQAX}Yf\uCAl') G摃x 'BwS;k+q,(8i. 0nY^$4iUiqJt?u7dm}=Nn?TG.A(z)zL35P$#]=|qA?lY`бh[J4o9ޞat"kF[U"y3\'r xj>lv oeA(2Lݝhu+y׎ œi~.(*tH:'FF Ͻn,H^(zXQZϳ񺜼m6_yG mh$^&SZj B02dp7I;;6^<) fCS#wmL]I)]eWfl J12HuKmc6Gp*2Aꂂ0y"b߽nO>w_<3gyfv,@"xAki!Gۀ5y  ^WJ$weSqs-Zݑ_|jl4I#(0a(H*.ל4pM׉hda(nEӉ,8d9\k0UʹUV?c336S:UY.f` ȿ~y}#YΏ֭5)/|匝/l,f:`,#҈hKN$vPUEq( ⫄"l@4t@tbq]0 R NJz'_gw}r34$x}c%?:|F?A @𾺠yRzӉ{5~5>@31mӲRMO3s(ȲG@u8 @e %$ D8$(R "K(=cy2n3ɛp|Vxh.)X&D !4L3 bnn[];\`ja 0ZF3M&b1BQ-E8D$O¡(\.EFu8ps(I24M)M??F$n7nE&rQΫO^L}t䑣:WT{6Tm{`J?{doϏř#[ ܨe79y5l;]xA))3ogsY,۶1mH 4%.,z=z% 'i+g$I&uHdS@2 |\#_Ԓ9:jk騭CHWϞ37odEyrp'dvz]^t:v_nZTMޞt!r@TӘlji:S8M(ιKUнr:*/Ƕm$:dH*ž^DAtRv5ʵR\_Ǻ~ouKXQGM pH"K:ߚwloW;8_yab7ZwDͱi+4971P8iY/|éYqr˅{&\f6a0JLt"Y, .o=.pYS(#ʛ7n/~c?3guR}E%<|1|F +0}MkU%oݼuuY.w.´,B$1M#J1DO$j:~Tլ_AP"JTJ#LT 0螜{r U)z*^J˙O&3@UQ_~}|-]q,ƲGXE4\KcǍSu"I"(.S"K\UiYXE2$4&q&qDAHO>%=jW s i64 {y}Mm^rKy`p#b=d4vsm)ktwJ&'&'韎VUn\wxnU]\k\K5вmN0' Ĵ]DQDVYrv:T~ "((^|^/eK$H$f$ioILGFe<ҒYKs)~?vmTV#?x7"\²w" &PEk_I8iRY{vH]II~j_`[K' B0єFBLb r9($K{l͕y7ȿ;nZ^nXŝ7YL 1S0856p4鄒$( Erf~6!"ágzchiIB$C Pv,?8Sv< Ϟ[\MdXP@ᆦm8?:|3Sumܹy3UE#gq 034: '/6$`@WgIpv9, 00 p$J2¶mc13sԕ͋gxwu4r%{{7sLx{ JE[<.3OG`6%~?fVf|-}ѣ˅}X@8(Ñ.QZJ!)ETa6pdfn QQ fLNO315eY8f֮ZUF (* 2ǙL$i)#QZ*e(khX2I83Ugm#r%`MϚ:hy}}Ż 'J quxPVRBMU%#cIb8ZJv=#F'tA(/+m[)*,| .JGG xϢH^,K l_ю(mgֺ'V_mszpQzƘ GIi[/9Ǚm3s˶icWpT*E!LO$ؖCu`6DiX+ jE;- !"5UUX8mSr-Hrhbe^d=mI$02ʱ^:/t3x|emW3h 9,?3TDATidKk +jk9:ϕ37>ua6cNM ekj8yQB!"%(YӢvc6#c$fJ3AښJƔ$-971A4bMeŒigi`68{^F& ۶l{)xdaz 55)/%J11H4 D/Y@"d|b4z<_?"߶sp` Uea\ d2A7 tD7͗tů:(z8$ADKx?vN%Bv~ <3*I0y{ 5_uBaFcqRN0L$&c1&&x Z*+.*pPvS(ɻ>Üðm*Xf $rt>(ӡ0ɔFϋFFUTnjkk(OJP krizKkI 500-PcX=: IRN*^)b8e!u5476T~"t\A P]g"gCuռxoaHhFF8rGF%StW%QD$,ȢxUߕiY$ `_m׶8K3S.՗ii#QbnZS)1,ӃCtR]\ĖfjJJE Auͧj I&%Ŭ_.mTWqqC!F{*4Mh,T(mȲLyY)Kjrc:g`xCä ^~ I4 H2 ±'9X8v!"NY%K Jbk$m' 3D0tqYzѦ9O ? ,rLm1/]dp`iG |dY)Tk5|>/pO bmϟ'5 \} X{LS$O&𜮫r2+L/) J\l?@/ rpl<.ikc~C90lYYӻjs~ߌ@&(ѥXta2|>OYV tY׷$nZ~.C$_| 8(;{8OѮq/T]g:%aٸ|"gSq8twuF75ͽC!.'~0zlj. b IQU,ʕ-MOip:yVLTAYQЬo6:J jhRo"]~]{K&,I"[.)0ׂ s8͏0lؘs1} A҅ \:B; |^Vc?7oBd$YRҕ7uoi%[F] +_#E3g|xK2_8wvo{oiblI+Yaq3(޳mcpˉQeГf*k'8fR۽cm>XrL2OuJLJEy07σyv;@s]$C!6'Bc_H&kn^xvc_"LQXXZ&  q/( $q!;Y1$_<GFH`!do>McqstSU376)j; Z%qmļ^/6J؎|3ڔ;]5c}H_0@_m+K()*4ә,y^d(M\w?rE[@EQLMUq\ GEEet|Erbx O(Mz:,+/ݶ x=^qc}c;\9}\8{)sEO߸<3LȚƅxH=Yfqstd)jLPth.vvcBuT}GpE%7]*9Dݡ>v!XahnX$11SdVs9V* `2Prx\Nb1>{9 =S@|.ϋ_fp`J'M<%}J jG4rrBZ%ˣo׮98;8H"?`!#_ oL4MVYe|ibՑM b2AߚB(EClֶr9<̹|A$'(Vek90g vuk HFa˸Pqkj*PK %Nn7PX s˓Vq:\x3}zqfHV)T%p0{|$Ke*Vɓϙ~Oe6ϽƃC kj UXIX)N~[0r2pm<^ΆC]!o[C{jw՗{^it,vA7 -A#0K2s]gK?rWa&EYfXbR,[=eE$;)W_sΓi̥R-nIa^ϑLӤZwT(]@4?<ׂ.IIwH¼ǖ+s(Z +~(+D<M1c3 vE ҝLZZbfn\>O\& ܷ\'HL._@*M{=\L<=U΅GA4$I~kA3}-W*-;'- `8DClY $U, yIz솨i7M3p8bD# ,S֐eñ/q'M)ʤ94Mtͧ]# v*n ο60IdU>G'jenPt}7尴Z`p1iȻ%jJߍM5D8+I\NAz;W$U  OJOWW;5b1MX䖘h1L XZ >Q9*g¡M:4 j xK+~ aiKC"i7SK|mde0aM)*vH"̿b+n<y̤sCC| N^~^x[e{#ØI4g0׋f(?aDx>m1 6?ߡ($Ejա ?~8ʭTr<ȵ;̍޸+" R*eٚ ۅ(Z?A9>2e "NY $&*?~7; qY_gy{!;fRQm ZhVMnLerz\4M>Z^?~t$/盖0@8Hp:363cT`֟u#3=;@"wޮpQX*Tv .'"wWʃ-Mz-ZSh67:PQUZB3 F|&}O M x\NhƝ3Sa~"Z)3EQ}> Q"A4=!A73}qnj?3s)4ߦk;ߑjP6h+4_?LMU r.xj~Yѐuӱid8D2DQ>x;ZY,v:Лq -՞itDn8㴉nl1Rj@S i_G=h ,(e<ӡqT&x{>EEU%~\r<=856-x7 #NhK%f1Mx3&(@Ry/g~>㰱T,SZ44)}{q#cT0T;UF }~kGg˺AQRD3r:OD1 O0N9H̭w0 x(@wIAM(h]䵁~U^`T 0MҖ%ҴjkiRZ %QtG,3q;B̻{R o  S.W}!"`F.S$.{.?P2i, y/NdP^^=Dalc4LI<1 d`TB2̅hm%4K {1q9d,vn|988Beӟ!'FqǚMwS|}t|{G0LsrpFPVޘ;3TTހkW4b3L`S쀘Py0$I;wuh1{Mluia.ǃ밓*I>\Z+{r0rhk4`X2E^ |"~8!k.S"j|xvg;O.zvߘ ,s8u(J"&vف8}A/5M%==K9=N&dPB+RgL&şޜc20M^΄xcTJÛ\BRTҙ,˫k )ӂi<^t,Zg]qCT|Df@>fwCOOgX*ܙ>$zOgTHi2p1b_c63EA.ǃG^5 [ Fo4iLLO^䵢uzh,|t.BC'h KGc&Z?z8&ip9jWȻO^2Uz^tHiD3 nHFzO:jJ#zP?K&_,2jyc77Ig2m6@o.6UUG=b3x+¥Xƽ5( c nC2Jڝ$rkyY8RWpaihKp(П! S33T;NcD`' MӸ! 8 q()*v|͘!/s(W$owDur|@`=<;|i[[dBnF9S9jW4(w;w&@VEu=Fu"~C'5 Yͱu Mq.D|c|T-LjnT~FROZ=-OfS|*aqޚO:wGu׿ kǺ\wS-o}`}s v;]ǫ3  S4=8|ʉ~+52FRO!xP}1MkQtڹa0ۗmo=(v8p;\( p+MY^Yԑ~Kd(?$IO`&=0zUA}heuXՀWz%dxr(u7j#7FZ$OPTpP #$`[ʊx\N\M>Pz6:u03lw/PUUwș[av*έ;weG1T< A|IAn4]>??襦j|noMN)Ry; ԟvInT&8cbM7 z L! TNzLgjH /lec3X\^!_(u9ݐzc@ER<N;,_gttzX[#D$U`su|B{L6(D Gv۔-_g MГnY\Z"5$fa]901=i"9AUm qJoEEQ,oLN:BM,PN$&0^US3\VznV:17VEw4iON6,f`p1͈ܺ}EQ芆E "g#cD ᲋Le>6HM֘󍍦K0fm޹j&Wp}x!jW_v/{TL ;> nl.ĩyƓ6kj>s q!kFCBd+N;+k|ud9kF-u7hԙ5f1L/$lT$t$ 4Mvu[rM44-ɍ> Az=IYnNisQ{L_AZ|gf IU7m`vJUTa8y7`ӘΖ3XCy3]q7BFsl F>0M9jD@OCz-obso66=1.6F668mV 4m;Fr[% MMkWGF<r;JIwT rhnDgz"s )V҉}$9A?kJM y=AA@E,cO8l,K7S#% :ݹO&8?=vzInkpПb&3ssfr>qL<5 iܾM莆iW$- IDATtlg-aA[+|mtbb J `(`QQüZ4 E!99iң`s<#O <8EA;=[NS015M._ us;ptةzM( |6 rT(g|69K`q&5KgwWa Lig#~l4MUAN08vVWYhtH'“Mi 5|*6Qd~a'ZkIl"{ RK|}tJMgɚb]/7:oucː$^q&Z |B*&񐿩o`zb|W I+f4M>x(„K,A `4}nKz"6RMJKUq'LB5]g2+FX):\pA`2mujt(7vS*ia6 s߭|d,LdtbrJa Cw4 j>7?E5>X\̶J Ѧ~ j(_LLYT]g0XzPQtd]vak&<ˍ4 ՚i"+m\ ,.-8ӼO4@ E h=Q;㛙-)7r@m|-Gix|aL38ab<`הb ξDDQ _RVN&7л(|*ŭwuA9(*Ӊ 6||Do4BW$f-i6znݠ )LgslT*ZM J;ΕI\phudoOrwm0 xqF0LbuV%r/W(WHLowcla:FFXZY0M6C (RdVds}>bx,3:1$,#"iz(ⴉd%See4]'td3arf(ravGAEjLiRجA;A/EE()TT.gTr̃ t|N9.cyu@*YN G4ޜOǨ*={"$}gsnD[q9IZ#bSSTZYIf- 9޻e}+6E$3kj6ފO&dMdx}36xۂrqҎ ,PyDù!ro޾fڒsnYLf}]:]Tk5ff[jT-F'jDa_%~W/^|_|_@$PIMqSirAX!d[بHm  /Ij|sr-l7n_ih LD LMU;O|L-1ɢI@.UUC n'?Ұ"{x0bqyp(Й3(sr?3Y{-'`o@_7Woq5z:lӸkd|^ *%Y%BO" WB,+_[gT3 Ӽ%c7 wݩU9,+0hΥx]]n񸜜ŧff)ˍiWLӤT*frfM8;xO?Ys.~?㋟ n0~|дUo]鎄q9[b>lE^^u٨T$ߛG7NZ҉ ҮGn[[*i8E0L! &H @_"_09=+^ntHQVcye0-~Mnagoޭ[]nݽ;|@Owϝ#l&[{j$BFT7."k:&sج'Q䵁8 s 7I7zOlR,+moҎ7 Ӥ(K'b%E+hyx.H/6@? M ie~&%tǿE/ ]|J?i~;/յu}6M5`ieU\GK Qo9_ }|n N`P/&z"]VT4yҮs&.Do'-pgu 0z D[Ĺ 0<-. ]̬353C,i71=7K/e~s={ݰl|i^"[}"bgBlP/`&_܋m]c~?(JOBTʻ f{$`:0v^Ih+"WKckިT)ʲ } Z\Lg# =PB\4~Ɖ`븜N?e~~>E{v/^8wGF K|W|K%bA?6CyX0:f-rPlPOfx=JG'x; ˑj`5)))jǗjutd,p9|4pIө:{KPw&s {m0XY[?`yuk[^3].OS׮/K}>ҙ,z{8+dy.'n{ݨAΏaվ!/&&/S+uu4AV>IzkN\4H[HTz9)|nz $dE᣻!R 7_ .;wn=!6u|?~op㥫$ow?AQ5)Ax p1f 9"3Mpwun!:bʵ5|ŌX o[V.wy+ٰԔ0Mʊ( x\E>33;wloěM=n]>+}&^'LE{\y7 OTlMza.F\sڙdG'j3燣ʸ"/v ?0o'lҮS`Ij5@yIE-5/aG{6UJ48% IKONOf SVP2ڨT_\0#'{恛0EAOEUY()|x]]<"Ͱ[?5#a(R$Mܓ_ʌnd٬ָŋ]M ߋ>`vN6BEU)0X*xcr 0pm\sQQ v:p~d\B&c>C~R)@l(kd&nIHg$#mMYY|vss8IJ%WVX][ի$4L͑d\k3]*Nq/I0'SxsvlMS=yZ* pviڳfʷgI4ðl{#fŝ|oTE(bιpKVdz&( SGy[ xO%:jFji Q~96q|,Jɝu~wj6ӷosǪMeZP0/$B6qfy\V8lڹfK}JǬi 4a`.ł';J&eQwN#p|F,pc]aU>c$I&t< "&zt Tq pwǡikm]FOf"'͒f9;xK.|4M Ip{x[ۍn$[S҃_DA w8ȓj$Wq~dhGv&V IHbI{s '|.^Y$TU Ӂ@_<&噚qjֻ!K2q{v ?z᳚E%E!zzby)c(!-x-l#{OU^tE,Å|pMzwK .э4M0}oRՉ@=&o`&W!^ocz i:aui- 䙹960Mfﰲ(\8ǙdpK_!|XJ4ztjNx@Z2NF$_Byl`hFJVwvB^>봱Z*70H63;wk#hNRZuh 5MeY"O t5-v[= plf$@LZ~8kw" 021q8A|jUK<$6owApO.DBd%7*EE᝛3ЙSS(ʙdThN쇀׃a(TqyvQ3}q5fs%n-T(@?["R2@8pjC&V.qT@G V?IIVqN $3T*U771MΕXgn+Oԧqwgu]ksykǿ#/w89viv:ɗ+Ț8( t[˱R*},S]y,aq;9 dƺ.:\$BAV29f;zT^+ds9O]Q,Zd qsYTI$r[RA$$;Iȗ+Jļh8m\jejVmp'i8ĝ̑MT]l=@ Qtʰ;O<'b7Wb}#' rZ[\cGrdRFYQAw(Eni\Κ8P\6~>/+*I(L-a # {mn'nR4S0L2_(*PZ~[ȣ9 U++?{]>-liRxNHVTf6)jȪ%'r99'NQQAbL^)V*U3Mq40;zRif%.O_XeYY)XW0LSsO9EsQ~1]fARI\ |RnhZ\PVY 5YA"nqTG@ ATM;Tm~3 @ClP0QTVU*2,N/ۋVX.7668@h!~i7 {7bH`&[%j}SL{[]iW+393>>%)pܰģM1^h17)<10qҍcο-Ka [WìoǍ MڒEnə#v\1&~) Q`cSbN9&PT88pj{ӡ*Ig`y];CUi h X6G&&0-T8IVwe=<4 " Ri>7SxY B~I IXoC>ؗ-^*ZtB#s$^ICזuOFҹpQ Ql* t]Ɗ&cfmb 8m8ernM(,;G LEҦZm dB?>8ՎMږ5znT+- ,?uyx?|Q.3 65ˢ2KkDQtUeݧz?݋X Fw,JW,J޲) IDATx%![4Mg]W'2P[962ʁTmKpL慡4%ǭR3NȌRiޯ<;4ޝ e쉛)ـ8V _`,c,c$  ?t "|2Nu/n*j.ooe,1^*6K gHer7%ih-]z{S)Ɗ%\O-gdxB*Z fL |_8sE:|b´ߞ)eMҞ\e=!+n$wh[@S5%h .O3Yѣ\tsڶ:0llRr:@ɲ@SPEQh h p<<qt+:is1th8HXbX}7/./ ɖJgq[wn ۹gw*9Áec]k*%ܥӲIde  ,Rm}NK8it CB* '" d,%b-M4ǣHi!оdi)5l! 2@,b'n"J1Cgs{2H&th<V-q8<0L&_rXs㯠Cz3¯ã) U]vs%iM&=pd"ChrӚmI˴c M5/ݎ'72X I|~x9@,hyRB%7Q՗{aVăKsh(Zd@A=2yTPXtY'!M&sJ`UQd'-:=<E$v5Td0m&9˦?5xfZqb EM(.h0-rti{> ܇|`aBO.Λ$C.hZbc#B=2e 5N,y2'  CwEӜuB x,kJCQwK{[+W_~\ץ)tnDhi (:Å"#"G uцк[9>`*% xrJGEڸ _8;߰\C#ysXm 2%ѩŭyJDH0aC%a2L2hP}}'`}[<8(,OU#,B@S,`*qvѿVSDY U2FJ[8LY PhreMN+'Zn9"#p 0a=y\}=!0mmCO zp*p-> 0`9$.^j[lҤ9% D Pʹk/ur*)@sld0hyۑilCMӮ0ޡN}%R2h< d^?o}6kqIg_J37ٰ3ڗ@b8-GƦu\A?.9KVš6[l/Bpyȴv223g?>~GU=+5#T@麈~/(2r@׈MM\Ci]mk<fnܭgG8:4Šx-g8 `/]/"K,#7# x'p*Zw,LW,D"tz*=mͬvŒX6R 4tFgeZr]LveȚymq<_ @/0!M{arb`;Фa])$ ceq<\2^*Q<#g ``  Ԉ)C+~mɘibQqZY'J=G݂q!9a8gr'Ob!!\uӬu=ҹ<+)AD7q"k[S=!N~2HodYIY #n T,Lk:l&*).n7/Ȏ)cR*ՁU[9VQT*5deƈ I *sG&3YӠ(t[NiBˇp\kV ̾ 2͊ } x/$RYoO'&]UU(]Y +HKׯq=+IG$p|_ 3ZWM> l0L.rŧsH5j*du%"uݷw?V.SswZE:nGH[hbөWa]#u MUC>Z"`Pd,,uV6 \}(.cGs,59/H?%vRG-Eg/щ,FR E'-&ͽA%D"oۘ \!>uHn)aGq&Hq;pA@~ U,fzpHXT,i%gYcl&Q~i ېCF[ʊ) H@U :q VOL%CӈGB yXsHͽF9B-JMUJ@h 8<%ףT*w_@:dC,wSA"Ғp+*؎|nQf1PTT D `bQ3KkB89IWsUUOsx`h:dȓzr$XoǑ&h9@*G@WK&3 FW4Br=M֒fJʊ??ȳH~q_0aWH0(]3&jF.=(86"yA? %&R?_BmHK^SJmA64  *|eU-mACguG i*v5a.rd-[ Շ?Os Q*ר:9`m:jؓɰ:a/N@H-"|B@j|[7aUQ 4mNxIƒń̚_D:} 8-!XH* X98# ]v]X@'oxB8V9T_!;)6z({6{^t܄4GZ" UY΄xYf0W8ȱ/ +9siQz5a!7׫'nmX/"iV܏ߏH YxubdҚ UQiI,jpåP> _Yh7:=(-`lUN d0 $L?ϲ||?HːqQEQ:@ Tr9T3֍姑Q/!Y:'9#=߈̢B*XߔWuuɸPE J>_ =ʃsv.deH/xqde~Xg>>>>>>> t#}9|y333 RCC-' >L1f9o6v=US^)ꧭwk33m}g 'N"292lsƜ_AH5RQw5 ,h@x/=D?)ty6 @(0S[Q 8(~ < .Bӗ#kUU]M-lh+HaxCj2BtSQyuehTMQ'L4rY삚c684˺l8~V:jGX(4_4O+ty~U22$]0IM>wr-7̸L}~-,5{q rLn iZm[x]s5!ty|kߠ-&61I;e 'GY[T*ufEiMqyT|q.1_VHD8k>Fo*C0;Tuva{ݷCǎ39HkwG#q"MUMa*.v!]q+:g ˛~8Ú6|#kZE\O > e X+_B,K{o5U w%umT弲g'p\y#LQٵ;cO%˽ C |oރ$q_ W]Nw{;Mp.¡9ĴCCML[;?Z4nsn4Q3yrܿ6c ENDP}_&yRHS4BYN٫9xP)r]^3uʧ䛻?9H!@Jl(DaVtsu\e3-MIA4" NdYQ 5{u ӓ_RB(IdR]^(kg\OS*r\GKN<_QiJS29Y Zߔ+x9kFYA2'sJ( ڔ$EYukp:𜮕 r8<_x d'" ֡WD] W^5joCf ;xWzeKO1KV5D==F!gK *SNj@?00en? \ɭĀ[}ƥ7W^G-US]{LW7O&,kL51ʚ5J%hQEYbB &Œ <:0͚KzΆn_Gm 4ٴ<+!-'И^aG6Gwr6Jd% ζbZ6*j1*h˝#x~m[|UO="fptBєIJm\7;ټ~-W^N0`'`<& {@5`1g4nנ:4N/5obQ:ú *Df*S|lY׀Wp IDATt(c.So:MQ n6*ڪ\D+L#JñEIϓڡStMre[nw׳cdyfa ,#E! hi9I,+{شv5M)BѤ_9cO?#GbxlD% 5D oDC_(J(7l.ڕ=D¡'x(''[(O326D6OɪiW-F8"B[s3I} }Kbǟ UEeZ^Ւ"Sjki,*uT0>`<fYNO)&+i4η6 T;e]U87W3gؗͷ oO|x楗960@&Ƕ,qH ֭KuB a:apу< MqE# g>L!/~xF8Kqwvq1-}s/ʑ^FS8uMUua`:X5=\m W_r [M= 2>kf` @26DP0ieK{Iyq'\!3\lhmQ_¡ŒI:%14bptt&GɪAs2ʮN֯ZI{K3Hd47TρIҁCU МLВLcȒSH魡:!ԽZxBk($SHWUTUkVqm4'~FkVw=g jr lazi*oJĹkk<72C)&9&9N 78fiQ>L\YxLf7?7!)qzA{k =]9(˷zE-'E¨Tb$| UAW,!/v[frtb]}"'ƷJg[ mݶmN e`x޾Ri:2 Ȣ;M^B8dVuw.֭꡻mXSg0>e6mt%rZf]RؼH&'S()%Je8tt]lTzBgs  )׵\םIUdSie-a^<ʧ7*kZaAbMMt]t4c"`ks#']g>Qu&\ӛGh3G*Ŏ'duil+yG~J.r!c4e|H$5wq*(N U-9FFuP0@$"LhN$hmn4"ohg/%?i>#_ 3 N/` )Bߴ4\uE\y+:Xr唸B8ck'WKkGɪ=ZM._ _,R,kjHsm%9P8"EV$Ͻ}DuCQ0T&Z iRSjIJ]90Tr9s=CܜS\nwvIPbs9x@tem{`{2SZ}jh3e?;-5I:ZZhx l*:a2NsB|(&BV{_G 3l Y#'mEQC4海9~9҈?K'(Ӷ* Z):3QuM%Peg2tBUզnT}6T6]uk͚iOe$5F\9[+.$?(_'*2,pd&Yx۽iF<~j7;y<.-j_tQLB$3kP_j]]uU`4 :@`@M;8‰1>[ƫXcx`|bT:Ïr \EuZjJo=%-zO hJq\xEV |'p 7&UӴjW.H0@'Ïq(é13E46YݷU+imNҜLAߓȮ=|3Um9B"v]̒iK9*bq"U Q BjMP.mzf-eϙs80hc@Kb,u5;z-V1[uJF 霢SW4M!jeӹ+e_*&Izh=SFEl6nez[pQFRr?Wz;@VVvuyZnZfi1Wu|(F$<ֆꄃ d)r(6J Wng{e˃SVcyz@  p yrZȔK3EoK$AZj,J Q5Ug79Za׏4]h T`;9>wp?oj8g?h qZKFvB_Glk垛o)s_~dN'bHoW?8 n׼ζi;\={gd|L˶QV^wM\{%D#3J2Ȯ=էkYzYT,ŒiY ί6huNby:rԄ"T&W7`ENj6|es2u[CU>-aY&V&iB={RZ;e'զ*zV ]/ySW]/kẦ-Kcc7]5Gyd r1Gq=Cٲq=7^y]m_pj<'MW\p\B$_,?CO&ˡ(  w W^1+GDTbK/}t6K8/o{3E/(_~Zb&3u+{&CsBB-+A)U&e) +? 3QjgV,yU߇eJbJ ؗH`̯°{mAPĭ LLדNoeSl4đW4q 0  k:iR17'xp_TGNo>Ͽe˗ M債sB0J|{QUVuk(M>/gpg1X{UW(& #|_p* .ܰ2dA߿̒UM0 .`#~/a*փS2Wvs!X}75(հ:^+1ԙ:? ֍Q)sיԽLqu3f-kt}LiL,gh{29:'qf<2YU WvPd8=]7^GW{Whoi沭v 6YE6}G,غiFnJ ]7ރ=!?_ (ʇmڠ[֍lv>?¾ y㫸疛Xriu=^:pGrg^zPٺq[6c`x>M p ~u"C׸ݔ8Mnh\&YurK4'Ӱ >Fu?9V5SZ_DL]^?8'!Lh!6#%WK]x#2+!WR ګyϛ@(DӴ3l/wރq]0z[UWiBQ>VƽU.>M_宛GQ\OyzGz31%׎5iӔL9ڪPCgx_Oզ\r\,,u p<|ɦ9?^ضeL.#h_?-kx7*ևSc|Gm;LT{6xɀap+}g;O1cM p-p$"b/sK4BNPz9dd'SQLM85_Jm:CjU;qmY}h^mJƓk~}˕iMC۪˧^ujil?>u1 煾0gR~+xB+dEt`8W.SIzX YӒ-aBqGƴګ+_ PUowx՚?4_8rCzथQׯZo&2YÏyζV~El<gv j &{=|c c,n I+'(d$BA! PÏc`Gk=37vm[ѾmY`2r<8eh;K6i;خ;E VZLq%c:F, 0$!Lr%=G/kKS">3./?~G~H(e[7֍Nɲ]|o+ <`逋Ohdor|R@{Y{gyK_a{)%lXf.Xè?|<,~ fܬ4j0x|= TڶÁG}dÄCAoG. ";v%ʵ+r+FsLͪ䠪P k Ww=J L%ۦVWJT†N,9&DG,M96ȱv`bg&x;[yg9xLAZr]jv1AəѻSpjZ#pMQ9ǸQtkBF KAetk-é1~|?DU=mp獯p/}?[D.g/Bxkÿηru +!cO(p_w-3gKg|cǮ4;H}]O.eR@ޯMPIJ+ LmUgGQ@W5†N4  蚌]/ 2Jiw:D8p3;#K>G^~ Ad.dN-']</tk̃-;ܓD $Ȋ `*;." v).T v9T eSML0Q H6~;ϽoVoU]^ը=s^[瘤2PףQ? bZ՚#C# 80b_A#$eG}INui;-:yv_yrˍ/ v+ʒOW vுRGQ~^_}G˕No_5+[5›Mȟգeɋyh*REIYb8T |W>Pj^%I 2 kLCqG J)c:H.r3ڣ\: N 黛H$Y',bdTD:t6TIDATҒyB94\/V9)WnXLJZߏijxHQ|Yn%-?T N#|G8 ?}Zmgy_~N>/]7og^JmㄏOoKg8| 7s5WSXv==5n9rTxjK'IIxd\1;Eƫ$_*yI\PUJrСa'v2ӬӎB~p${Qo>8K>1f9\ ZNꝑٹ'&wSo %r'.3&|/qXһDg尤Z Ma@=Ҽ{jnz1z|_s]xmrЁMS~1>g=*>>Ď 7y糏\_g<#PY< oWȋ^w o||́/ R),gft Y54+d AXPz0͕i##m_O44Z9'+J~1Kj; _q3.8 ,˲rL˒ހ~L'Nhk57Xn=]om_ʚ%ˑ>h?WN V2U<!Y N5rfzeM#J46 V1gz2zhp\{ K?ϑÛkV,{Cя>hp假T>N&# |ꁯdьN~A Ww{'QW\{xKԽTr[ XXyoבz.ГNɤd)p&s&k!}\5?Tcb8 1>$XgX>5E0$/8?gw웛<;~r,'`|i ;8| y^pCq 8y= 6IXKVjj=uZ\&Z;1q'|H}o1<}s3]8JSeIҔ8u]TIQE lny2&4}IWsE! 6kbX2۬QmB&yN?qΥfd]mZ 湮eݺ1(DE%ZˌZ< s+4.H8ϥ`1*KBӮ#U]>&uՉJ܏iU{-ߥ所{w'1"E3-AVFwTlaP}Ch]Z|ȊeW<w 9`QtnRY^dygsG;p\jCiQ(b8trCosZ ,̰saVKXǜd7'b {l>PL\ݲc+饲\s< "M) 22z}Qw߯vߵ@Dh0hGQ{NU!UHR]r*=N=o6?dQ#"80s2{e2'V{tn^T{B?vW HqAqa%h> VkF?&v-bQcՠU$w)2m,Zk;o0 YV62q9YGAMSlPykMBwhvA6=I8 _.rE'1!(BgѪ4#(Vi̓Q'Dv℞%u.hf7^匶^Aee%Y^hbQ)\;8<ryD 4<שfݍTz,U\*Aʡ {nפ]Uz ٭td9~2HYh._ϥmvSjäL.Ph:l4LN=urZ/b>_1sK61 &ˎ\sgEI7IY$t 1Jfd {;pn7. D{'N̆\YBq:-YK-na3}O5941~3JA?3X mRT}zPG{;a8HMqtw< | .pݟAz|B0 oD\ovq]ב@[ܚ$FinnҊ+ZGS:ӵi YN,<ܡ6D"xLCf+x_ _n1) `G]vRI)Ӿ܋i&,DŽtrv+[g-*vM >8֨cMY޼ x)}{yNfR@67< ځ["aXD2`rvk}hvbD k+#s/pJ8s|C'IbMd$.DmO?fj{W3R} xrOk&288#MQHKř(B_\0$ PۼA©n3:v}otse1Qj7ۧoTBк#[Ff쿬_?-avD`xJh!0/p]WwXum^|3YLѐQc2n8T?䩕.W:tL3Hd$wZB唬TN=jN1 @5na`%+քlzSMZj!9O*X m[MO>؁e(d273H ndw۵X.8<C|.2p#pTK%>mNQ*C#H6T J_PE'b;K$%ru~G D]E"{ӡCc 6-nΉX~n2t3\7Lh…ibRtR1ߪsxnV- jހ^m3rcm@K1jR9e+Dc#|;vgpkFm(h~Un#,Pe._MI˒V{_YȄH#xurŽTE>e N>}e]JUջQ=ۊB[ujA@Q8k$ѤQ:#C;L/dfDiDp8; :0q[,cD, 1#L!'?[7.+9ӚE5bB]\2mcua*C8z91)21ߧ8ivZ2Uqɩtms]Zu \J[f%GQK3&#+OO*aMr"Ͽu~ZRT s7/q.FMQٛHq9:궐,/H._+=CH9߼Nc˸@75 8TLl`h]>jtY1:8Ƥ=R%hxbTt>gn餌m[q&jo o{lHtW^RZr:"+f:/^JiTAO3Ms:f!DEdg2HET Ld-H zu_ _H= mAN{T Ҽfoh%uwawBqr#zGGg|(\$EovwfB?ƹ7.6Dfɯn`u{/2sp|b B`b fǩ8x)"Ev-+Q\;L4k2zq\܍LU~=retd}vk)BI=M*EMnknѻ.VoX8@#h#b^ xrKYTM]j헒B?AkR%- fI]:8Uz?JAҎDY1!/ n" ,MY,L^ A8]S)oԿd7X<܏d9H5>d]=Kdp=nuZl#؇Ev4ZO2ylbyB`c| 2-*TZ)(ʒ^L/(rh&z^^0ߪS~u[EK⨇Fèۇ;{2^L![ :|V O-/>bn!Kfx#^qn>XxJU1q^TP|r)߅uBN3 v'e%ěGvg%DcbmHmH#u>Ч{h:;0]8L?QJ%cGsK75{Z3[Q6DDݚ۬Hԭ)ᛨGFb\.pY ."_Ҷ0"X))$/'2av=) MWGӜAgl7q6ŲۑW"U]ch{C=ujB(Ji o3VqrY,n 9t=bd*Q"Y?ducX, %0?%ߠ/U5}ȓJf(X$R\H.08kڵs!Mnsgon@X,`1, DG xK=i>P*iQJ,K$ge0,[u%+D4^ޥX,˹q G#v=׸> $r^9s(7;bL$uh{ˢoX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,bX,˞gշIENDB`structlog-25.5.0/docs/_static/structlog_logo.png0000644000000000000000000045167615077624341016745 0ustar00PNG  IHDR(9T< pHYsodSpIDATx1 @v6SLaEhҡq)\ @H @H 2H)2H)2R) e2R) e2R@ e R@ e R@H @H @H)2H)2H) e2R) eY{s1F)%\kmZx@]ĕ}93#P*_PCzTIJ}ZuwwuwwM[O.+t+OȽO_>///))n Ç_x>{Q௕|-[wtL~'Ȳ믟>S#.ECv+<A22HK/P,mPA2I{ޞ`wkNװhW{ݥY􃓓'L <|SNjɓ'feyyիW512\-\(,ub&' pʪT1Ӹ\={ಲ2QF`y7o,Hn+,"-2?wMxTӧovy[Z2?;~<{Sz<"n"E^)u"*&F 4"\[[`uE^'**JUƥz)yoU_eQ[8n5O6->>>;;{ufG 9"Z&lr)ĎFfF熁q3;,f:7nڵ>ĉ:8+G``țdy.ER_ei-`~ڵ瞻k.yoTn%%%2\wuR֭j"j2nJ},~pAA޽{[b?FPQQ!?yGYL"}g*2hge8w m~ܹs%~$IGPUU%HFy"mJ2橸GT(Ғ%K#b߾}:P8)ʅ5+W>GPML*H$)^Wy L3w n綻׭['5?eQݴiOTQPwGNqJWڲe|d:uJ2ǏE3r݌L{쩪k1$ZW V;0CP_E`c,}:g=F*++Nz뭷I4<<ܤHyeg_- f<Ӽy~pMMު(ky⊌ 812ܓmv>$AlZ%tRbFZ"tF۵k~W佥](ciY q3 vw جJx1ԛ2EΝ;T~S fOz#(iX?xsߘ)!#]^~_JJn*N8(@#Ķmk\DQ,qV<O=_/ಲ2F)JH{-,,߿y%+"]sfy92sh6~͟?KdJQ$K766VJ ƥHa6$:3~pmmo(@ }OEH&OAF͘1o~w󛎖/͡9`|#/d c9[V  '5CfP⋏=/ՑH!G CyYv-1S y2ꫯE.b%mzfsvy-*?#7&IIhHbylhQ@؊C!#+ ,oWi?#iL6cS(OU/Jbczz@`Ijjjwԧ^{ͪbW\A"øpt=$u!L  _A R&(bi"рX z4Dn&oUWF^0˰ͅ(e C])O77eY!?}csOˣqkG#!1O榠 G>*.Eؘi/bDAr`(#CޞS1~+Uр^z8h===>/yZ &B2G]9ںiӦ/~aU"੧4-6aIlm4IE]D"7;v.ew1mnOSZIolU W^/E~a^q#/~8A2/jc,%wr%v\}}}o|goE$ǿ4jI}4sX, G}o#*egÇW4u>E`Ej$IzUWMMM% Ys7fF^^<^2"ARGWྠ1Ub9_t_$S(BiѢXT̖s.h4= (bkj˂XeqiW_OI,>8|sW~k{RR[:z@e?8.[;?W_J4 R)[)ۑQ^ GB>< I":4P 2q\4dɭnE!#DI|H& .*1~yyLRnn"=g`2JXM/ LW`2_k z!ey@6}$ hMcc?x Njm."F+'nU=~a6eL]V/2?߽{`0\`Ry_tly!JNs?0͛79r4?,Pp|I-b#CX([93gM4s__d(b,Z+b8Nnkya/c%)ms(| Ygb$I/ETs#/(e |Y؝cMѰ<|O?|N|Y?IfI4 2Tds-̻ifGmf91ygy`Aܷm˗/Nv#̥H%B2bE]t{߷800@"m}s* ͊M@$1]4(bn1>UdR]ooxqSKK -7olmnnuy!D)a~{^xK#IR< ~/]vHxl$s_:ӊ cA( Ϯ 9(e@ـ"nEi(bYL|veLzBk.߫y=O7 `Wy!D)!|pPwxMJ.ǯyO(徳) `@ȜSШBAS2EΗs#ȁO.22n Q>`7v\7>\y!D)!Q[C|pK5e[l9?\ |[JC1(nn]< MxAsС(e ϵQ hZϻ(G3m 6̚`}уv#oK"h ȋd!J/lxHb[ dDs_px#~ZUOSK"yI$2K-?hn9::#X2ŽsY֫EL;#r\6Ǐe0G?_~t2F^`HolRl 3PidF 'Jr)|}Ue $ + =J"#p&%-h.2R0fq}G(bJPtGfb|[UM}[H,v^<拫x0~liߕϊ6{40ƾY_]G"1Kt}n&S"/]AW1X2P&$ƚʬDpSI7YZUEHG&F_ \!J|dꜶTURMYYsgZUÇIĥ.蝜`K0@"~Dˣ ƣ*'x* ,Lc nE¦yhFrc$wyVu000@HA/E},ffƅ eoKӦa3a*Np׿u ˿KX[-ݝ<$s2n) X2\qQ~D,(r9/ |C*.(:v~RK ,(e M<&~U?p^[Ƕܺ˰LY曭J8TP/VzSF$IwgU.MΖF$ZQ(e ,Cj.SvCByg#D# ~_@8XFi/(p) eVa fD \z΀h|Le~SVO[ѣGctBJΠB2{'SM7g*?k׮%spLn=n"]+LNNZ2DQ+e,ML>>m-$_p`U_%IY&)X>U=VeqUW[}r9-$f|]-jPi0<֣L0A:$ͽtIVBWUl0fee] !D)!g<=a#3IdҥU)<#i'yeyB˫ ʌ#ٰ2_H3km8v+/hPG2 K|re#!rR>8mS @"W^yU?v^hegYL~ Ѥ˹W' 5ټyUi$YX?7ԓHPJG=XDz)+R'xlLC;QBLcLKF᳟UypDNN<-tB"&2,KQ(e ,oID'$i|d=jv+$ַ*s~_' J ,OI7(F&ȘT\bdK_J=C3HD>V>LC E9d`Ő(YHdQX{qO|' a{xJ{FX3OBqC&~UkSSS$R?ZϷ[HD%XY423qӖK}@)@9sl%s8P >6@k 83_?X y'Nm"==BRBxP\9pơޘڷD"ǎʓ /Dvpa ;6y%$272.tg@)eh=S5>& I-$S1\D\<ߍ%񍿊K!$3;52I66l`R|D3̝2Z{PH)`c$uVؿ?Dt=}A{v17l ^KH.̷EeAc~`l4)mps+hOZUD66+q96zTͲjR|ziWgtY2Ӊ"I`$(`|$r뭷Zeµ^K">U' v[kd#uR{`4z/B p^r{>k탻Ib_3Ҝv?r駟F$2L7]:>0t pWd0 Xf%Ը\w6˘|wYHD;S-KAK}|  PwQj{ͺq9# n5{ UڼCCC$R-2cS$Yg#chD~[ $g"P@b`1zXtrr[KFx*u_C}_dyj4{Nzp9HV6 [CRB8{ve9?giD nsG\o'سgt]jB$tpw&c$o

c%˸4},;NFx瞳J뿖ū-^G1eg7>^~{uDvֻ4ϭd%FGWԟ8BQ@n R&:A{#slzC9t9K0|[ߒeDNn-тu$R62!]w#Íj*ٸ-P?6mD3dikpP5+게dMf3;!?>dx<$1MO6 UI+e(&6{/!D)!<L"]oGN[B"k9nJ"_|UJ'?$2Qo :6"iu#m6 k. 2y])f/5tr5^󡉱M"U xDGY&9Noܰ1r^e 7^)!L F~ǽ7vf9 ՚ tMT}Ku[潑1pBd@)@H$4CȞm/de2ebcG[ifţ# IA\ɝ$2)mUl\~"c/sz"}r2B2/nAB29KXQm޻u%|@՛{M``bf`%}2ZpI]-漑FxD d?KY2{|Ʉ#˗厃.Dt70 w`(iG^{rYb^B2W5Iy(̓I<:.(`~|w;V-)wp|o$qd## _J?Ak E)e(e ,z#*L|⩺AۗȂz _VygIEV=DD,FB6n 2~١0x/ P&6uuׄ1CU(z衴Į+5J6NsGm]8zEb_ThaU2/jXsQ&6HmF@2ӹ=xE‹B~z Er߲-tRF&F['n9xJիi"/ =#%2[X/?utEe3˘˲V,%%Fx6(Y8#n_ʎ^&SVI)!ʆEQebHǛwK[68ֹgԸHdxxW^UW]E""/wЀ3 ,ep.f 2䃶6!k\ʽLeM, ߗ|>_/cUUIdgэ/oFGZ)2Ȍ3^/e _0?3Izͺq99;5Hdڵ^#84qj7T8&G wM;$eR|hN+Wq^f<Lhl( =DZ[[wI"gָr۸2#;e/st:CUHDbtX0BxXʘ)e h srnj#"9H϶N"ueߠco5F&F:H+/M(em,r(nGLYLxUu*q) 9]eh*I"#|LPDVe.Ш.g&{K˼g$aE2GW4  r]2ڗ S,fB"]wyT*E"aCr0 Ͻq]hdrrD~X2dX?s˸r˴m骀!ͭu ^{VNy饗jkkId8ݝж4bdxо]uA*!RB]~=S#RNvxܖ2<-tDY9W_&uHe佖`D +Gs7$pYf.<#aaqN>М̍1wQ@XILO'cWg压.D E90yolfԸ] !,̥n1?Vn G/U5dedb4iEQZ85[Im9\yXr^2VB)!t]W\v|s/&w1 e'xGuQIayY p@);hC÷{H2†4wGow]`p1ٳD޼42\-/yq<.g/sUoQ>OBx J FFdIaâyLM)ZϽ;YEzZrEC}兽c߸i/x#m oٲ e||f mW(_L',çZEDzzzyk\s5$V ݁ŵ^J68?>9xkw|(e D#SVɁ"K7x>q4e;#;@?5_l8&HRޙ` k#7}!(e-5554Cp^2 خJܾXD6n8˘>$ve?YmGlD=&ϋ&ˆe'D)!ڔK%,9m[WI ^|x~@]]ݳ>k͝_~yttyܷlHĐiG- pd[ I3{(e;Q̦Ee`$ì:V>|ؚ#>hP2K|j،Gƅ :˔ ڄM/Aѭu.W=Zsd޽$Rs*:eΪ pOHD(exP?| f4U/eB"'Yo~S04&k~p׺298-_B2. 90U害-dDj qžpb>ݐ0Id寽5k:DoǝI9BEhdb42Bb'@)@%B2,^;LR s{`XOO,w?ȦZWa"֔ƎNhg/ϻ&hyj\9G܇ %Yݻe'#?*i"rs;*\q֛H,VUI6r did<Ŭ~[ Wh—2]ɘ?!2諪Q͝%_|s=Id0ǜ8R'|}DʧJohm]h#ýf8\-:vqVv|nMv%@xA 4\`R\a#[^FY0Q:>0 ~WHۧrQsdcZE %cI"x`MCm$r[C΁PH֣|ӟ2/<3K˜ s"$/n@)@i.2>]Xz<4>u9r3,ZDL(׎尗n%kSe`.E)RP_oM252id}HZZϽ?V;e_;`9ID=b]𑍚 2P(D3"2X䄽Ǝ FU)TڮSO=D|/Xp6h}gNz!D)!<20dFb^w(!kR;3fJL\A4.G~C0wa6J6Nm 2 `)B2«2#'m`>nm!["^HҎ7_'GBōbkJO3TFfM8@Ձ m61)EY 2O"^}aBx#J ᕽ]w֕$>a[:NG Ά4LD)-ĹO?Z8`jjf+JqU^$r c<~;,4 Pey^r!D)!+(5<52܃Ijq\)8(r]FtwI1oyFwwp@)@}}=P-e]&9dܡA7<0RUTX/(Uxo^8=@I^kHRbqC&/2e^Ky|B6(e(eL/,jJ>y&[5rɌD|>>{hPOI䷿@)s=G6.sʌN[$A6!#G0@R\z-HXVIoN17*RuzV׷6u`QdD|Ik!R%gD#jd&^rtaS e'n lO6ܚʣdf8tº Oh R&Vuf̑%>>vK֛Ec$^6q2GMY"gJyQdLfǢo8SvtsOC je*D)!Lli*̌4F"K<\gF=8a)Jhk*ٸ[Jb5tr`(#Cǝ2{GxBI봩2ObϯɏG KϨQ&xQRח@\(S!gt!etD$E8*^qg[ 2lܸf0%(H2 3Z); c"U1RSҧ hd6([ 6ڎ[o |[j]$ۍM" M+|n.^ZfJ{RqCA>NYbr_{Aq P"a-PiF(|)qe*<,Svvֻ({Ȇ,1( ]g-%!KǦHPdW5Iy(x!ý₹w0*IL$$Kn<9"d9I^yk~R^{Me0w 𑃹=whiS.fKM,D>sM{{6p>et퉃8(e D#*!EhS)HڲɭKU.ALHU-% M6V.eBBܪz0.q(;K#F9B6{o[H2,_X0seld.Y6Xqvd#e|ԖaM"a\:>@$ˆN U+(eWJ66v2qrk &z3.Ե!aư()U,*MI((e E\_z(ޭ+IdC,RKf$R":H$*E|z[2w}dcoW1rPar"aNGY4u "XY,?s$H샋kqB2V_0jX$.?em!Ub$#yȐ($2,7:ٸ+J;oK5W@)O>I6NcTS3~}`G[}N]ﮜu\{yYKٿ!6;j Ǧ=90UM2RaBp"*I CY3Gu_;{el|򓟴 (eg?K6 }YOXRW[E9Uy1ëy,^&1̵1P(e`5Fޭ޳iyti ʳգ(Bh?#]wO_|] (e?8ޙa9zhOY7+2cmz#fIY8{Q@X~tImTȁݺtԑKHdw׊4$0tPǼzTdY 5wM{nkRM6 $廔i1MĦj=eg4]떾 PdGR!#u E)UQDFoߺ&ԁywoRKCHy#MSZID]X#DQkRA!(ymdN(]p5OЉeۧ%n0ݽ{戟B/S Uׄu.4՜1>X+;CH=z#SFD]d#)4M ~@2V29HٵnC" Cz JEw 2̞dcYПG6/cqvCێnU( ^E;P ?p %kMn LY Q@މ7S#SXK[ശ6ࡉ$鼅|CƟٟY`|_&:ܘ5rtU<0RUʂ.+;%WqRv>N6L(e gJIXT_M5*LT"!f黪Nz#,(e i(J&%pY7N"Z}|V^2:I#{s|d ?7i漑Kje TtH/Mpz@(1eᆞ&r]ҫ$DrGdCu 24if37E!czp.NRvԱ.&.^r8R’oH<\&ӧ/!,Usk0U${(e\W]"0oGI䗿Jfo~1䶔{)#D ͔^zW6i'rqp;9 +kRƇRoʌea2w[U0f9e47H6+z@)l{&әw2cW8!ip)? JKK2[X^d K]F,R~F$Nr l@)l8|0yM8@m ;44ʂOn BWfdGL|T9hj!Q@Xt/Ȝ1Zvf9%^6tNmk^f,$>;Wɿ"@)lؾ};͠0d̔$r 3P+rQt]Gxzð&湳xz4xT(e ,紅(qD&H}}rK~)~dĈΊGr`` axxf*rrTU >eAaPTD&zMrCƥ~ *A2r+@x-H=USޭ+I䤄6RƔDcM"AUI_ P(dRDhDְof>մFjuE”FtN)K[}dëk_Xـ_Jy1zF/c#s@YlT D<Tm/DMcM?ub@)">& c@Uim1’xt7Zk:y,ko>e_>^&B2ƺL co[5Rc壻X I9ܠ&H揘BI"F !5q=X'2w}dc󂷯zzܖNE( }~ģM6^OG]^ K?Jr)^r 1vr)׬'KqoyGȔ4U[ʼex[^m.l\x։ ]v(ޅ=ׄa{{ E,zF'`1+@+\e܊Ȋz|(e ̡j\dld޾vʦ6x70J/^f,$Qg!}l2Rk $-pK(:睃O) C>҇FfV#ٹ2z;Z&KD }rY]~@RY%Ɨ|U4KEȆ)YWx}W@b|[Ęi떶%9,'։ ͶqBJ^2 W`ht8vQtc)S&&+Kn^MG/Q@ +nN^\sؾ.M!me(:{]}=$״y8%"N(e0 f3y?7Œ2C05~Ġٻ6n q31bo*U<<^B2dETY:rڒ"G6.!]9 Yxvl^) ~HHg?J_ly{M4ULhyPT]{swXdTC+o˨; ,RF+RV4 Mr)/ r`Dރ5.\vީIM&7iRl<dc:wG\H5 eAbt~wYJ?{řNӓsh$4ʣ !$@ƀm2H`1skl8\{\u7GSww8{i>.lTwxerΩykR0K \.Q]܂ B_2nt)s߸Ībt0Fs1r8DFF*el7}ZF?z^ 2LF(Iw,߱rU&!bęUd9_6w|.s/rPBSN8N{u 7x?0- dFy4Blnn$#IrB h@(|t Ց@&0ƶdݮrǿ'*N͔0Hxj\&4enkbe 'enGDHG_ on LZ.S񐑱 \5+}W5kNB՞&!w4i󌄃t$nnB`.v9\f2-D(d.G|fˈ{ ddW3m^QaHuA 7 T&ZD:oj { N/]nTmMSb74xq2w6n&T zq8nl قe |/V%Hܹù, ֶg|.= ڒeH1M!ak $+) "02cyM[Ԃd&&+*(2h27q\JK6 "G:-Вf5IWV'ZK*q_gDk). N8s4seMdFg/I$.vX6t\2 :% A,fC%2uQ [J)m̡H%Pm͒,MqYIǂ 43::J!2vrd-ܙ4ԉIؒrLTqsº3XR ݲ@MeA.w'#fr>Ȃtrp5S]]MũTGɌD=i&4 g$373ܜjqqSǒK$޸S=9KdyMd8qe$AvlJI>S-*}F̦L%iu; 絷5$4uTۚDڇUmքQ(=eD"%2/'efc 2W e6֓jd䗿ByόFX94 ]~e/\;%,l¡ ul"#x 6N:b8 -pJ$=eN9-877ec})dFo&+$ԹqQHH9V@k23q0,!24wug{~O6lÌ1_q"zJ z{9C_]X|2Cm-VmB MB!ڇpKI:.;75 Xpayg8l,({F oi=eHyݡa'W_}Br-HUuc,l2ҡ"B:눣gjWt"pwC gkB_۞!#)}̊L 1O( HGe~|.7"e6s~GǦ$pm#=/lØs8Ŕ@2#ڱu>gK"D:TySN_UPt׾2|["}>i%-$9k9wuDј}{?fi78 ּi^;c fhCyxۘCHG)yS!HF6C ; VUB{G; e8p 2hCSźK.czi6$\vl"Isw,l%*L2L6= k!0vp'jQ'#˝ЗH|.]N1>>2ڵtjXX2# [4C4 @kA4~M:|v&uۉ#D(\w!unDF 5ýY"02R[>QDt0B1!]]]Q@(5t,?8d*IvZlD&!ׂP̥cyS;&qɤl: ߵ_3\UD(P"YY!I?GZ ^X&35e.%6"\+Q g?@(~+ґP"3c[MVj$eA 3T]]-~$#ub^73.·@2pNͽ,"IZ8nhR/wcl)2Ra,ܢH:/B K:r)uHH9K~!d,Oa6{tȢy}2<}"S D(W&uцʼ MeQ;yme\BFBϗM!?B~i1M,LȝUWU2C :UJƠF}2QrPE"82CYHd&*% ~j(5KF0Q9\ODڳgBn&ұ0{KJ\4 k0V E}rP&PXvC^7Z=jxO)N NlwOUsL @F."K.@(͛Ih8W17ª )2=cOOW?i~at40g.[ؗ˱(N]x B~k7 "?8 n'#;`[ b?xŪL&I VHes4Ʈu -Yh&/h+N['=éȘڏkˢtx2 %*qxTKP 9X$o %FF"NUs2d/Ice8H:vB }Ssr,IM--$4 Yrh<~}bƟbo~\ZkH=?S D(WUmoB"s|[O_ýYM`dj.|gsYNQn̦LģY"3܊}byM&%kQnI wX}*>~&pX]2~mqɧGez2H)W4DFy4GB ehc܌;!ߵR W^!uNGE6c&NQwgYL:$0O6\+2zbA $D(ѯ j=6∺LսEײu^F'-tbd\t|_JP뤣0^0l+[mHfd&_uwE:BN;fE&#G!9f2pɢTK!>/vmd.OQ ~V2w}cDFm-h 絷pzo1vO:Dav?!s?Sl&X&!Bx(O9eH=MOݾtv"el2j.@"sGZlqu[,C`&! j @(@uu55 u M¦TџWIGUЃ $|nhѼ] «LQx?V%hMoC"sl'#k+XȈ,ZT9M }V2|駊1- :bQq5:$6Ir>u,M, %68:#HdNцʼl DU(\YGLɗGZi"絷Y8_YCȓmce^'q\ c՜T22> SDKƞUA/pKe7pK  eƳM4 /V2_%T72Ռ)-a@H]2"I\qic 1D(cY JI{h|Dh*Vpo֯<25?裏j @(O9Њ:9\v %@LwtQ""S# a=e?#XtOWu12c8Jri Xu`M-)eɆtrz}tY*o xdxmbId+RlgB|L&C>o!-Q2˯, s騏gbJccYE(K8!F&UKP݃dW{~Udd$v-qD"'1 +#hZ'9d̨uV{HCfhPgG{ Bhew7MGzPgCΈJ}T(0UJ ݌Y\*TI_5p.c^">EOOWOf%olIG:>C3HN2rc(A(ciϮ$_Xُ8s0KFήtrRNXY[S@-1FFdY7P[QQA@E9m:$2%yi78ԙMđrXe@ G(?sߌ=CFw4f篢X-yWS2|-"yAGۜ!2Q.n s3ky0:ËZ.;-BXn$ےE`h0炠tN[\1zBAHo6]P={Cdfդ zgF:ZxgsQ>aU06[ zYc%=ġ(3mcEʻh-Q.hnq }GB:+ ª̬5 u"cdƙ FaC"k33Cˆ:eKdV$ݦ0>~nܻ^92U$#6Ixm6ؽ{6CPMe/OB6DXި \wghx.>8*w+ZKX\E.%rsuV-V,"隵\'|RQ@(oL&96p^{KQȌnr꼭G:II^qK=\ ,t;\8"< ٗ '뷋DYHdNT \/ 8wđwtuDÆv2#hR a޲|?9%[eNdN8ԧ,CeMC.;*:;佭'1ʣDfQ8km G9scmkNQ3 "=;fy6eT}uq "Q2o]ސ8.;YCddAf +0ʣ)蘛c}@(C=$wSn@;Ϳ/d#mODnT +e2[WN1 i8l'#{2׸Wb.'qlܸQC@(ߨJFƖtĢdWqtmʜ p~>'Xo,UH\M;d_jC)$tz[[َ}iseD-sj ` UNfEvu ʌw66'5w$dD X\2+|*qJd`:5uS~!ډcMC'2Q$#V P8*:N\wF&ȌKp :DF:꟫\3(;ԟJlPƎP(}KXV:{a0w2} y*]¾JFxK$2k q>_ {gGz `]c(!0^J[-es8d.V$eK`1+ I%2uQ&2^291)q⨭}2w}oE657NL4$XpL'A}u9>nHD>i_ m*]2qdA|9mϐShƒm]$# 8WwuwUıh" /(ѿc5S:l6M!3b$fCs7ee. qLs]%`K"d\O@|@FY~R /\ EO~@ @c4``Av= B1%7.q(2]eB2Ł]"y90CHG.h!rꞞ\e2">P"w]SSqdK;M{ h'q$eH:k*>ifۉ\ YOT8B(0w&& m2pwR=ax5PGY8"Nm|h ȌJt_ '2_-\_E7'Uu^Qd̲t|boPncߵnnnҷxQ'}Ytep&oas[#ӾB2龤_cEspKIct׸哛D-%?P 7 IȈ(j3GJ2' 6P[G gNv⸦94(D(_pqj.C.;8e˖) nw p/nx@a.pg=B"jȜ [u6V}wn{5kB F)圮an5F~`q2Aq]?]"dM,04N/il U&'ɼe({9QɌe@ڌk59!|'B2ޮI"؞(5Q`-Oo7ր_Ȍ?X ƍc N{!3[k v(^ ǚJo!/e ){;c3Id..D0KF'af9Sh>(pwGO9f gQwh\,H+Yzuۣ#>vYK!=CFnoÙF(e(}YɸhpFG:dQ(v1w wd(om3IdnEU)p[!!1B3## ַd⪸7; @(2eX ߞq Ɯ(D(͆#2vi PO D-"FpF2r7k2?d$w thP$cAؑB2jDF`wC%)|'#K3~{[y͒PW_}nh)™vaH%x;@8IGo BGl6Id$ݻz5(>\YΆzg}_,e/Ɉ"K3C8;3pdEHCqdjne%AEaP=žtt0u݌ Hwwe 2"2(F#cneymcXӚg+L.ٿa.F("C.;7M7;Ɉf{W4 B~F9D i-w^d'_•4qz )]Ϯ*IB("]?DF 6ebv2zi 裏FFFcu܁gFL:ʱ :j#TJpG(Vá_0ZQ\l'#Vе;VH2|75P9rd֭ı04 @E:.;6btCgq }/ cV+*)oZ*Qt6V~tm8l z2{i ˉӯDۃs=YMUJ /D(3wۦPߗ%"|{ª 9B$2 7q3[FPG{Y9>wۛ|^Y #\rB|;(Mه& f@QkQ|!2ew+cNyMkȍ ^k8"##wuVPd%B0 k8V>P^1) X\ff̬&2 N{HdDt =/##O>VP@ve@ yš^>2vI%r20E^wN [Pd<\, iw$w] կ$vNk0髖va+-`.reefܷxA;qC^ $2p~22Q iB8rYA+(@(;DFQT_;`#=1l^>Q'ʋtG(5TW18 >Vf'#N?V P? , 3 wTInV`o_@Lro B3w.RFaիWh! `YiwnH;?͹ <L8+Vz`ae'nI( @O!#oB#Gl߾8Jj\]=JuzˈON C(3c~e0URJ*|ti):!5PH6}40W\}nG%4vY\ڢ$/A"ScQMb)=NF&*\=!'돫"501䲓,U! dddd$ҽBNGy8bhs F:X2>7q"8@(3C;V,#Y:>~4{ektUoj$FFo߮P_ d+ 66 L%ұⶱ8VL{(ª><%v2i  oQ;!@(__*BF"k2;% c,1 Bxk[[RH}g 1,5G+  Y3hS2+ۓ@dK:lXrڈ8cND [#DYxKд=A˵W'*\d1/}I G?Q$!#s\k HvEzl0u]{-mo!< Z9^C-.]8C6 /qh1 9!/ CߴOY)ۚFt_M[n$7xC駟]8^@[1Z;$֣|YñB<6@UyzKIZos(+,vսQ@(]%+_o t|~lpI]8.{H,=eOV{Ϫdd^$Gf&>@ 7|3qY$2$̸dұ̹8RN2e*/qg',o&#+KjKoj$uY5ʀR_"qe&,7; ljpEs8bv;Hyf8"nsx`8,%(##BP|_8%v}mw6H$ZCQe,G|aSae'Ђa-2;^pqeپ)-HL@M3{[H. C12S8 Уe^r+M%2>w(qЩWύo(0;zKTyyW/sJ푰 !/ȷPӧOSrhUJV8ZO_W ^U-^^Ϗ8;^sy=^Uoh 4̗k$.nwZL.E(n"3X!uSnw`1wwip0دo ,/~@go͓IJ_]hկ7juQpw@(_GpͱNGkM:kB&';o*yXpDßm]˂9 p"4G;ɅOW>>XPy1pպb\rZ*±*b$yơ&u+j'ñy??2 O,0:oy͛bS-0&MPFfL;e*B ;Dza:%:6\Z㻪6<ҁ8$a{? ZɅGS>֟̑|e8Y[d"w@XS Ecؙ䓏L^%#xz&n}h=4ygM[&^HRx[83L<e8y$"<&;^-`.0z&vEsdΑɆk'b ;+ND2p/k*γ\ Oz(Ūʰl oXjv}(1*'/೿³;Shv&.L^$l2Ii Ik׮x^PTTD`(ݞ& RcVո5tm+6˝ ;|/{J< ;X,Si$nܸvڍ5W@('~v(,hȴ5-cH [iaT=|nsQ)+=Q)EֺQ)|DwfTJVCRlU,rW 0 ;NtvT͓|SrrTʑ >|Q) pyJGjdb*Bm*PUGQ`'>l\4f;ʋ#,3qOyFMPFT!]o*^>m,) x[V7l׌i7w5'=$Bxa$K_yȎܾڑ|MeV6lm㈇{yxg,RXS~c,5kGS#b r_*xˀ$f<Bn*2/t#I?9ytrq^k,u!?I4%oGp/B m)>k"F>vza%"O~IB$@uI #'`Me~}GS‹N!C! ~SSݹY y7r, pw`I0["Nv}ZsURu1Iִi&\-"J7|32 fM$a*'2 y=EmBhQ|ɜ;[$9w.\PkX"Ѧ{nd5IJC{5 &3{t=dYgj!dgڢIsPd:Iy×*;I PD Q6|vOk^tO>SGʏ;F.i-F(=Tݸa-UxPH!ύ+G CU}Qd[|]p:C̕zPK_R<exIg=N1JDek ja]nNƹu(l8W_?18#l5 Z4t]TK4-`Ŏ]~o'H9 P4M#ms;t  /$(~\ܝ4rرyǦ-J_j^+<$QsP0p޾|pCp\`u0B,eDFE<e mGy1I0w.[V215 f&oE@Eݭ$: 'G z y@bXzzM](XS ۷P7=ŠTDc/I(n,rZr_d.6X$QS!|X4%2K-$2paom14<5%eW7{IDruvexH;n$ /}(l[)̨*Nxu7k(O; Ydoh+chETU:pu4uWBؚ4ͰdW4:j9U$z8YPUUE'WzkHT̉cflHF߻iMđ y5uALt.,eq(#QUɖ"r5uʌMٽCH-ɇ ?\Ǔi[M%!2Xvm eLr|fp١ek /~M!kWfR%M h/Κ\coU2' E68D«W#Φ/1Í(3B~% "|$!t$;,5^Cו$4t$#(\4D.Ze\" ugb$.q@ۻb|zZWb$mFL I7pC, ez$qxbaҙ̮&iJRoP0`s{yh!IwPX]`*콤,v^a͔pz(++Kd](o;Gb$̬e\Cik*¹5SˬjT2eEIG"t653䇫Wa#ԞAT,2 "C eꚥ/=m$akZ%23r遭}\fM\D3C dQ(3 B42I@.P[z($q8_RUU:"ċG»[ ӡr:قI\Dlөe@hHչeB+B$O/' q~ȢP;$:ʋ݋D'||riK,f'n ŖJ2 qS\4 w,ኊ\W{=y%Fe 뺪ī B***H< =>IJDBU䑋BS_.P푪h/+BżvFJҾU($dK(??c2[Hwet4$Y|.ՏWfx(S72- {Ŋhena = &_ Y1Zh(ʿȊPݻwe,|/z Y̔k(nm%>Mp%Bb\.CEo cbW*Ȏ~M2 OAgS$aԞ #u«)Z_9L e>?\2dDC+ 45YL *"Ɇnb]]; C>OD4łg] Ԟ M ᢹweUf_B\ 7Tȯz !HPz!ܩBa!H CVZEkłgIl,_:05R}L?v2.P1`˪PNp|І8UI%Fe3O_~yLe(0D{ȶxr .t-#"^"q'2>\lmE!ٽCHxka!F易lf^A@E eHbMe$4 lg8޿z%+ e4PkwvZ~b$>u_ExXB[>8DH▱9zGn5x}}ˆiRxhP-0CwWSreE9ʼi;ރy5`#$%o e(..oݤ A^3MmOeN4.~(ӐP敞9]"ÿf#$!f>DI$8 BO$yz %>$ŽK2P*0 U%6r,yLwR~({jʦm>eZJ Q0s}:0M~%eشiIY0®ILO0dY$a*Jf05\\Jo(S_Pٮ2mDFy| fIbKM( A/R%eX̞nZD 6IhRl]#jʘiɵe +]U7- ΟF0s3wfh IXP_|$X qZ&O`رFT`f!Ue/J(*(CU߇0CIPU|Lhznڈg,|a{ $qyn3^p[q@dW5ur1/B(ӘPU-]{hGf eٽiGwqG e'/oa#;*L%|F X? r1ZͺP%3>0`$᜽ 3xw,I(W^IynHжx 0f/ &8ٖP涎D( =/)qrQ-ut*$$ P$XKIx}$BĽ=(|p4Xb]y;Bۊ%3o[?~-%B$?`C?$QAxYo;f5ëP*0}"M.:췇ST%fY1(pL>o$F"h>$P0;Kg?YEe&km[P]$.^׊RQXf2WB- STUb$YY0aڇ>8Vu/}Pj$S`i*I^QRY˔5  F"L?5O/oYpjظqc ex'IbYpK!EŎ}i$1T:V|o|f(9E<ͮD+xP ]U> x`DTU׾`BITB"L>uX]Y:婣cU~rQ3>=KU((s6H. x.fo"B鹟^,Z$CPo}[lKRr=+H4l%}A  \F~~W\fiCUbr\]Kr=]A{iOI444P.$64RAO *AHO:(h* rʥ eKdK}l+HI0R1 B>NxOO@(@  OɺᓻLȶH"P'0{sttmsel.!gB WˋHɓt${RA M%Pvl:f0(W.f(3V%kcYBxdm3IPfBP-0%}~IWP>kY 5yIl+Ϸ$LMElex^5(09w(B~_k&^>8E ¡ zo%˼Es 鬊 K69^#UEn̅!oh6RR)tcs$vO ?I,//NEݭ$ 6$!@p}]9ILCNdVbCsp2!74h$T` }q}/&BH.jޱ;!R H`u8jO4 '2^3'}$aeD(Zx K_Ix M] |`"^O2B XK\X2jb\1!45?^O‹(Fe$rx:e8|0IlYVE Be$\Tg腶UqlV(Kn^VE. E<]>P&hΒ rqpMS6ky% ptMW)syP*0ut8e@|$Į.gkc-U !n!4E4LRx+($(c訛6yl弨/PC N.ua,' zb:fQ-0ն7 H-o6ID.d޴q2hHI\+ I\7s+ȅ&ck#Ʉ2-AsD񞶜 ! {F/h2蚮BZ0E^m}(N2 tuue)BbJ٤(kSUI5$xG|h\Չ̖YCOBJ.>0*= U%BOH_҅u8XAP;3$JlA~B"Z !(SM +ItUGr* {tY*ݽ4PCuIvs܄hSLGcX0L5 0aԔ r˦{_~UUU4uAnE~m?O )!ĝ[fA 4@_UQFw.굫^ ^c-W M95~ Pn}7%'5/fRS(I?f˅&xP^͕ )8i_Ž_[[C>y'x'0Gz0<$z{{  g$v~(/qL7[o%%5 TT|9&ةMj m$v>T 'W ! umPfY:Uyּ 'v zRڿ_=vS|U|r&7)g>|~!<}l9O X {BA U@fyu_[tUW}`BR#X95LnK{e!^ryh]kfCPZpN޾$bX2`!\$Ko89Зf@L=TxuDRchZ]ĭ3IpWUM^Sgկ!/FL% 0uS5yn{M.8'C#??P̏'թʛGa #}A3'f|(7j3[RY\l: AMj*B[-AxNx&׿4/p0,uc%>]$uQ8~x2`~x$:2s{[ ifuȫr^6qGwWwcceqc~UQAxdrSS_90o?Ol 6҂5 ɏ匏]r_)mM(6xN;D痿e2`}Xda2}uU`K/Oʷ~mߺ6Rt ye4|hB3ckG6FSceКÃ7) ʭ2CyZxO[y+98=s~ wY14׵&LutL>{SU;Nd)<\G>_mai<3%+75$m&C6+̧oeVӯn*xgYn&vŋkv c:ěGQ[ ?wV玩e(+aWsX$ K̨Li \U5KD"ǥܮd=2]Ż7Z"E'B,Pww l0͍D,/Vc>1TԐ¦BaO4U6lO$Ԍ BU/v[JmnkQ\6 L SSSbGY}2푈KTRuJ⑾7PіǮ2(&lvb0G(Mw (Y9σPF GFz+1Lև62Q&v9Ɗe2`f2_v\4B BQp rjF]p(]z SQ9ysDŽהN(Ν; P⥗^"5~ךO1B_eh?]$*6Umn} K-=AeUf1Yn &2ij\X(s'SM.[Z ESSS2@*BqDhyy+7%߇^W2Pr4N]KZ6AMj(le*&yk&1e頻" f=,Oo6E@(y΍\D,w}2CɊaT}Xش°]C [RZ[.MVv'wY-J /7~Bqwde XU||{*6O0T_>YP7X-*d#?UWڈPL("=.Y$ꐍJg8oC*CНJv ɧ {ҥ,e8B! 21W7ܵcn[P+Ȳ-!\MCeڙ+m=נ~q)k(YAY2 '%|>@[W>!~뷲 4˿L(V#|u`*l 5"Qk{pO!w\,cP ٬Z5 eh[8`e?? pLx&g UzD|jz|,]Pv,x<*p Sp=m\qe |dSgϳv4ЎS CPNȱ݉H{ 2rBVFMf0ܲ M[kP sGEWWW3@($g]. w}l"Ľ޻ww^ͅ2ݦraƫboz6py(3F(KrVHԵhD>KpPVYt9 B,u7+Sa$Y \أP'[,*;,q[&ӆe n۫ܲIZo4fCj-xS2ڪ F}P߿PMlM5$ Pn ˿1KtokN3|Z_>ulJ׻v]U n2 6l/-r{y(3P |}~h_GwbP*M_0 JƐPѨ "?<ӇP&" [.kExC jΔE(3PF/>XV:7VEpə;@(RT e,2;0K=d(#9|хoVV X&M x C͘*:ЍPFw>;7'h3 D6Ql Ƒ(2uet7 :Jykubrr2 ʀr G7c jvvD,Ǘ}%26VQPzan 5 |e:U&B]?ݵ <6=)/nv~e u3t˗2 e 7$\6!ZrW Q@]Ǝ6 ŋ/7 غu+m`j#bEB߈},lD($2i. xi^9S*ʔ·v)k(YP^{w\ 1~ҠwNueG}$"aBrB+7oA(ssgY ¯cCP%Llu-/˨b&'` eVosMHZS2r??e @(Ο?O(R;֒r$e÷rƙeVD<`i[Els4tBOxQ:`vǢW Dxȡát}]M5p&B(n^e@cc#eCP#>B_,зla:2+kg4e fz|E(LTEMڵ^#tB(cJC^;m0xO_LәZO6UHmc-2]c]5tO~̂DǑ9$; bkjBqƬe%Oa䦆|zISk3¦јVPf}uđl ƺ4´)>P‰&K5d3?5ܞ @܆eoo لٷ~yfQ{ }! |ӵMa.&nT$0I*i=ё<_׋q&~iP455eu@(.\ ]ڞI U<;Aoj|ı+nX?PfE=fY峨SWc>Bx,+YFeq -25Q%]0l]z  91Vهg"y }c^owMMk(A%pS(h7TXNV%Zs*;}.P;fM %FicpK ߞ !aMy~3 ]y }!<:=PL}o x!Lʅ2dԮHcDCGŝc@  xwDsvռu*>qp;%G4`aScy1TOl+gh͸dQecŖPhkl&#B9|kxuuJ"ַe@siB1`ح-/QcݼiFݝ"(P *\NA&#˶|/ϏeL {6^k]a&.#padQ(6c`ۛ3bvv6 ʀRtvk[~ZB_MN C8휺M&|b 8-,LpUQ;xg]ct$I.]2t_%BʘtE}a( ߍ[>\'K1bQ"Z*BBԭFM]d)^eF e }hf-Em#??e@266F(ubЗ(! B }=01P+[!$={̦C=u) Zw'*b&bT$E(% ĜG"y`32D9 &\o*-mO9|7„u i,6 v X%KWe{{+<˨Xq}\teq]CVҸZ[ sCa29{,1VgFnu&ԗ#76J(̂R_TJ6Uzlü]j("ȫ ݲe bIx+`gO(,@(JT*E(]׍wz[軾'p/_pqlD I%nqD4=>'gZ%AZk(Cz4L( ''~@2n7h4e@WW1V7/ X؃[ C,l:켺u"B/:mوPm ^,BLZWB e mv /oagBPZ9rPLU0VWwOW9ܹWN0rPbaӖgte(+<[sZ D&ưZϡP(s_WdM IB{,@(J?zy) +* }c{YB_PfBd xTơ ` *Z']Ze |t^٬TzEf+o6 (!~'Pf̵e̶ڀ+nM=C`aSS0hE~Ea|2\2ʆ&:nV]1LJVRB(a1 ܺ`qXOOdBP*ýj)]γLq:_>B_X,w "¦O2Un5;_:my(?{u%FQ X-.5 Dt2 ᑾzBۛe@I,KPV;>D;[2, C,lxܢMuAʔZ뉪TvRw> Fz0Ζ Ͳ_{a܀s@1'Z,H<7UxIBolZ5KZ ZkG2>\2C=uKNBQQQ8 ?BaeKmC5~'Q&v17.\MkͲʅMv*^4B5]63Qs5~߸VP­=J7#,ǒjŮ6Y-ʀBq]}fْ }9Fsn<EDiLۿ[,\@,lj-#6d32zL}sa*97܌ʵ,u8SI.D2-eZzx`ݻ[؄ng5 @(~~PĜVLepm]V(1mʆqtBw>]kA"d%+УPsOud,~L(~1MBI(N 6a]W2ry~:{QPںP e 6.lY& UvӦ<¦eʼ0yHKa0kNh:GPfUӡ e1>9$\H&B(= p(tnznv}^;Xseb^;wনeBaG(baS8N;lhu;V9ysaƐGZkaBV u)i>p]/p:YPh~P$6̬Ͼ=omJlJ"iG(' 8u ۗ #98}Ϻ2*_rro< g\JeբtM77 MMMV ܋)ʍ $Ba7Xo\$ #'c(Z(CDDU!:>X@x|ݴRY؇gb||<2wߥ8|yܷފ QcݼiOnhoC(5#q˲A]:c;Z3:m-2T&nQ*͡L̺VCa7W$aۋ}yҥKٵ vyg' me4*ϤBA(ux-Y}E]n(svls)D&4##у];$APNgN5s.:ŋ/][Bz E׶<ӓ[ EKʴ BM>ȫ\sZs N@Ȋ2/[[e2Dk!BXtA97@ɮ! _UB2Ij\oIrJueJI,lJ"ǩĭ)h6Y^(ýI@Tad٘1J"YߎPg :Jdy>Y^{e PlܸP̵dm*9ՙз=~𡒾A8ڂP¦\ra2-Uҡ [eW10Ae(,4n2zgFMԗ ӧOgP|<ӿ7k0zZ^͛tppLIMڅM"ώd"Oofּ;ӷMt4<,cP lHh /_!' |9_V{f ɮ x EBEo㥓'qp >&٬raY4i_mK*Z OZGC2mzBx9`eni-!I {]}Bku"_h/LF XأD(D(SbaSs(d7:&au\%^z*xӵ֭>jPfB7rx&ܘ"֭ˮ2 +B!G(Eqqtn<75"ѥXTYAen'Aݷ^=93;<ӐFhyKs O:wؕΟlS[ .]ʮ& `*6$S]uHyB2M*^R-rBvg:KAP…aF*2磸J(Ν;]5B;Bqvn }Qۿu76") ao<uةM+eNX:O B2h->SG(@(~gP,>+ig9nSW˧t_ 4 B ;JW]ZCP۶o3W9|Vzu0tW|]B011A(r 2%\VLU8|9f iG(Sbad" >ܟ\ׁP|ś4 I=1G}[nɮ %_:s}:+X_R$/ eOt7!U8 ;D~oʴs[Jd$>/ 2̙3dB_!٣P}B '2\ztlh +@,P&+tBM2(R*g )ۼl 7le@*"wNuc<77e"nsex_pPH8 ϲdUY6`G¹?D(K!|t0HH%lQeɟ ЗUW !BY.g2H8tZE0I\A?,,S s""N2e-V+I=?4G(DQ-P=zPUFK~lwD|;p* #;lpFj4]&hX `@ !B= /ESᲽ^AL(~leG}dX;FK7f,œe`iW6HApk5^O<.NdY6dvǢC23B}.E)_)PfO{5hjj Pȏ. ADže Dpcc \9P|[ʮ, F =2#^=1U]O_41!Ue;NB˩9fPZ4QX&a\">wi6 J,b$!۶mˮ }Ba%Tk3 y~coj|浫 ,^L& 49dZz@1$kC2-݌T#%uϺnB!ŋ+@( TM\smC !7Q&޽sNŠL \MV~:i/^eo{=T&(4|PfsW.x2Zl"=ߓ]BpI.5TB;/ee`Q+{˲qktR79v_C=uWh(hYy'KgYŢM psWIUlz~ǨʎP(]& { EKīB(>i!lC([k+{MXpe/ fYbB7ܮT!B׶8*隄홸vv92 py C3}Z+ C/˳5D(^8^s<۬V׳R0IQZ2djqi"҃P y0=WSLOEPر# ʀ_5Ba3 ڄDtÇ?!- BX*{=f1 NGȕ0\A0C2@ߥN t,@( cnnPlJoM@llބ !B:!ȱ ntZ{. sW-t">0&IJv~qngBP~ ~ڞI 'VpA+ei&Ri6n}Q,et ;١to_9lW2,@( ^#qum\DL(žz¦C?B22*{]{$@z`YV[L3ln˶:%2gz< 7\2S

s\/IYE1v&˰LW?Iw]AC<H$] WB(:bտ_5>Z%[l,6"PFpbO'96WX8q$X@f1K~ _۵b2) ZVQŝcW&dBK.y.?;o }9)Bcet\ٛp:eqٕez: ۵mV _6Kk842•p}[iӰ|n*2-/سgO *ٟYB-[[?0 YC(+SZ%I,SH v:"ANg<yܒdb׬d1rT$tv!E(aKxSCr5nn!$]x1 5չn\֔`1MU.E(SNdpj8F!FMTma"T*6 xkl eBpM>߯ޘZ.gƐp^Xx(޴mECY?=BPx}&Yfes @+N4&D֨2UQBݝe5yEm+Žd(vU9 eP ec4T 0Ƅ]j l=5%A Wg[w߆B0̿gBjBqHK }J-&lE(A(!Tr(p"O+o~٫ʀ>f eyT"2{Q /aU8PfEN~e/Pve1 +EBB B~2Ob&MGW\6"٫ʀ!nieUqseTZ\J2kp2x餲096h$2$x 2VѨT屶} <@($Iʽ " $;Z*WWSq}\R3 BX Bb:^is:yztS a4]eRcR(&9m hVZɄP0Vc)mVR& 'xPdDU+K'֯2p(Yr, Wm<ag }`0X!q&H+L AF=Dh|f o  ۿ[ 4ⶉe^x.Y$ 0xS%X !B0Be8IӧP mv9&Q4 <4JbRT(PK&! t˭Xv^lg? O(LUR B2~BtRc39^L_1b '>@(SbBxI'?lP\pl6gF Km }(B_aҏPfɧBwİ,+K$<)aܲT|B#VR |GuaOe| E*wc}2O.M3%: 22Ws4[,d{(3E5IC2y}LC(!SzsHsK!_W-e  K >oks^ıF„߇P&DŽFY,8R>cLdC(!=݂W0^Z˻ƯxeB/Eʋ*vDS"-ᐁaYNg$3*haQKoF(!iFA!8.L{O(eBpS}ט+=B_aE(dVa>?Bg^j5y*te p}wU%Qx[]ͣWXr \k^Hm[۞N?sn0E(s3i$XF!,u0C{$|)P&‡;fa{-P<#ٲ _5B!y5yV͛V !y=eZ丫N9.%˥gQ(8Z B!2:`,>?7r:F(xlA( he4Czy@d Դ:sW el\6:F>׿<7aazgfRw&ypm&WRWj^b^ӈHC/p,@tY\\l*2}]Fb}",|Cc71F,eq1,V\aL&ܾ &#f<\Y<)c;"Vw[OAGԴI eW_}5]?>E{.&u/Q]Yq}L )  y󳙒h;6xt}.بfLNRF.eq!lmo5X`{Oyϝ}bey2]&ҫRI&t:C6U{xvS'])zQ !}alE!oV@_Iq'ꇻi\3dRc|p=J%̮9|U)ܺuXZЕF ןk 2ZIDp4Y biGn4X.N;LjJ{R; ݲIJberqiNo}GoƋ $eꩤpUU-SBft2[q{+2=1ѱkylxČ2< xqkK.f}8ܩDڙ'O62??2_?Z~nj.'FGB,ZiOQF2t0MTf)  }owi3i=f+y4cY.;y9q:TwjI7 x2\mz#˿˛HoKr:T<4՚cG?^Ҧ Rfr o@^H0졯+u6VWTU!F7 0{( 8A [NMNd2>EqvJgM^O-]e oMs8$ȏH)={REYf427}7]$5^4 gR%x,~z^b腦݃i~HXXC .ˡeuRH;|]DB{+Z,Ls,ky:EБtpٙi:"ZƹM)f  ehhA~Wl"T\~MxE2L@U+̙)zzϕg|KL!F160fmD[}}%aJJ=xu#fe0R)*aڦ` r00J*ܶnH!\tAM6"iM's^&aO07KӼ4Z&*GWSi/L J|_D[#R(ggB{ P0m.eD^x@!zBW2Z!i'GS(r\2*``!e|,s_<0Ow>=vLNL0 qJ)Li)ph!:3P+/'/RCz) ]+4Mڣ-4CL(<2|4o2)#w'bs|mnxCepЬģLJSX10tyR훇`}$KW^y@!z׻.!ECW@?WG-4՟HUo:I_(lKb0@flctmD<6X2Jd,+-wD+eg9}ia~\ls)MWrsCA8k MR&]8`t8u*j ӧLsN)W ȲrV*tcgr;+=؈v aFlL9Q%LLL4HOO|͚d_q<*ne-4F|:;Zr!/&˲^@h3%`H%{+6&r0~Tp)̺R(D!#=,ݍ7*.W^m"2I("qLWErò,X~#kt0G1(d)3IWЩ:t2QUv<}Ac\/F¡, i!#᭍ct0p=\)3y|beyqx(hq+ rꫯ6H#+;Vxz|t-  TFn6ZEj NDA``U]MRze5+PHq8V\}_8}@>T)3NKҺ ccz62(yT J|;J`mq%I#f$ 0 &*͑B'=dFt6 KK;ntՇʥP0x2M1J'ǝYKC(X ۰aua޴䈄Pv&ؽN&,c5{L( F2yaL)m!8qi@ bHe3-جo#tXwSPUSk+zR& 7x؀xi3=)AO؍t"J>waf+Gx]ҡHgAc^~ 'IEo Vo7S8^*]77Hdllr Ms+hit+p\*]޽k:p)V*gcyl͔II&*l*4 Z%e|.H^ c,oS02t0#?=)r]& exK_岆'ʻ\h@6 M3hA C1#quLG/omۨSI[vQ0l*y\zr]D2 򶷽Uܽ#Tڣf8 J`bRf@_S0%RZ}2D .4H] ;5Y;7 eVwY䥅w_y8]qm=]uL:{Χ666JNfy~NCmH+e=ۭ`$ f4^e _`[B}),,,] /ONOxqX7!߯(cܓ+t  ejɄ_[nLtl&wm&!qlmC!J(=sNqzj,*y/h (tgAtٟ٦)ܸq2 /!%#c1OgِM׏{ikJ6)S Q.=ESU~B;Je2)#D2`t:-W0Y?K#7`Z.IM;Ry饗.~pQӸ~Lg~{R& (N]MuGgBf<ǡnjGHR,X.e6 e:E0V+M~x]i5mH~.ӹ5O48Nf"NNv B"7Q RfX6,b3 /eYN'f 2ɄWtR^`)F"Q*o',W]ʦ1 (,xdy0:77K^-eKEE ;h3g3!Շ,0jABIg.`4+CTD gFL2~b>g{1;jX$ i>W W݈C2G0ĺ,q˃)P0#?F& )܎c]1 ՇGS!AM ONto 2ȿ8񉱑n\l74qVv"p\iKTӼ٫{|Hbb 5?`R"9rڪ,!߉[Ѝ9p) D⸸"wd O}G]>3>@ w322Bt9WeɾX4q9ɺ#>գؼIeӮaa ˲pJe}%niv,LN\.{Rɞ2>4;П L8]#. ހB=#|w|GADZpU04p B,Xx>, ^8fOIJ  8~=Ʈm&IJc+WW_ѕeUU _HƢVK(S @rAGD e ]-dpQ2I~K,4(f¡4/o]އQ~L#2dAy5ZJN'J&`S:$FIzK(xlTO u<֝| 2w~w]d%x0MɜƺTUE1 >u['eʡ1J*&셑AH(bN<X&HJs+]6T{ԼG̘J]`D p(h]4ѿf׃@ ;T|9`0DIc.Iڵ4RD,jH:^8vt|B)Z(:]0ܹsMR7drR8u ~g0͑B E70jb>_X-f/_^$ 075% N'zDY>nN+x\s,|]Z>Ko7'rCUItyD e{sqR6݁ YeBA$1Ś穦i]Zxˣ٣ɤ؟C2h3EC!aW$c:C?5݀sK?MR79Ct :y4`L*h``á|$BZJ;pX//QAZx4}%,70 j'*pU#2 +>5^鮛5 r@ ?DtIRw-uFбbUE?aYvin{pLOLH[)ޕ}%BJJpPWnX4rH_?@#辪zMR1OO 0HɣbLL[L$dv[HA@ y0wLBUC8t6ell@ gn{gj>qI~d2$X ;Ё--~:"N1XT[:q:Ojj5}Ci@Co]|f 2 }rCL*Y4Qu0M:t2ZMCSx4U!afqd2@fgEA3)smsDUHŋDTn F}04bE!|=12``׾R/@ o}[w8mu\i@vK8t>g>:z_Im)nD?(dʥ"[9^*kJ~D+lRoV"X)qLu>󻴙+`}%,LR @>&)7~7vIxQuZ}/I~ zRX!5o3A4X. i$p ٬W.MR1ɫv.R@oϙ 5I0ms~H(R4" ®G7|%J<1G䓚LZ?>QŽ^千es=D e4 ~]nL-;d{JwmVx##b2֖6S:W='W]ZA|:4-S`?n$хh"2y\BtƢГ(OT"{@AtG A˙OP45LXhd~uvwp̙}%ɢ@t)Օ\d9)ip*#ш.{D e%UzG#-\$uI}C}XlOƽs>|pR$hA) NՕ.Wpte%Jhbf2+:k#// %k}6k@׈gyqcEEM ٌ:-kJl;'UʝzR`\LGܮe,nq}r&M '/88 a!DAЧsip^n6jo7m%]U_kYڳӍ#+iJD$hYfhD@^iZEp)ڟA>4%8w˩(WJxCGA~iXʀO~Đ2((n6C.$M_q. {}\1Gs..g.O% U KUo Л&1G23 1DteBozQ].Uy<{[YSˤθY' biXt +I\, ]U۳gy@J9b%=fN=~B1 p8|q`)o$(+zͧz٠s}-fMEK7L♟0'b>ONd)<70~<8l\Ey4zg7İtq`)~_2 rAƐ"^(dK{h 1];w2C >@ ?'R  ^~9v SG| {R=]rS-ejdȉD,Ùq/Եr,U>Cs9E c½J(R<İ^ȣ]m.5I\+%hkeF&fg1I2Nh'ɵފㅔLJ}~%h(M\co|(R|c#r$2^!.4bQr! Vn7I .t.%Q<_9x'T2INDaoNZ6uGC ^Ź%yӖpa\B5tzQ_{Xʀg\Tq ڪ$$k 4 /eke1L8pbLD_k>gr [NuaT.bBȾ;L^dXʀo.C۩`Km1:Px3 9p2bDвp8p2tcAe 8g+Q p=Z1lmm]p"Xʀ$Ce>ikDNȢnٳK-1bԁ`4!', ѡdc&s!bиls j 9qbdBv}YLKp||L w6PbF&,h`K2Fx CǴ'p(DN 'b3pbu &ΙxP g*c#2!\/oo0Y_,e?O ˙4JnRUb:2:]/]LE3S}\0 #nlHެ (ɁeJɀ",=76a[;|;/2_$e@8l@D ugr=7$sipDܠ I ؠLGL5j 3ϐȲꫯb Xʀ/Ġ2J ^7(dk:]+fTGmPUka6&5RD e &΍BB%rP77gx@8$%ޅBcR˧n>*0ܫA 'B}b٥˷ݜXxꕊu婢(8 E!KF1؇4Q5S B:Z` 1\rcRubxbfBj7û l<ĵOTvS$9|{CSEQF\!g]UI$'v ,1.fK {JB3@!R]1Uy6b 8` !&MS~͏ !IǧL:E6R02$Z ȅB(pTQf.eģ*]~ 1AH.0L\<:A'$8;re&e-wЮ\XE構F`)^ySxZs1!|KkNErBĵ|'$ęOĈT(hzkWc&"og8$FrEs*1pR& +K𖷼Rt]b輻ףr!4Q6۶R^vSeW;8 ɲL6<  1TG`OE@bֲ]Bs.!ިG?QLKCNrs.!|kH"9b/z1e*EcaIQa q|Db&qQLF,Xʧs Q_ i3VAm/} R,8EQn 1i!s9՝2A٥Xa Ω+ lW<@EpD[E!$HEBP3k|U3I.4z~L 9z``)ybXPtN  %Bi4gATKZʬ#I'18yVIpD^X4J61h=m?W%Tv1Q '>A PEBRAr")ۥ$y($q٥1X$'Jbvp{c9yVؚ%1ާֹ=sA\XHY_Ek _b`(R|aLUAс5߷ڍ:9!by-q٥h*0>yS'wv76pD%QGf}t4L 0`lLEl6!OTtd<8 ?O[@mQw N'ƈa}}})RebxhyNOWRI.T"'{ʠ.4Fk$ QVȝ ȃLlD-ӥ&#j'Dƃ4Hpjͅybxꩧ<,e&b1SGSzMt Z!p.%A3M%nׄn-/ሼ&<ٶ$Qh5ef%&o}[G2S1-No\:0MyK2]3 cQTv;Ng~G)lN&ɫ21x)n9rBJ *CƾW^8 _O(=pz|n_Ʌex -F<+KU.-,;;-mY6gV1B)a #p载e4QpJd.8 կTU%z>p!LEW1?Qvi.y($!&!+;yr9yDx׵`l,V,L+,U&'I:UJi1 e"XʀEbxbNs82J/-ceIb/9[O$|BE8"gj5 %3X z*89K ;3q`)|Ab8QyUܲK(׍-p}״ xTsNRyAi.5?ʟG!F!a(7͠J6C 8q`)>l<'N&M.^96q֖]Tn!&A`9;CTJEDp)hYdc^r_k &' &ޣjȅF<$I4 K~b'w.-a>cbe.M 9 !\A ؅jlQW~5|\DrThi24mX(R|%YQ&Oܽș\Rڳ BLm|ox™z8 w6[pIW~>%\·B**.81yZ6ĉ`)B1<pB|TU 2Dȿ}lI ',[F V| SēFs%JN2K\24u_Hƈ!R&*95; hɠ]H'__8,e?qbD(CпWndLU(&jBK1Dtl,R@c1Dp) Az\^ $x+D@#'A(o i$CG2_"14e'+I3@.Q&%260_a1#wbh,JH6k'Vs>O0Sai>ISez˲;,ec'kQPRC,'۵eqdxö%Zsffcl܉8 7}aO ~)'OIx:GtaTn Ǜk1o>,e@>'GWP_|zɅB(pXMe\}v3l:MAшw\96 @V$Q6!6a u!zOom/w8 wy'1in;f($r1[Bv{Jy1b0J;eF,, dc&uUg]JZLN(x\yZ&7}Xʀ|ͦQ<.|v_ɅeT 2]ţky#L6T"Ah* Wr*A i[i® 9RuyY8W򕡏KĐ Q,w~Nv #R&:M\"D<͆֌=Ĵ5}_A#IpI׉G&\'&΍B"H&Kw{Z:q`#l(R|k_#ySxs_EɅD@۫hKKq`)õz%#:mDAh&B\35%.MdIUջ]ŨmвEQpD#Y.'` v5W%TZ>/7b˿KJK4՝Rx)TL'A-)5MܑeGmL6J&I `t*鴥 ˙c~hohXʀ㟑 vJNc bl! lp 01H#Cѡf{)9?3uA $xQYȉ?i 0ķ_#G2뿈A$1 9Ty.t*٥ 1;^iDaKs X&r6:rJ*\~y Ƙ2X68 Ň+ |rXs CTXOjӚ]*գC ympx%I2(\YꐍL,2EMC"M3w!H;# |zG #H2`}}޶04b^$t'EZ+ĹnfKܝ1m1!)򩥌(8Q*[.LU7mD k;aۚ#8 =1UJ(L#oB)lV\3%]Qw:BL,q#$(2fimT&t%x[0 ȇ'GӌAeG:,eC=EaVq b/}/٥r.Mpx4F88 OE$QRngli\ә`.%BNȢx) w󝑎Ku&8lZC!r!4 osKvYg2ء»_#_ʜSၢ(ģv/H0M =Õ8\-UepёKT (*+Y 2.|vi]GG U=d K8\S$fSM>z nE"'4I{1 B6FWT0,e@*"vQ!|f} ٠qPq R%1'J p.+ģ)"Ipr5@qLI4KCPãjr>_xob뮻0,ek׈7(O5+[joe. 1I[^PR6 jP«R@&<΄[&z!9򛛳m cA 0,e~bXP(|n߈EɅd@+nѝ db ^A< 47S-$jSX-C'2.B;|z7& t/aXʀO~P P%J9! B3:Ո=[vIMMΙ'd:TUeb8A Diϓz.=C%WsqM KUoҧALbx饗0,eWS›kqpMN)y٥7zYV: <%jL [8 dY&apoL@.\t.h KKvkL2 c<(R?ED,. M_v 1GkG <&St~)0۟k tEʢ@NuN2 V5`)1 uaR&M.5ex 2.ղSx3e 1E#KHomkjuIBE6f )/3|)&+j.E@HC o{6,eO w4P. |ҢM5F,xt˞6!Ц7]tWl!ŅˌInnOж-/.+K1' ]Ν` $D"xhs޶ £ZOdl(R|C߄B:p-K76.\K1 p`“'$ *İpw_EGōV㤬` ԙLp[<|T<+ Kĉ6,eg>bX&م,tVm9bU/y x0 O(fO~٥6]BI33= Ckvu1$Y(|I`yK0R]!qrBXF"qK_0 xoAX B!T쒮ݶE%VV'0J 3 ^,FGē5*9ko!ک0:: [zoo]maeee)Rz=bxJ-GYMWrK.3hb ZF&WׯRV. lΒJTAs|))h.F1a*29FͭN{1K裏=B诤-\ .\ >u%Zss \YO F bu^Ύ(ē n (Zșz,zҹ9Kb?1K}q*jvAFťLb %<%v55A]\/e,~)SpPH8L<,-a4 년H&K4gNut֢b׾6q`)>C<`TAgWˑ04Jbmܯg={SBg \Zh;,ee3iԹs3 T+4$rL0j sSk7:#_b(R? EP=אDEa1lW-VEIN.ηApZʤIم[R6Aq $EoZk̔T*剉`)81<ٿ6LPwʸ 3쒥bB!t} vy'jN xuv\ Bģ*m70l@ TӹA.,'Z\tİq`)7槼ZME Q9!4WݶXxt]l!&f2N huFlN6mKlr`2AF+3!M{b{[\Mb-OKV0u >o&BTWKɁt ECL-TV2>A؜9e86A b2B}LpX:9!rg7\o(RPFHlϫDNЈ.eK1qoovpQR& n+P0HP;ը 2.!T-n=DAXAŨ b {pI,wCK0tDr"iv_BTD Ju0n6-3E^G+KA%bXaX$YrX躆.YE<]@Q$x.%CvefR>_!7nxhXʀ{wҚ^XvA1GKR,CLz\ξKUUqP+ks 1*r 6!L>|ooy[_xl~C(R e&$bApy*ņ qBL yQ l:M $E@ "8(\Z?=4 LSe?bRerBZe_IsQ ?}jY>>ur! =R٥6d]ʜH $w^^ =)MbhĆ`GL=f L-y2=0}{G2&}TJwEk$dqAf ٥2K1)r+ D+3%D N&xVq b'W7R(` Eirj0 (-eAE9 cAXFj>9a(}͉b=7,eC=D  >^>2.Ȍ3."$w\90 Y|xc)#7 >!@/L È=N%^_ES=ynXʀ~|M35I I;?r 1S) Pח218<7dc~VH0n6H"9a[-_wŰÿznXʀw(Lj5!bSJv Zlv)J4|͓ wO"lqVouL &_Ig,z}0,s(R|'M}Zq=YQf 3Fx*fQ$Kk̩_vqV5KȒ5K`, DD,=$ANKGqA !M*₌gKmG"Ĕ1XTU,Ms\lNNji ngE~ 7-FNr7}ue\c=:,e.1 O:bxG=:,e3<ÿe*'䂩z`.-VCkabE!L(21^_HRfk\xYهg\ ]D na]u o7*Ýr^|EKO|fb1y]^VU\`vI@vi!H8!Jtjo zy &Z9#6!&ɑ Z-{[oG2K_1D %N&M.y-G*]21u)3ᩑ/lK~wyO ^?>Rm\<:|*& 6,ǵ{fugG2?)YPE=[YCˋ pd]{"bOSo)#7hV:|J0ȸވO O{,+p5MVr1 .E) vX,-L*_钍 hL=H^H\$=7㵶>'-OKpwj |ww)iB.8'$w]]I,4S;mB≼QB5B`)$835jL Da>F})db2l!a}̜pwǾ PL!΢}oެxz$\DeQ$'B@=v>#W`Xʀ|×P= 5mv'.Ő]Xi^n2bXʤFY8 DAhĂG}T#T/DžyΔLpJg#LLϱd"N<9S &];#a'"8 g!FS23X\kfOdDzKp=T1&{Kt6ؔ \Zhh5P0ǝ`;`[ wʩʸ]*Iw A(R|%CQFP/oTI"'> S٥!Z2'\[Y4Ά &d{s^(V0 ~`3rh4]v '\TU}W1,eeYlß 3 *{Ãjx]DlmoM 7V{Aˤc$`<%7j&V1rBZem6dbF7(Rlnna%?uYv{Z%bVf^W|3,e?qbh&-NTr!  Kp! 6et;tYE׳즒 C'wtM;۝'⢶+Tpke . &Y} U3ՈE.T'+a+ /| G2_"JfMV1<]# 1iģ*߃*T,!oLj=>e+eg3~OVbJeI"eo2nV{a K/q`)~ ǁH4r!e3fUQ䕖S ~ùM#RǧKZɛe fbbHd .Mra6{jwj̛+MD LY_dQ녜 #$.eEvQf N!՞Lx(E\v1+$ \9Y!' j!$LR٘*D@Qo7/{CvX\\8 7n w,_ww"~ 2{e\.!Ĥ(WeȄ -72|_i&xdIUp J0J:4ȅd? w--C=q`)>xU] j1X#4f 3{s:0'L`a3z4ef&ޫ[nj-o&W~J Lu\*x/E;.EpydK elhPf(&~XoP6">>w~j).&Fk0wG1><mdrtPf00?y] GU (˅ٙտKF߬` Z]!/0x$1:t!QP|;!׃ nOwTRECwiYK(1 aSc^ַ}No>a?s7_{M4&>PhvPtN["LB 5T:l2,;GVzKC ?b0U $8EiXyCya]ى b*^t٢(i3uwu[O 5rjo0$ G} |rer&68Lw$DtYm34]j;KL>c$YB߾[d~jc21̔J{!>MO\wLŦz:5){)hK.K$Y `B4H4D ʀ1b8>P}m,q@\Z]lZl }ֽ˟} SbE* e$\]~!P谭l3#6tt!{Ilr>8 JU.';w68 ʀg^n(%$q 7[w %&SV;{iq+Hx]\"Wz;7,Ua4Z)'BUow?-͒K| }cɠ92cȲu@,G;+hq ԧ?i ԡ~SUCw5Ekʧa<7ݚ[Qu5_ |wA U2gVZh'>[-y¹Չto'&0+B>ġ'N27GBCCmvD)hmdQ۱Dw f*10JQd }nWzW**B>M#!62mw؋RS:)1ZرɹwOwKuJOqf}0, a"d5LݛL.fͤ0CE#.B I.*}(+)@(0]bL&ͻ@k_hJL˵ɹ1].*K/|ݻLP]^b>zoK_2ٙ`(3]ʚɴ _Lqb~Ih-|v`K/wBQPXuiC-3:4t4l!KYb*vW11};wfi*as/}&9Q{pLߵU\|yʿ;TuѪ|X9 ̔RZF裏nq o|a1kl㝫#Y¢$K.JɺgdC#Udʲ4;𷿵*@UC#f?s> o3 n0LǍ)`R9>2l)fw{>68 ʀCg(HmLg¿!et4M*@ @ee2}5ؖm &AgZ%B^/}p3E (@(m+ԔKLUH%K(1׺d̵pf}&_s41 FBU2y$5\Izy0RL}QIja*s!l7P2{M[L pxm^GtuʢȻ,7 u}RFvdIÖ#r<5].;&i]looo 8ca_Ol[N~`X2X]BwFC|6Îpd`@$kW#oQzm>3q%J{ׯU%Xi+nPh0dR!Ch-I<4Ãp)48 ʀ+&ض)!u-@neQbChn}%+zha \Q(|?_N6C8Ed'vuU!Lpr>8KM0g᳟l 7I 97'ǻ#aHd K֍-~jWG{ }:gt"1Wʜu}|<\Kd}l(JL6 KuY$(@(kOkۏB>]֊(11֝GGUU!>,I4X=d{("9Ϭ7~ tʒMhiΝ[rc-K+…|,qp袉sA $(@(^y (4>uT?!$v*f''Zm0޽D,cwxC= N;Pʜ}}Ӆ*\>h6S$";Hŷ&4^< Ip)kMI}>_SM $׍7^>SUّXn1@E9bwNtDa=^p֙Wi(#[ǩ,~uLCdGSP\jE3[|+w!u0]2Yja b(JM52&u@f4(lKXb *x+֙ a+$ M B.8m['}CeNMfڻsYE3QlǦԂ7@/GSOo=p48 ʀ05tP[UC沑 v]XS_䱜f,÷R ;P梳rr~x=_L@baH-Plp!Fk!R˦d:I lq ?1$=؛f2izIR%#MzTad`ZeܓO8$°|C@%L=s"˯~3.}KMf';b/je3Q4j#D43?쿼~58 ʀXqwGN:ҖS,iIxy|'>(Z}87_t1T\󜶮=gU>#!4SDgyiEqI F LjgZ0I_ǫi쯀n ~,(!r5Z4>2c5wзc#K.!`x\Z0ESՉzN{2]hh0uy^5 AEֈm&CvI<<`jT5AD f{DWsxn&GB".C=m2~Hy]5: *Đh'DQ!@i">(D WXmo%B=yϛ5}6XԧeLj3Ȏe`)#L"ብ 9I~Q' DIy&GB`$iw Jcy AtYM_Ct ks];%l裱H~,zUxo{W,C*!E˴۝fڵ$ u6 +,|-5'C,Zttw׶E۽K92 åSmz,vy܌ 4Vtdt*%^Ͻ_RPW~o(@ '{]V7+@e]ʼ;qk3ڤLnmV ]a-.cI8tCMmdfM;2裏&Seӹpb- $N!K%&Aس̉w>Hz&!Xtr(+߳PCti{o"+3xTDm3;:NmbS :ŐzzMb8#ve_92td8DM}GV!K.t*%&q{;;bjz·0|G mB=ȕq?x j31$Z%l2<hbn8G#LֻL@ {vej>POD'o6_ݥ͈So9ĄZ.o7]'nc3OP*.#_ZmC'0 3Xbet1&^9˜ݹUzNI}1~M;2#mc(2q:~]JAQba_s9˵5Oģe=uplf?L yoeM z#v"]fr fh+׍7go02LpkE.B Ivf4Ϛve/~*bvk;E$ an..K%∈#"kH47F Y|c2JB=܇>Z#9>&bWǶV@43DvӇ0. VM^}fHBK [}͸uާ3VoxRmDs[܅C"W*c3? bV|#qy1UQa b l'f:r]H{J%B``p6*ƮL-eb 8ӈ; 3LAݥ|%:C߽+JW*Jم%qdž2?*B=w٩ȶLE&>HdWr9N)jh0Q̄\ƙ#[udb8|eW_M Vl.x58x5e: R%XJL.Qv|dX}aT(U_<3c}ĐedǙUO $ؐ`@t@iqvN& 8Fv;R T:VeW VM ,1|hq w}71d|ޚ, -@9\ ag*x\bi**qؖVl !D)sBkNzx)w>wlSg(H >` |rP0['R:o*.M8)1-ndQ3$Wʸ"]Ό-n!1enKձS",fښeib=Ib LMΑIwwp ڧh =W e>Y=_t,buO!\KpBVVsSFB݋A?1Ό-nbH+ ePkuׯG6igW6Qjt,4%7C;4ZJez%QPLMMy#JD/0i,]e* l g};wC!J"MZJĈaיŭ]K B7|GYqa:1 c\l3ǻE^u{W;X+^~dH%Z E*vn>!GBpaGIHR^bdඩ.ZvLd)W"qTMә7U2q9WW%L+%iLDd"!U4යXe|. \81V$u@/18p%QP|񋶊@,Z&bIzAl%Jv&mMEIUUV37kL_;nw`r$Ɯ;o8}!KjlW&6f ;`Ǩh0Me`p"J"gꑏnʤkiq O>$1f;Ⱦv@&j (of|.tZ2Tu{$q⣈l_u'$^Y(2rvgCHי%lO\_4^EqIe3Ӂxۮ5q.Hᩧjq ~cf( Jc@[c~'+Kat6-JL@n(;64(bJs1\|_"óK['.a՚b"+#Ġ@fT64XyeU&RPęg.U9 .-EĴ{qv }-s(&KKL.n?tbjBm.-JCo(.aK{=q$681Td: y'҈Ps]WSO_wdr0 eW\A K\-]C沸Ѣ{XOӥqYObz 䌲(G^sM*y;V)YK,յV#ud,8l6Ubaee̼7`J-ʀoAfa]XIB8 8|Lvkfa,GUb:lNG|LE e&w+ 1X8-[e†N ?|1ž7f*Sװ3Z*R:G^sd&`e #81:tS*hP a^L <.m(1ŢL̫eQy=8{º~P&de~N[׹B 16lKGUi3{(+Cq ^Cܭ;©Lأ) e3kZ)~ӟbJ-ʀ^z=HJҊ=ҩH"Fvͥ#Y ](1)jp]T|_i+m1}IN /7VUy%} t2gy)b&=X, }p#&s LUx%/TZ $S=<2 -KQtK$Iڻs9+}s<%A[, :1-ʀO<үL@K6\' %XAAے}"N /w!WhER@4_vu.Y }n{2jt5+e}&q+ZcQbػw/Sr ?H S>]#* e{${q}%$S{Q^L١M|Ty,lf&t`?' ±dP%Z E,vh#wݘOA˅2;2QEi|%XWsK{CAg~M#'V٢ʼmo#r.1\Oi4l1k"7M̧BT,_.z9Ql.*(9)됉RGOug2n8Dr["a"@%$z!SЊ 0xd8 HB%I]w-TZDAb8rnG?,ʧNefJ%b8 q~D-$oȆ Vܖ6xḘEQ^y C0>>N4Ib]졍%]QBqY*8c(=i3C.vT~"2P&{Ӑ.I*8uO8HvF8x)hP\p.E]l ' b]5r+ Vr$Y+9_QgQ/*"Vʨ*8(8ԌK.1ܞk` `jHaGCkq '`2-ʀ{8clp] n)©ΰ+.$e"T>*`۩P؞؇ xCC3_W_~eL Xel_#^MJ"j(~ɞJKNvƃWŔc_k{Ή0yq{(T!Ry wqqij_.]`` & sшuUWKd /G>4Cvt`O~*@Ӭ#*I]w:$x'PF$$2O=bD4MnVd#Î3]+`rp! FU7M? ??Jk!Y:Jt*mYCS<7Q|3qNA nPFvf(sӟ"WeiwPYRB^lPk ۓ1{ b!xm%I:3ʑ =\I8+-c;T&\yw ®tB%Z(Mlm_ Vї|N e~qU4h;B^T7n^)\`Q+MQxQGww(BP/] B!ƾ I™t$($q5  vs q㡌!3,;g-_s41|U4hkq񺌑<2G nmΐG1{+L 1<2K; 믈.Nvw}Au$)qǣ-7*'bЖ9(E#o4Q 0?k0&`h0-`'S$8LOO?2lU+ ?~dn4a߸l8&)vtjUxpZYrpkh01%hԈW\q / /RjF(֗ 7+K]jY;S1@6\S( kR mp*}qUU+6MIYM{{:[mKDFi|,q6UU/h O>d@)juJf3Ik$|+"_gg躾Pto؅T.nn&`!L&1U  =\HtYgkCd /Wfjj :!=l#!<6An1U_׿ q06eG~ܦU|x{?&hU Y 04uٔvdg>ӆ˅x='1 ?2<An:ݠp4$;S8ޤtM] 0v҃Š;G2EEC.8Rnw-~d X&`ttWo1CvYR%[fsdxÉC;xa煝 d`yS]?G?Qefi]9 4iC]j2<^8cZE0$hLLM^DXÈ&ମqtMU[,r&&LЪRkA??zj 7YySk[k]j^U{l]%HW4 $"~3~O~9){ﺉ2L|c ^e[ tE^Uv'&Yޮr1bO<I' 5)Oe˖Ma͔lmHh#Ԍ.iDYSIYU, .$lJS)~8.WJHhxB, 7ximT;&ipǽ}{'j2ԯ"1,J{n3#Ar1qB">>=Mt$$&DC2B@L1tAF3ݕ2X $xGjٿ;"AءnRDۘDz.Dz1 KQ%e'|F:_tML&w. @0P6/29vP58!1Y{SYa[n< t Skk(D"xX<`X6kc!z|KcO"T552a8]S&f>)e[:O#3X2Ӊ_Fw&'JYSk߷;<<|)+l޼Yu*pYU]jh- E&e8q]HxǮlMoD;:5e/^L" &`MF:ܠ &CXtRkjs 0exg:,UUI /ՖKRNB%=)kפEk{FUG{w!MW/5a! ;B `TÙ\.?'(įmY\YBvUJ41 x'> 6~M(ҪbQkʐB!ˤu5cp*,PϦ5IA`ZLI 1y饗ݻL׿):B]^S 15֔2K iqo犦8`Qtrᨕ+5LLb1?3!Th< LU.ag`f]̠W+ H$UUmzq 7xʕ+ؿ[QeX3M>٥5\.(H(0Dj_H5Jř2rO$z.Te&`EK9 &TsnŅi]!1Hǯj1 ;w&1 Sa4Td.PL VрXH(x3P@I`,-CaȡyNzj 0eطoߎ;JR|!d|V]ranzM0iM <,ZFn+$ktuM˔N4ez4elˊ9 =Ude; F9Uo>C&2 &h]94ap =X w޺uk$eG)39-vM!G`٬ B"'kU)^U*I=W_A8|W-E5&`\e.4B4$ZיLq9cZL^y͛7Abxn8yT4]dP |ݲ1Pm'IY==2eO4e{{z?L j&` XW]E8s!4Hed46x!1]gٴi$I$Eu8 8VA.ޥ@!+ <ĞTBG" %emgb '9]wԕJГKc];Dr $zA䭷Zk1)?|g 0ݞU\d\ϱ,{OG|6DWnH蟖)3>єHʔIt`_D(9/ F%:J0A#i[9CooW_]ka)mI²  40e2 CSӡkPf9DWw$bm,$k,JOo/_{6B0mbaL冘@cȲkMկ~w(3Y(.ͅ{: U!9hLhX69< :DS6֔B_H ~/Bm2޽{֭aNjLu:3.AK]S'H7prq El S)x7olYg^ל&H#.A $ 4A-\ Iyamwc4MDXY&bt˔ AMuLUY]HwX`㢭ŝeY=yϣ>ZgyfӦM,.`B{6yG]zr" lIsRX AS˝x>?KI4MUK&b(-A4eJG(P$Ũ7J0H0͕:Sz0L?qF㦸0xґ]Dz,xOGT|;1I^оp)f&I÷4- Xkx V nZ>3)kW^Y066_);7l0E[E,JBvNK=`X6kcD% οfٲ2):c|>oI_Q-ȴMn ȷI H0͞KXCbӛoES_n͐xLIf] 3גG&R]J*犦]BDPdw̓-_9^Mg,=r('>?+|H.·4B0q }qoWd@)K&ͳLJ8CR+k92eW]uҥKI<"-{gT($'Гg eI+27y`ooLh4bPGy:aCE^D pB &`e"t9| H0HcGc:::o߾`NM@}7900@qUiy}dYdRUq#C3H"f׿u嗽\!cgNх>,h՛I?Xțbv?B.GyC&YN4D!TzFί}ujS"z &` "wu+ۢ8!ӡ3]N,1'x?\4);7l0E3s.2B5TrL+: (2zAoԃY>a<:: -[. "eY:C2 ZN&`S }w@Ldan&:_~w]4)馛=X"pápu8e֞&..t5-sq=T0t}zgz?_)-BC-icb5KY=c\?;o|;ׁX,> >ļMx&Jw||nڔ\yjcHiAP'ԗP`BOҝv$B& GX%tp cIbOj<^S ^vхW}$%X2]j% w Z^{m  `_I<,,κ]XKXjRQ5FLҚ0G|W Un/kh 4!PЗ϶,!m`ZI)Gb])ػw?? Kr^F3Nk߻TO0T Қ:HKY<02! -[9CyP3)I\T^0W{Zc,Dz24h"Obd2۷oo%-l^/)j|U^jСlviQlj*ɂ;hi0uȃ^Qw`4+lρױ%PLƱ#r%LLKro8e˖a & _<묳 ÈOS]gkו[34\t_he[FCPUz;_<6q} {vXie-6V,4M;[5@2'|ӟ$ISdTe:3q[ph B7[zRvG&TR%K;t@q+k]&K#B$*U%,_җۄ5@2zhƍ'5w8ԓu&`e;J'TkӂtJ9C>ofLK/yf˲H<)E\dfQŬ, Ml)l/yC-uNVC1EEgNM53 J096\$;\MI ]qs5LOoڴIeCHMq`٥Rs413_=˶*JP\hj3@89)O%2 x3n3ш* UYUlSL sdJ!JmٲeϞ=5Lh֙H4tM(&bwRI:ٲHb4j2y淿 2 5UŠgkJ!`٥N[S öKb{wqy.i8>AJW"Y8vN ˖)R/iS5ۈUU%(_x A~}$eV7Aj1[eN2$(92WCK|<$:vbC1iLlGu<³clbZ0>cߴ-\|\^{WnXHLt;w$&~,(nYq~V,t 1"QDQOr3;mgZx~/4{a:999?m3~+MB#PّtDJmVJ5M P!Ur40Wji&KlZ+̢+'pOȣ0C7Qe[Fij>.';0A+Ι}&4RT O|~g<x&w~~VSI&.'p4WJ6-Հm {Ced=3}N(Ci78bAǥ]Fl< #!of rsi8@jyW80l3^`4<Ŵ=P@` M1b̡RZ= +j'Ft0AqPXtBEBHprrt4LJH*H*.aQFovJ] cJ~wJ0qB<<"8hbt1xM¤顊LJw#*L?P'P!t"L)A<غJDngPoGl`@+k5NZEdpV2;Dž}fqqf kC5YfwBCSLF/1UqSJM:RӬmx >rS[u0MD팈a̲ @! !e!Rb+*".Rn14юsIS 4jZ@0r;~|>/Y n )J Z*WjÁ5 LߞXh41Q0s4/! !Fm)Srwiz*l"S]1< EPuHrM_Ϭ"Œᨪ\|M!MR3;; &@O{j!&L٘%~-!P!|2Ix>ѢXz.M B_Yt/"nEQ@@\0kfe٬d $H}qgx)&|SJM(Rc;NsBFϤf4 '.3iqx:Gϸd@ XYY?k! !?~i|3͗f*@znr^uUv#jo%[D.$&b&.f * CSb8eS *X*55 ɹxtzQc^2t0&">kI(!e!p=niU"4\&%޻ߓs PS F 駟pp,Ϗ>` }BFp0^DP~<(5oA1{L^/%˛ !e!  O])MI]ucd"4<9U_]x !ֻ/H!eD䲖\Z}bRE7SjmVӝLR>QL5X|{B(B/b|K9xj$@)Gӻ49T11PMB_"Zv? cXӑ4}8hv)J <AfJQd3 *H|D;s48\oF'2ZNNNIN!VJ3#z닜{UG(2sw- hS rn;m_HJqXt'-.3& HSId>ܦY;yxhR3>>iJMz&>OGSu0#2Bd OuB(CaooW^x<;FRa`/pPXH5N=KDZ$Ux;m|_\N/HX@K>D|VWŭ8X2CS RlD"ASSjܹj*5z hbLi&TGGG}Be!d{{IM]!"*ͨ)i2a%4<dov/Erq 9~ bLo;/M1DDggfY-֔xL|0zxSWKi86i¢C\^B(B/@f>,#{&Qt>.޻4V.j>aDrƒZ:*N/XF~. ]G{`ppPW'>fy<44Ő(5O?p+5 q_ 奿'Wcl<"]Ƽ4܆U\.vQB>裹9ɔO`ޥjob$*1N/hxV֖NepWX\\4i4 @mq~hnz4橧RI-6^+Nk#+L)ȐNcb54tQB 1`ddJR>?]$Ŷ*N GEQLF^]0!uqtt;gyy9K@ DRpCћ !oq}m>`pb%eky0W@<-bݻcLcB[ }뭷^~奥rdf< ,I ~d:et0&">$HxWBe!?7bTb:zjpK tn:&12 B2xfKEqF!t^@@oڹ풯(P(l E To͉~I־b);L ޫ6km{K:h0$"B@"P @@@H :w{ۻ{}OsHjdDŽ1NF)c_ eU0|>_xt:l6_ Z\].fRSrcV=Myh[y\1L&nmێ=!y/˹\;!h@+P8 t($q˲jדEz}8VEavFBI^?Qb)&򻥘b8e"bI|BfNS*Lä1=B9O$VeJv'j^,z=J5%wɏ‘ȕbZmwDaYeޛjBXII[q =CyP !@D$1مZfwa?;_ǣ🡡}/ńb9bԬph4r\2diC뇗˥w |>0y:rzdn/jt:TJ%>Рg+S. PJ$$5PRi3q؏&v{: _  )h4T*4l6.*N;iVH$op#=Ռ%R ?z:CA+{w q}@8l.Mi]# ƕMG]ߑH=.m4MZ'{>|e%@27o8F_I3u?~IVi9=;tFQ[e|EQKwTV gO(F]*s Fq(?8QXVq B&*vq2|>sN3_U+Qҹ<{lQ`\N`nTNg2_I8⣘SF1Dl6n6 /ӅqjQ ׯ߿? ZVʙl6I8~8߽{98(G˗/a_R@~>sأz~@X,|j5ؿ0\G?eo(^̭[:NL7O3jGc2/^L&I(3\Cńaxҥ:Dh$gtTLzV<}ts< 'O(JGTJ^);_3@7n R)d2RK?w],Q8iyE^P(, ý?G1 jx8^p!꿿 <~W\fA"K17o|z2n\N`nPT>(@f}>s_y@8^w޵kd2yS 22 (( 22 ( 22 (( 22 ((vPC; 7ehFbZ0 `uix[=WJ9z9<}߭۶Ѳ,Ƙia`Ѩyh]W`<ދHZOkطJ( M@m` $x AU0Q(_Pj   C%=DE@ !g.v ý߽L>+&`VVV0-//G455e/n$'dzǎ|YǍdQV5;xN9˻ÌO~ξ 3ǟ̉D蓺IIm_UR{Ե$$3#z33}yC~o a< \%"h .)8)Gh'5558oI%Xvya ؎Z˲ajb ȁ!V\rcn=An~דm@7-,,@GQN8o@O> _|+p+Ʒ_ xa@z ?BB IdE Isp Jp Y(%zV m"`Yǎʎ`"7xe~VUg[ OWsFia ?㖖bw!aNn]GGLxqMÄlX]me^ @|.|<- 9 '2 @#CCBȔ)ȔDJdJ%ƔDJdJ)\ $jHIdPZQALI 6i-> C>^@ŧ.:xLX?VЦM> !ߋ ͪ|# |_2G.YO=}7)3  2 =ҫi HE $3  H@zhϞ=*! !a9 J:$ʔD%n('ߕtzyq>4S{cƅCAl,I]v} B  O5Uw_s[ f$-4İqb)qI $3 ᅖ a(CIIh'.9?sFk{= 6(P x ;KΜtWaUlm-[R aGݻUBrrkzoN{ko=_lGصTBz֭ÓOU%Pu0i,"," eI,Ƹ:,'AIj9wȠ$Kps5[.:Q..Ww<|’}kךR!a(&R wKzC74a(Cp߿'OF>q7 2O)}^PJm҈̙|ainj T&W^ٵk~7G2{JO=aD)=Pm{UPt'˖-| fPIJ)uһ=سT!mzdeeO?0'~ { `|o1B/57U-u-SJ)Ŕb\5+̌9r*a(C\ҁ^"4:YÔR%ޒ98NC #PV5B6l؀'p:%y9;ܳ IFRJ[K! eȞ={nFaD){F  ״BR(w2'#ٱc^x~_X 1wƒw΅?!#RJ)b0STTjAʐ-[z .+W/wpg(c tMXLX0}ߗb.6Q9QJNfEywN0!!59ܓK۵t)K?G$ K<Vк^p|?\\\d|\9tꦔR R ϟv" /0xvXJ)eҘ!8.4aI(B Yvޭ/2a„z'{̘v?/<ΒRʫ1T8 inn<~Wb,K2vNb!ǔc蔿|uCǔ=MNsNœߙʯVkښ{0ݝ}{B!n%EG_D",  3I9A/Df=>Oq8pF~6D ZQ$Id"-v^{|pСqss'~@&"\Z+:uauccccnPotG") =$-[F?Ґ(CX>}>_jJ)6DbHH^5eםт[~zUţ l,,*(2(DAL`@Sկ>z ?94l`D~3:RߌU L4 tv' I'QJ" G"@lDؑ@*b$ zzP.#/PHa) D"1/cnG~7T%Qp8|pTK6+H$bω#A0SWF# zqDyq20{s a@$p$;C[["_}|0f+_ʢEzzцjSgtrhd 1Jsk@(A9\*j0uaN)|JPS D"A? K ! 6mb1@ 5Dl80;G|8.%9rAU4z4VWN{u+?u~X ߶@ n$qN:[oiۯÆ YYYXe>G`vO= %+:vs`#ߴqAn+n$ܹsy晏>Ntܹ,(?y.;~3p}77yܬL2C!N1F׃$bA,vIBF~P =HE"iz~jĈDbq@J]eC_kKbwMHAX3bY,<ŲB 5x+h뢻ydARtJ* A$x$+ d1MCVLMҤ.PTS5s6:u0(xeYK$0BaҤSٳG8p ܜ3g# (m^y0N JEtU,n9ϥknqz}>Y '$A3v *[hP`A 9T914 7Q1LJI bCKߚ}p"(Co;GHaoB%KTBH$jov@H2WGNG_ tHѝ#Rw#GT[8qd]%^qho*/?wЮ,:q@}MvF:Xk\9C[ 8y5Oi r33X/ƍRW{#]4wD".YTKjq{6Ow450n}{yVH!<㊢VUH4_`G`4S1GxqT>|#\HTqsiIR`#yD*گ#6EGlG7c`S;i̘1O<;CGv+m7(.  s'6* (bAo6,!׃%々L d.W'N^^ʅ9`yduAנR_LF9`Λ|!0B}ѥb!8[M{{ D¡CX Aq[7EBzHNKfG@SHl2+@#^I\na5j1\~+HŜ8҆[s# RDay UA C c+񫾵*v@] C͊y3mcw\Mh](9s<1=OEz`<¯t}s҅ȒJ3Fs nⴛYHfN+e^CʅQP3+JX5A,) J/2>9RZzP׼yP5ź򃢑^n95A^T[jAR<?&Q&uF> ]Pj?IZ,`CƲ].'p摨8#4#8˪#`vքH1őXVRk#\mw8B$8~%- 5j|^,(hkJ} \M;֯hn$/?toDSLߋ Ki9pu Mnoj@Kw]94Ac{MO?`,y fPnF5;ZkHt%(_ gPO- u+ A" g3LZ42J9Q'qb.YvJPu=H?DTLMMMzmԧ4C ģFC> o ugjw{'}Խ,G_RK]ufz#tCstC \6mzQ]EY\~Q7^zv%%a[c v9j L(@+#DIz;ﻕqPEe t]  1eAh6N M AQ=$i 8S O?r82ÇG@[[93sD>W$9R`#|*Cai_jDyuK!r֛ CMQ$9a;DO>| +2 ݸgϞM7\BȈ<~ kf_i62M捗 々Lh; $I7IzN_U49Ę4{)fNP@g$iSL,F~%Q&~[>sp>C;3G qsXbd*#s#XcPH&pޯ@f΂V,qA@( -+2 5VerD%6ĩ(_#o {W5a 3DQx— kͅl984?Q!DJn)5ωA`,F 1($佮<:=D/KڕI$D\Gڊ]ƱN@Y0ub{S}^V&NQ`+MC&lD3>@|/RZ|^=T>NXǿrlY,Oe}5Fl_' A>)ӞF${)$N>8f#DbQȎC!p=lX`{ J Fv];*HBj>{ "Tʋ  X_W3~?  ފkAOSMUfr%!BA-cOCH$Z8<T(C}4\"H$f$6TzW axꪡȒd&=-m@mc>P׀P j_{1;'''G|/?-0φ%r (ݠAkq'`.>qDӈD"ZvO[~=)$fΜɧe"HbnYuCA0`eP3jq$b]bV\'$!-wxWVV% <} UQAY[8mt=v_}u]8E"H$ʜvذaT(C(//gQ%E$D3nۂJx0)Ym5(~d `29!#a.qR^{ C1;nYatf0Y>J2cB(hC%bPZKmqg9cw0hO#D˙۽-U)!Sv]I$FQHT&:q`l&WriQTTgs_@n|BSm'%cϊʉ H$˼23P iơ<&D"8BU 5}6Ĭ(M? /gϞM}bɇ(l)+6f$.'4qLH$Z͆8۷e6l`ZC4zH$-f[XeBɛ b5eFF7ԩSoV/+cǎb4@lk_4}ě 6KD"HkݨZO Q0|p;.H$Tơ(K%?zbV=͙4),̘1;BٻwoUUA U[^ nz]/ K Ơ H$]`T(CB,xLD"ʯ0-u5T^ ᓻaѐP , Au",s-=LAC2D"z"oJ}`w(C8$H$ =0Wɻݳ}Ƶ_0uC4bvq~&m|^dHȒooNE=.:2ˋ k0'ƙe3x7o|JI8仭2D"#3/e=〼kH$4q*q¯&zgΜr15>p|FHǃC?e"΍kٽN-\H Hf7H$tB.͛8t_WD"HH{W{ĮmXvyǏ6EyRJ_ױ'/~R8F~730R䭌:hı޾nűnܱC)s7Ӈ0L^A$D4rH.jjj} D" ó%: ;[m,_1dPsc]EYqOi--v` L3HfB.K,yw?}w.\.>(?'#(VJ~ *xxUŭz ո̸e5wmSwݎaf:Ark"D_Ypjv'2?PeEXMH$&#Ie,,}gB5WΛ5k˜C:5R]V DSҶ黑%Kà -n6e_5Cݰwg<~h&ӤEv?}9mx 碤H`F^cJ>SwT-vΌG‡Qz<]%c1.l:meݸ|7={Id,{H$1|qHcE‹/8H$Ԝ,(Sۻcu.@JÆ @JKEI~)-6iI0܊ /O?Swݎ1MՕ,iYz~+!bMY)<&ޞ0/Cv~u_xUS1m@SMUYQAnV&D|-!`'GQ` Rڮ-zM+8~qH^A$R8|+_ݱ cK$s`H;G%[ykf A'KDd U.iiH 薖22&mX`h 媧8uFeրkYJo uh3;$x"x.x:xFxRx^xjxv0;^ l*8X>XDXJXP'EkmV;ИPH$ZN a8@cE78}\'D"HlTkJO)HpۢQ)^YpAXE+~߱n%Ξ8٨mfPgȞ##­c,e/ T:vXV8ڊ2e.q_D=yz͜6}̕V#'3˃Ynv?)eiH"<[ {uL?ڽn($&;LƍKu-4tUqARɀC$S.UWWGcEBVV'gH$ HmNSQTB2,-Ic A}3Q imF SP!ڨwZl1m[苶dW/(%_2͟??O n4پP"4wqΛ$9G5 8~Yb&ERW2h!İ*(dYxw(CxWlR~D{H.tzzT1s}uw`,L18u.Rƈ%˕hMMYɀUQٲ@+۝Ge3(Q 0Z}ɻe0=7_gM6f$6al7E3qSW vKtvD[0-10R@Qo~qOV H:Cg)2--TC^O>x(<0v๓{g|ޘ7c$*I w^!Y"O~G?QBLxW 8k+}' В!V nf*!P~{wㆍW/Z4}aB4MM(Gpܒ 6S\JZ?E$2N݁ ;dD$ߛCMwKZgJWOi""I.U\Ah(An8TYɭ+o*-j(.o*e$IyY/cw|{K;4UeV[Cc/?}aJe̙oFzk޼y,Ȳ ?=hiQd%!/>>v"V">$FcmbzՊd%9*zh];<>e$[6l E`PEю^bcf- Z!DD8`@ e8vDB&%.奎Zt')Gh PP0?3,7YW #;jsmPS `#t 2Bi_`钷t"ȲDi޲t ⏲D;&.uSAA;y|q~^f"LHēHaګ˰"F`V 6lA؈ٷmP1WH>>"D~˜1c~w(ChjjbQhbg/H$e@g k4.әnn۶{Z.- ٙU]:P^gk+$\Xp+ƀpMU Lq) g:uY,A9sҟhe*++c >|W SRJK`AO 1j*,.HD 8MA% 8D"ч="Lߝ&ȅTH$&csWu\UsƳݤSgQe٭{YZ #]R JΡuCG]G^hd/w0w G !u%*NR[ΝbI!ڊD{g`/B-\map NCI!DgH!CC>RM%-T*)8;D"9pY%^~e=*H:Kus ?[ʄ%SwsOA a4IIFl#uWІ`GQ㓅㒨|aHIJeS(Q|q_DZ*!9OHࡨEyILx&9l-&xR$jk4̅x.|`FTqq'-?.ꜾHe4=YLKL{WcʧuyDP]} NKy1mñcWpb-TD"h*Q a˖-$]"wZlii T?Z`iI F2B%95YT ƺ0r8@hh9~tY.iuLiӦF+Ӝ9sz m2]:3*_=sZyqaB]-x#xd)g8W) IFg-!:G\p)TD;*Q aĉ\$|"5J K /x]T5W]p.=.U+dNܼi[A^IhH3HhZ:c*J:xa訊me] oke"?'^}$a5BAmnw{S˿tQ )8mUe8epG`r )8D"HUDB$aQxeD$YrI4MFL]+ d_hд1 OW eN߫8sB!ā؛c 8Wp =.9uAq qx饗w(C2َKH*R#n)KCYxKYZܰd"lSgRW[22(a,D$B:2yPOEߗDݛزk׮qK2!v7N|1FOkEK#vor}F4QZrKrp6Z\ `+lbj* UE "/OT;D!V8hXZj7>\֡2JMJveٴnPs#bqZpL5/\\ @=AH![> X|bⅹe0'@y[Q\`ّĊ2DRpbT"F41bP˧-i*yf"/_8h1: nUyﱽuȗ֡ ssKEq{e$IW {gOP aV@AC#{LZd߈}#  #Qp9-!H|0~x"Xx!)DYpRs41]h R{ZZ+JV\Hśϵ)JMU\;kg$?|`7Õۻ^KhYʤ(Jl+SԂ-x&־wY/sÒ5e cGL#e~9(CL4ั˝8  C~cĽr__"0\'$p%rUR",X|f RGSVoR5V2Dopi()-ΌHTCPoe?<7^rtDKΒ-bNw߁-vYp ]7KK B$*K⽇/__Y*J E+DL:!v=pѶ]0YU>5/S&Mqx׬%|Dq3CSKD ^:%$ee7?%I؂d)fdt/q{4MvP1;^ ▋nׁ qmÛD“O>8h--RծpK]7ʌCMEZ(hkBIW C\9 C衃Nܚ:lÕ{$8 XwHeɹLtU]Q_35 p]e(][V_k./y8QHܻRA*8Ⱥa%,03;fxw?̦$vcNBe~,GYfLwf"Lw], UzqF13}&Qp72~J#!MÕit;J-Mi8{@s4ґ5pi?{|"3cu|;etf>6cgqkEp\/e>زO}j/| d2)5"~qw\ AЉLEAIDC4VDqMoHƱ;=<\1ld؄{|v3R?5DºuHg) tC݈f.ɡNBnÅ88T2D"7 clOPpX|#O?m M gzuoRYҍa Cj!'W~{`46d՚S_#{-њa+sȲTIe[8LMeqFl?mr3]ɃN~#SSOַH#ojAXnۑA% ZQĬŠj_sNge7of&պY^G,--ǡ{H$ƁkU0#7X:wfC((R!B!TW4Ԍ,ߌ(RZJ(z?Ѫ|)V&S-7wm<55M :&=<f\$yR( #ŰUVٸ0(C={6,0kta,:%^gqK ްxs0 Lj;a3H$zecCw]tکmM)S'O1݉'wKp!Ȓd<\ e: Ly7Ve˖10\P g܉޽{'hL< &n虻d\'p|CDpxSk% NSq'QƝ>קpu:+pJ-CNG j{H$'4 ks QD e>t莺8*sm<}F )hYںug*%nۇ\Xdv<NrGa}P$fj6tsׯyƟng߈D 8M|2c$1eZ2~QBSNB%uGT$mhOĺ2-Pgv۟H$Z@8`hs\Lznp|z+ 8?hlYD"GA>x<^dv<$3>~{Q6%bӇnzk |;2D"8D$R"'udpr9MͩάjӈPm>67q9זD+NvH 1:R[)DQB6X(`h1L,1,fY%Yde M$-8~7~K\do];a~oY: aڴic^z뭥K2:L35\O#=۶@jijaөKs8xIxZwؐm%d&K) K d2*qw{w3H'F ۚdT ]b)?mYI!y晬@ +0Kt%v0fmY"Y6qK}7{3,;ui#ߗӡ!Jj'9Ֆ%2yorxޞoOAl͆4I⁇zwnO&As4Ҟ֒OꟊAbgåi%&MQJ.|"#'Oe &Q00ZJoG|]w(l#x"86yGxjDg?ѬL}k_2?I5#}gOB\F -/2իW3@ % co]l-gՁ52\[u1:00u~U/de:SHYLg8^J"=|ֵ77qb@ s-K/2M6z@ 0Ta |{qP wod6_[|M;01bYӟDExwޭVVߑL@`Ǧ \Coj OO @ ȢhLMݚuSq ! ngj~x8\q$v8ۺ精%Ee~gsQnOj+iԖJѢM4ŽP)l 9׿5 G)J:~'kΥ*t ޞ$%c,AL :}rD@h/޻gׄv6:0dڲy*ߵg>X,0Ob.{<2v3:( TM*Kd )ػ36`౜c*3ޟg"x@ȹcRS2-,z*zZғ1>-k9΀: uٝ'3{e%QDҗzuoZ&i ikV3 ,y*JiO*JA-??ڈ͜f JRsLd:L2;nkK,)hezzx?*{<̉lGul!w Td $2t9VԸ¶5tW,l\ EwS@fw2s:wD ZnnvtJ$.0ö,$z!/oDfqyHMjZo:Ad nA6\`)r^XB}r +V읱aÆ dY+-~ ge`<>% ]//^QA_qk/D6FX>wK$LjhZ1|U͉ͮD"q^g3Oز֒(Cxg tMDNh7`pL\Nխ*c>E` g$5MH6,=!R8Noe q%{=n6 ÕN|p%]{d>OGQsV&L~% oЀ03nUIp9BA/ Aʁ^E-PƩ|D;rfkqxWlYeI!|`rM$I|$B<=gEǧ/kQ6:?? ϝ:0iҤoT?я̙t@ 5oep<jA] ;h3~qᭅWVkCPw$k&]UߑD"898|e%QόĦX,3"H ³R錆(~ NԦ_-tº y.ńI}=lteLŸ"8>ec=K_ѬLw\mV8)JmMPdd_#{-f @wZ'5D"/~527 aB$k:wgfpCۑ_B9=DAB!FTC@κ[TT|jx߲ ٯ#z˒}gL>OLPjN2L}F&My[GFSdAP}F 6`p81;@o  BwD"TI%z-kG":_ ;m#0[A }I5evԇ鵇}Q׋D>jbt,KMf:L8[躵2͞=0.6_kt9ycH$}n6L&CED؞S&+q{T| =G8xe 8tė!mV?qlH;Q—d 2V>! |J,K'-K"LVH(?$U4u`n`!ݶEȸ5eTok\N1Gc+bV=8 b (hJ /֠lρPD"1vitTI1;.S H$e/Xu$Q+" 63qhy:$2yދO[o +exI҉ߑЇ>DxV&I2ee21 Њs^p + )2)2Hrq8di@#)ts|Xz:& GݎrkPP{Dql-ZLj*68{iyd$wt`pi!_f!892wF-Z5|IئCo̮-" B=Jf-K gLe:|+_B|Xf͚U[8 "ג[SW[T {*SDjE0?M$5.pTI1>l.QM/I|T 4z(Vk{ޝk/%C;xK%UNt1 ߸90Ȳ(TO2+A[_'τNlAO"Ѕ&iNGD.'s96mFiq˂bANx\M!RD"` (cc\ve?$#/vU񅟫WEbS>ԋ$:B/溌.sE?32Y(2OH%S=Yꫯb )+S㙒F8*_7mTX1[A.'coѬDvk\z!H\38\uUTI1nơD"/ɠΙX|qIB7] REۉ$ 8tM7pߡՋr2:~7jB95q~zwH\ܝa9BDGapΡE^ I|fްCql95ɑt/j+ ^ÕxbhNTa JV3|Rc5)2s9I:SW,mS$ ūnS#b 틈p^GOR%Qx ZH [`u3He?Y *Fvs#+}p'v/6g$$S-}vh.l<*?Ȧ/}KTpǭiƌLCLh^ O-45s9ᝌ7s\N0wB(XWEѩ*BBXCh3[S̨(cc|ӟf(D_Wk$YBE`(xdQ=G]Hbd(P/ 虇UYtG1}FM ÕL=t|tL*t8묳~PuVL|:o3{vU)#r9lǹJ\N #/pBj`Y(k1DiIخ%ψH$NiN0o&U[elc1Zģ$eFk {#̄FVmΘ#9qv}BQ _\;4.v?xU> 8dY2_.heBoex?ue (2nRdw9^ 5%s9Yibwpߣ*p[n\yoy)_ |ߟ>}:aā;=W5$ ¢Y_~``Nh%S3)nYPD2F5.BX3~ DboqU,26~3AOJSY2e$SN5Y?Q^-IM.Ó.%R+;9KZBeW@AMo{=ltfJ[Ié_*l +Sc84|uUL,f%~&C"SYNpv"+i5Eg D>tgxrDuBY(xX , d$cï~+$o6/Dȉ&/w4cv^Gtd)\d .܃e"j{J)*%a7~^Z_ugc$3'{!pVtiZQK/YLP9ǍG.'Hh*X,'Y`AUl°m2V 'T&Oݲ`U&$5%f #hv{z9=Ds͸!JP)imA옱)d_Œ12Gړ GNyI늅gVэ\Uёa:.?{^OHeÕxL"LZ 7'Oc+ϴs+ q|EՄd#| }4rҒhm"-ED.fjL* % f@ۙ x2?5\V.l6P(DUDۣDĥ}mHۅq45kQ9s[Se饇@qFd{*"֯UI@ N@P@W_.'F4+h7}5 η}V-FI_D +uMezeI#9FS(̮#Hk `U Ɓ)DcN(pPN(Afe~]Ê.t%&~qꪫz(c{޽qX>gC7l_T, |H (nʥNŅd% phHw]HBM's!qt4Yh$|5[G-tXf~V%Meںu+!m]W1X\U\S9k\Tw.'F.5`UWNnYPsBeMأb6 \'鰌޽{t(c{u] ?-5;"Bjp ϹjŖ;Jǣ{r̲m[ ;&12A-cl.Y7`Ec]cuc7WV)r"N iZX9 X`ut"檓T$.0GI3:im/t4ū(W/9""#jQ(_x%pm΅ Ru!IƊ!0%4~ₖ6_ gLݽuSkSX;†tt,Hjl}OrBub4\N&w8j.nl`L(ADZ`7FlMp'|*&2/8lKR d3ZS=6d(9(<(2_ܲRKGmG9r=2j%Prԅ(hu! ^Z,75LZ?)KA~w33PeilO 2|OTc!S=pRP-d1+ݎd]4v X)Dd 12}sÔDݮ@;5@H/pnBTK 8l4%r5*5n \ L+]a"18֕e@-.刅F0{$ɭ)$ {:)\NpgӭU 唰.!Y[v2Z/D$`2o2/~J5-fh(xyϑuOaƖקTq4ub*kR}I4w0rT|Ui{>lÝ偸k ? !GĐ@@"D"B$R ɂ$l3c[oUpTutWskkkmDssр jE;q^s=`kUNl3<ضU9;Y\LJ3`\}h1!cQWO\GǽZ%C%dU~/;(f6tI;^K+ .)&1|ߌL`aR^MEt;yInt;7>,Ĩ`ccF3~z!qF1G[hP 5n6 5=7TN TN vA k}ۚ|Lf7Cexs3UG,HU#)Cz=d'9 _|׎pĴ?=e*ɗ8Ti$4mkW!LBg nA-ѱ%ԩGYU O>?SVJhPz-?\{B_DiTN-G֡ aQ~5@F>۩>ojl31>0!VWWE ..5{5<"ňv6=|ҋ64hl)KKB98THZIϱ0NYH00iY)%˃Ml˗/#;lR& GSf"J՝]5~'r҆i81q!רNÀVm^'rM31>0n!9"W]AD9GZ9F/C[T\ݞك%!fh⨡BZeUHɳF-&)% ܸqe`шۂ \#jd'FADf1m';}O%j%/(T p[BERx'^k]*oif[ɊBK#;L|ꥊ ,d m4X.Q׼RT! 􌘤жm̙61sBquֵۀ [ ⃡T|1@]G.LDY2DCe{2UN*#k©`U[95V V*о\k[,j!egyl2 1%b꒰eB/X:R/ ic@B)*&W_ ￳re  ,}O$]H8LS`NTN B+7E"a[Jʳ-\hXN<ɲURx嗅 i%t39QR8JQl|o y򏣎UHʆ댷 yz#^UO,U 4=Ki g{*brT9ܞUMGVd `)U\,Vc'{ /KUAH7,vn[#JUSQGmn//.ZgGWzUH72mie19Xbذar4 ΁TÏ tOT9!Y=ȶ& I1 6ya7`m*)C;/I9BL!V.YZɗq!_K[PMDgvFw>NBbXuVݰj cĉyRJ )蝙HX+Ѭ*6'S儡s*'i /A֠s?wLeF$/ {,F!e?Px׽؟#ǖ~Z u4ƒabxa!cK2UHhIV}M€ ~W3x[J OF'0)vf'VӾT9p5 BAzV%0P/m2uէ BXk/=1 /Rֲ/J_"QuIq˃Oȩi>L@eB/!؇ΔӄU:u ,̍7Xqq%9 3=GU!S夨rYӉlk<;Y#%Va)lq5۪ 1MAH>e<˟|?`Y|5kPP$SN8jdT5rQ! gXeYfXkXb ~ PutzۋʦYU6;w#]3ZŵU;Wlbykb5%]93gX_ B|M>9ӎt/ɗՆP+. x{SZ;>jG?g!Ɜ3#8@> RpmdPy5cYAC beVՔK>L;PA7|VUy>% <A7BKY*/UH0?#KF r4n].Sr4`"HHHXv.xWLӰnTJrR崝UxV,˃ ,yG֋B2_@ K)$_]poM욀_;K6ͣÞkBbX55 Fs4RI$-wHص23ݓ0\J"Vg;s*',@֢ͩئ_=ߚtUqzw⋬"e/²,aM _ktb]a%Ň#aOIhp3R!Z*$6Y0JaA ?MqDU-KuOSiTNKөTNNr6ġ xL@р:BHg&]$")Ck@`ɢK]B3UK:Z̮FAp iêdX5ADco{tWF69 QɢRJ3G땩9D=l+ʩfST[B]{=^cAW^vĜݞF)tI3Q^K3֏},$fB ;xJǮS0c`X5AĐ`cch\MM&;#aKlt# :x;_,'T,'C 92ĩS.Ȳ uI"E^PQ%,P?5u*8 zX fD&aբaAzќ8qR*=GC] k;۬etrJh/bx`4hl+) |GNj‚0KBl֜oe交vP^"U!kiw'uU׼^{<$'I n[-2@AA&IX&@ * Z[M#-rEU= ~,@@@c#ihY] &l!Ǧ0._]m)XocIw0b_=W{괜涍nž/GG=>38qvAG eٳFjaVg%~%]Hvȿgъ_ J+م(m!Rt.~͛73anFC,d4=HـX&-xxS#n_ZNцbSM$ӟN Z(wLE? ,䬣IkӂWQoVǞjM 0w UKKK6}"hߔAœU{T^)$;FI$؄FvTp\9q8q"ׅۚ(d|M:$fo .;N^#OWN W`*\Oa._yl6/Ƌ 6J ƪx jŁR{_s{rŃ;'i)b `r83>lB(&e0&6-DzI:N )ЊRZ.`5B²,/@FSב tV4?Բ4t^$5ՃQ8'x ʀqJ{I˜^DS䴐vwQWBB& C7)w+! 9w\g1`FN©MA±tUYmi6D t(aTpPMI)lC(Ǐ'm>ǏEczgӈb_/G/~L趐+K݌DYV-IxjV= xxGrqoo \ 6AŒ@(s'-' 39q< O>1NZ@<\R/i&-0nzIN XR:jJ(,&9!eտkk+++nFwhz~A‘*zO2ZNJAs Uy~mfB8uq{9/ˠ4^fUt`S32/Z~NBz&)5&'Of Uܸq]˝Ğm\q>&U{R=uZNx;r2\4#3gdP: !ssp×eK>O$cz-9fHi!aYO? k׮ g4$\@W{2J6#3ݪ-' .~EQ]r%2piո,`C Ղ^]hs|ZkmHPZHoNzu&NaxvnFk.b GFA¢AF?=-rrFD I%#~:sB|wVJNl i6h}$܌dc[HX$IwMҥK ]=::MWxO^0H85W 5P4Gޓ2j_ Qs_왍%q,//gBxljӻ7>D^]KrQmkM6Һl1_t} 20Qղjd4FNF/Q$P+9FB4`  F/b ,..qL6Rczjw P/Ip֌LdGڟro*[HJ6, 0ߖUkIe=P'2=z4 &GfSx{OG-'ߌ cȨǧ-4*ئQnoݺWPz)H%1iDM1AI^ҾvFG (Iix;-fdI'npvjX]]uow9HΜ}2AžQU j'hW>[N5v1 @PxW?e뺸b9|H{<@za;jƫHnA0Y5Ma6ܹshֹS A#R3J=6JU1Jbm9 %=$iG/ P^x^?9iD]1YRbN))8B ˪-aM?XYYd4;l0Jhq|G>qBmF{,P|Lo8} c32ݟde/26m Kz%}EPRSl@v81' l+BӁ30UӡhyޣIy?iYtܹ>g q!Ğ)&;*` Srq Fؚ q*`9MT dk-@F൓-$,>vohg׈;HXi(GOA92qoqx7cz)ՂZH6")СC˪xf4fmlA¡:{Q 6H.UϷܮy… ٯ'݁t]/i!ZH[)U߾};@FSבlA͵ޓ'gwb/ZN}NǏ ܭ~{ոMϵ^N#j dcp.8)LY˪ xS$i0 \'d6(oFi܏> 2Çc҈J{I3x(hOv!Ş ˪mEƍ@F}f4-wWL6ԛ{rGUrq8yde {G`2{e5KҌn^ i, 0Sszqq1\+++nF"wh5S})ctGWҷvF> v1󂽤}D2F7j9$:.>&ZrYѣG;)˗3~b꺋c3SCxk'9tFO|o$;*qx" |GRp:czG59݄Rk0nrbYu `p\vh(EoWFA~5NGZYAPl 8=0;H㴪䝉B4)„ahS[V pnF355E\h0H8_JTSKÈ$5*xD3ⰯYqP>s!{͝խ E]1h-$-:E8e$)ewMŋWWW-]=::5Wy#BT%dۍKdZPN>MϧjYKa8uv! { ˪ UҥKo3g91E0hv2A{2W~I2 nZO<B(7|hoe=)w)[q7Ufl޽6y饗{[nel^u}Q6Ɛ\$gVo]uR}OZmii)2P{%xefdpfXε_&$mMԩS? 3Ν;~k#,,,(H.ʙ'7a=fpȤ6JkO} @\9pC;ڍ !Ǔf0eB6Χ02֭[Ōfڵ,mǽH6$v6% ҾLd4uh` ٳGH)Y&iÆ r- rM6+;eV秦4efggeD:6vc˒"%y\-Na>}En dgx)3LHX~U&3]m@(C4եL$eֵeX(+=qTY28ywi K\i~h|We'{kKpɿm6[@f A 3Fuu ޽֦Ld-0RV]La %+,LiFyhx:%{]P۶< ePdkPFYh҅/NHaeAPV m۶hҾGvWsc:Eɮ]4ePC^zu!eu͋_(Ia*]VO TҌƲ0d4|weg177A(Z$a7olLeܲAP"jy)vGn`U}u>ٲed466$ڃ/o߾ A/rXTb4uYKWʪm5Iccc;۹\&o;)m۶iʠn5?2|>/_D.-.?*ϟkʠIe"qņLytj 2x;Dx;S) B *q=DL$=8\*B0Sʪ -hh%M-=״g߶o߾i`U!C'o2WceՊj F$-o۶2"d݃2X>L!Um/+[V-`MLLeYQ-/12ܜ 㸚2ʪ{zzdU )hfcCr 'NპB@ҊByq,d5ZV=22Ԭ/_׭[2YN, MSLo~ ɓ'W&$5%bql6YLaP,.f4k֬ _]ٓ>vY @(HsRJeQO f+Ȱa?(xB/eQstructlog-25.5.0/docs/_static/structlog_logo_horizontal.afdesign0000644000000000000000000016147715077624341022207 0ustar00KA nsrP#InfT}`l|9He (Prot:#Fil(/Ht-:DGKxa۷z(2G%CкdAlM0\q-zOJII2Ȓ Hf}Y;Z ]HB%ubX"0m$a@/ U˱,=Js}`zU,lsgRr0پ\1i -BS?7'J)Ha@yrds U[DnPES(ѓ7&$:-R6CeUE(`(OHp.8r%\wD;U,A-lC S-F}5BپHuԷElhlhB Fil;Jѷ tHe; =hlg@&n}JHg,s03Ú F𕖄tnLҼڝ>$=5D'^j`ap AÀ?=l``@J96V `wȔ9 g}פ@emMa0i!tQ4:"HH?CU.2;\/ "dnG O#grBAӆ-TNZ \$Q`[][h5Z[]]\ZP"jQk$E3#@!!V//Bז- qS @~4-I=w$y XAC ,X< C/e/-0Dg1k?kחѯ.OBZH?zoQ/ zF*z=Á oJ*Ty!*&(`{>׵ +JcqOPoKyQt/7E4 KQ~uXuվt! *dKV5Z ƣ(iMߞJhZ?Rz* }w*㿕E:]^V|vYey}[|$"%ņ0'hLJ)So &qVäL!%,bwx2DDZ+lAjX0VZyeu&m?S[g9fίke&֪֗JKMK EeCESY؟&L;u+W_@40W&Y`H9VUUIV!$DRBR󚌙Y,lqդza隲?45jXV[`XB~QjZ!t򟫪t JȀ!Ⳓ+( DNt.N[`<&XYX`V."eK U_վ$rE#W5$PBBeX#4H _`x`@PdW Ŭռح*x;o(M/cezvѣYW4/E 7$Wi^/\ɰM} - *j=.H ʱ"M-16d}q mC ! [gKId6Ƌl$?78'$ -.khҡm|yYo3$9nK=y0/ ]1T͔da rP1[vyo/rL?U‚O%iH\yxuJ4d}ǠJ&(T ){ =9kHr@AjkIMmȉg<4B,/h~i灗$S-)^0ӷ[؀zN&(WپِѤ\P>e lcP,eꛍ)$zo1Lc ) 14L h~m[-Kx BS$6`cS+q36z*w)ڙzS$/Ut$Ǎ ?ŪT`?)VߥSS$ >X6A8f jbQH7FieGNj%)Pto7D.`PC0J 6ADۍRPD O'* a:DW&J RgvD,Kζ [FgXEپ݄1у'$Fhl 9|"mx Q qaN#~㊮x#ovQᗚX~gC$C ̀jQHپ$ОC m-Ɖ 3M CCa}Y ]IDCct}GyG=/*fʍlgn#  0gfcvSJX@$J'fMPg2hjo15֤0ILR€ oL7hԡѷʺ"Jg#L.oFJ$lmbppj?cGDR! 4`~ذ 3$b*)'LaS+e1r#魧W-~ ^/{~姊i}R՘ԥjKپHH=3Zl?7Y.%ն XQdo6"xQ ᧪Jw4%oXEMU&&|o6oXDD L. b lc@'zYCl@tF;F)?@0 T$=p ~]pvFE- Tt(8tDFȈM(*j&`PB J4 yyIog59d?7Y>Pˮ l_bi g?I ?w[ E~F1 qo3[f(`ぱ "|n0/9CB '8r&yZTSr& U<(hig6eM\WiCD8?w5Vd,hghX[=g%z}"l34alߵQ!xAp}gMA!, 8;h<` gؘ $}hb g#,l_@yl$'Ȩ J o9[ -$g Q < T$E1'٭+R~_r<ۥx?7,$T!Kq2O5geMe)>Nn\ w\8۷ ɋh>P@4g"Cα#de6:}1蠩m/Hj lqcR^EM1ѶAtPپXQG촞+7M9Cb(A}ˑ zjX{WK:Сm~rHH#پMX4 DF0?DCƄ݀rdM5  L: T:DE'0I0gF: i>qT4Dqo2!ɀ)_dd0ÏIR>IL+vR~!fw5ӑ)ww$gd훌D-Пvپ= &Y6=6. HsUsHm+D\3X"ͥ|r% +ȕXr w? [kz_z?wVe5f1(slߴ@AdLS$׏>G^%ۉwѱDSRپ2[v'I^S%ϔ۟a!I,0$ŏY=c9^֮D۳njpqgX☢ W뇂` AR˭"d6f玝:+Z  *^#kr$gA3G:IrvQ񼣗7EU㩮8p> 3CKSt:.יw\AvxRUP4G? 3]Σ]0)yv_ES$,S𦠗zqtyqZnb̦^j9-ohi=5- I5A #?} :][X%;r4fP|}񲜦HP`Dba,XZef %)27Y7GbXdwʅhO^:hu rfW2DIonzEX`پkhPCGPhp38p}!sh }d"}bT6FroZݔ٤@jV>#` /m&%VQDg.CGI}vD'j(leޘ(MgGl_A6}6Ef80Q@Q$8,GW^R4ų}+Io,Y^;}\yM ;97l٥I #! ɑLtUKLryAoG8QH;td.4'F]=fC ãhL#&=G%"F gv*գٛc0n:ٸA@7Gkgo8,'GuK#jq5Y|#(7!&X inٯ' *ڹ m(T.1!%^Է>3Mnpk)xu/,em %rA={9, jV3`mCtX H?V~D#T0JJ>l췂7(*kV)VTnk~z U:o*;i'jL4AHhM_޻]&/0q1[ \ A0 [dF)PNӇuDb4L*JjJ5PQ\'xn; Ē%_^iadQ B%9XsTͶ7UeZe$BIŪ7y.^C9j7& #" ݟ.ɚ iT`U:CWAl,p5^}V)(>1kXˮe[ic[(-] (x(6Q:ȎoX/qژfGy4F\ԟ\F N!'jNWH_(Ҹԃ0:kz09KOIr GY+ԊF Jk/j-m*LcD.k9%FnVΩ@s kV%i\VB.ha+a&9zH`ar.37wq9oT̿y>F尚LI}Ko/Ʒ|Jjϖ],Д'!6xn WiW 3 nJ 1bx"re1̥o][@aT̗]v/$欽/9mՊPtz@5>q99l.r6h\:22f^r}ϕC 8ʨ_]ÇMBBdj>"D\Qhuc';"^G?:Z~kA(&.ZTJ};B0ԉVb68T$ S;,!MlV0"1Mi !Z`|'˫3>+ "؁r`1L)5ܬ*ҍ!nP j #HV 271jg,?_Ơ ԍ4)9)ACc(#OcA}8H7x *օhiFYg[p̞˅bVqH(cm Ůtpdz([28J,T߀"ēHfP':  pGN(g:-5L b: l#:˧r{\/LI "@ iaB:RFĨX %o^YD0Bhb - Q||&+ R9bX VAg=Bp\W@c𧊾B> z@FClbt>tr<'j4)7eٹD(91oZ&C QjcP< h+w5MR - 8shJgJQ )c],%0 ;A@;M*>L3?Š5ӵ~6:(}pb0-~5}b^u! .ZhZ>%Pe(6ݟ(|n1 pLǹ%#bwVth"&E!NITT 𜒠wMGPj V6w%;VH-܀t5UlfͲ"UP%AxƤ Yrb&?@W#7]iwڧx  sz/h":8tχ<Jx*Sc㕲y#?cjQV2\hUCQ&,3:A:ъE= cF oannØ) -s3ŖP;lFla Rɤ1d& .-;Gڷb ~vkYSRӘD5&ri/;LvLc>h):8œJh!ڃ )QAUdD)e$\܄Zigia2,]nN pw1$ W! I \2*~^^(N/+BҺEʎ@7?A|CP."M㍘ߺ^ʐnV*]#Zi }| m\FVU lpouӫũ[*MtdA 9J"cz3ΗY[FJ2 N͢CjeHE:\%5f  `\οn Q3Yt?2+|9FSwcܿ@M ܋+=ko8&g@ƕ< Z.uN<dP'㒢i,j -1)DBCz,\[(6qk0oo.cRNS5jn/yʍH,ThM3UxO\gcPD$ w&4I,5݂U6rz,꭬W1 ˜Nwx j@et䩨zLcı 7cd] WrKĘ͂\"ٽd}.ViتbNVKVк"OqYUKySO*vztbU!Wݙ8|qC7X mEk0f*rqP\$+w2j J= 2dR~QOI`EuJd{ǩޡ17rQ ˂)5a4=9I Bj7zc8u YCφP> M-FtHh'کjZ1O,W1F#-.!^J~a''z3b)U&(ta9(V|+?Ҷ`rE&oI_Bl["S-$>Aq3)@``bJ )*6}1FWC6[š~h~#ٍi^U!U4j}3ol%ʡzB_vCg2;Wh(]lLʜ)V/"%F-4PyC<'8rjH ^Jy̦Y=0SzKD!R_zAuXBZ!FB_z@4!9z젡b$ao XBIe$0?K/$  />AD!,qQ,>Nx2l񆘂)`שc=;3 ~`Fgv*h edB/f1F6(`xa@SMJ^ I`0 J H9. %3ǬN0!Jfp_5a lNa5IA`ʜ ".h` A"KhD.@Ίʜ|A etQ+'7~Zˡ LJr]PB#PC'+=B# b<鸦虢ׁLG2 3=,*U)^Zp!d:B_z #Pe< elCVU)CUeI2Lo2 d%P 8(0PuUO3f<⸎e `&G'#5%g*#C3f3STCq_҄F9(!BcTȗp}RT!H$Ԭh4y%zaWzYJ& $+Rs?&O;X...^(v;Y0"0O?b{j}4Nx8=t<,ijЫPۭd y2tO}&̩x'*dw @n7ܗwt gJ䪹قSʝ-8Wf s!)\\U˂+Y7jw)4֭])m~r~ПM&IHx!Bu$H)ťT*y%fd<.-Zx͚"HrV1fEJ1kJW"H$˰^$={ }/VObH̊BJG)$D"H$kԣ=qP AXGĊ2JcXSF^D"}e{#>T@k=%VXSW"H$D"H&^q'RػJa/,>ݞ+XaEi֔Jry\ t)2%X2k5Q?93۔6{gߩ9mSμ5 0"PAGfX fKrjvfplmP+DM=U8vj$oSy4WL!)/[54UjL̪s)h -J !Zż~1swjVu)o᷒9$S0{.̤'VQT")~`_\Y=3œiڍKE0*뙂j Mj`h'vbɓ*9KaNUJ})"fiEa_Lќ"1QD8yJ2WUS?;J`_ UPU? 7~$1 ')K$5S>׍!ur'd6;SJfQ ILDוL(_y1PTE1 g1;tT)4̢z9Rv)DOM1stQ8v`E}' ,/3(KSIV0UA`VS; A4`bBh'p, O2fTF)IJ JMT̜~ޙSsJYR\ǰW h~*yQJUa!~j"ScǴ#}`O1Д8Kئ$H SJ!w *X*Pk)̘S0F~|6}8 $I9e\/whf2O]l&1hTIMѓLSIU@r̝Y:0ݘJe60Q4s`>ThJ6'N&kFN*cCi~YQ ?&b#xЌIoI(/D[%ws$ϼGs i ӿ"<O0p%S)i+s ]IPV&*4[JR)F\.Cf\.!Jͥhl2--6{zh]3 d3sL8p5m,)yCMYI6ݜ龲IO,־/,mR뼎F6SM:%vr>( K&YT1jp/n[՘7 [OfW{kVl5 z< fNI'/P'v;j&t:(IGA&>[!Iҫ՘0ɒ%KMMZQ'v[SWt2AQ1 VCO-%OYPΚhMZZ IP͝`o;͂91AQ#{hyhaۛ(2+-jcup'1I] MJd j4ﴇew^Ež=-BET6#oS tf^PTTTTTtb$Z<$Fvd(ܬKh̆q3zzh:AeRXDY e $`  h5o}[C]J}~e2-%4?H+htp3YZ^TU <\UUVh+]O-3Kh{!?^=l]-EucU#[BVXP(ЍlDǩؼ/x ^/~ z}isJ uPE߯?bA؛gQ Ž.N@!/?G PFuFu#VgP3ې"y;쨃9`<2&6jVJ v"q/;I$!6o ?&n U~e K`K0"|*KA;k+` ?hQG[@ V LE&'QcFp6vM`Xk7 mG^tZdEYjnc[,5XX)^N/VZ?Ȋ*Rk_PZ/6òp4(Đ`5OE{] ҲBovZ<0 L`&?[a./wum?1[ͧ=K@#^@wj7tk_IPD`<1Yjn{ⰬVn2X`R1OMZ{k}ZWj7+u7j)M3 f\?ƅ~hpGAf0ò~8=,o{$\ך [kˀ#NF(v{2CN}21p:8f6SA(V;aC#b ?ڡ9mK\m6?ڒC^cqLF  E(( w֪KW/֬嶮jhZV6a|6hČ9CĘh:J-ȴ 'ZSpk[RzVIL 1-NQzYz(AnnJ7kHFܤ'ݠݜ7fߔ_M!N6aL/ MJ7s"݈߀}on WZٴVں8"N[ZeA/![;`žBG5? D/e} ~?ʭBn6(1+CCzjemam.W ?hG=ݛW;{z:C]T"Vwnn1Tn ;, ;7@2T: g fu[Q[7E53T1iZgbW0ike*yZ -eѴC:JO FoXoZ/)2Zl]㩡6 +[ T7 >2E5]AuxXV,ܕb4+ffd0Up9Yh%%Ǹj8ɩf^odN~)/ &К_X)6|SzG)V+]mEC1=#Ù45=LPA't$IN$%p@#HE@ AkMmAA8藮r9cTdDkoJGC6anJ#B6rdDH̉4!82zZ3KoŠ Z.hjAA/ }ozq-s/i HBX$ dTɏ"0GpESqIcG_M3AæA2x􋊊b 1ѥU?K?69%G&BFi2nĈ(0BUJ7fA/\ U)7fw1\ {k~iMxJ[Go~h_ȋ}h(2j*i9Sbfm* 4Z=WnL%F1u=1# v?o)H4^A9yT@,߭ͅ.4b @F1!w330 ɑ0¤ "Pn8F" ~$ P0@瘚5O~ VɑLsNSXoNJ6S'Q;!6;G)l`f>$4x;L0AZcf'“D}˗xɕsK:QFޏ֜S>|"q32~s0s3e PJp赗aON|UB?<=}>$\:ZdT7ڙN+/8f{uJڼ`W, t'՗\ŮvUeNwetnB *z:1v:ߦ-Z%daاLt4؆zfodAAX&qbC/f'I!NmxrT/@ȋ,O|qzCxL$Y@~|v[ !3Kndi}E(Fx\>w|>a~>+-gAU0!Ir$g fV9qz,7#y@ :mPTn(juSl4ΖQSRxQTỶ  ]@P1c:*3`=BJ'n*<o46+8q@իmg vr]`% [@V!Ŋ)܊Yjq6Ktk<@a"e/])$߈Ek`McVͷ?w} ;ڑs.ѣ9|d9'2Bhȗ) GID\ `+\.tRs=6uB  ŵvqrCMQc8I`A8=o y;w \Ho/}I\-rV,gFlI+{c+p (P2iedU/aBGSgl U>hC.C֋4_62߾Qnwt+d3[}iBi4_CM (nրZzʮ5&kMô69k$e#_,Q&X4Zy[3qmt{edj2pa9ӣ4|7)K,/KROeL}vZ؉0! L *TLsl;mVN0)d;E0y(#*y-HN[֧&Ϳu0(׽ղG >Ѩ-\ RG[M̌Fx=\ )MYg(SA764N-%@2E!/ࡤo0~@[M}&#Z1qFQj[:5MsF3K_?<@B-& u@\1Z>zk&XD WsR29'$nBa2{(կg۲L!KN:3tյig.9]tbl|pmɳuMgVu.0yQCU =b[GS;1 O[%F&OM4P1:cדhoE ˧I_Ƿ'3_vEBS ʿ52??VM աP"ţ ij@- ;O_naS+Xkt:a &仞4x9MAG=S9q}VIaZts˒LD xA`6(9el.d0+LWs)4WPB N-E;P6l04`N' e1t˿džs-Nk}Ie1,JnbbU%ߘ RRABIG&(}a3E@S|?@1J=rӗ͂T8[M#S=Xalw=1ܥ /8R8yՄ7XQv@-IFANT"\6o3&>̢c/DYe%kLS6q\UX>UȰxV&,4KϳݜxE?ZcRH;* F1onB3@8s]6WVkyε x(f`ځI Ks͵ބ\x╵"88@oS o"%͗*:Srr*J N|2"qJU?|*Z~ ]oXtb~:эr6]hh |ō<+2 zX`f[ ͻ,S]om$F'(Y/b* n•36 ^bd %_ݙնisFJ~=(#f)dM(F:Ad<+++VsVlx>1mh;Gh4x)L6T+Pbp@CYqg&FQ' ZMn\t[Ŗiz@[Xy[ w !`IbDR<uxxdw:FzKaͮ0C|Aճ+f%}E`蒚,͊]!7O;ڤNVX4dSW> 6 Gy}<*A:̓XE!ylD#X)T,oGmz.<eY3.+( 5)omL]Q 2!:]TNT4M'& \^xWq 6t<ۼ$} l`1? d(sp$ƕ&ub+!na8ā ui2SW1GE2O46*VmD)^7]6.q#g#Md`KzՖoc`6HX Û::[pa>1(`UPDu I#'^=Goǫ̽#Iƌ3b6rN2~/Y Pm3` 8wE1fS24FhXDbTRD-)e$ə euYoʀ8895*/[ vHaȁ CT0.ݶ ARtęղ;.!ǭ.}˺cs/bX$EXB[AYcqΏ\G9ZS8w;Rs:k3En4l+]ڞnH,'qN 9S{SLfyD \y!c~?@WS!_OTA vPj|Ј^G㹁x}Y1INTsvfP1I3_|p9iO,ꤍ I .Q$TV<\ eִUd#Fil(/H0,0#Fil(/H} X},#Fil(/H0Xa ,gq#Fil(/H0l)fP #Fil(/H} k#Fil(/H0kЁMV A,#Fil(/H04"#Fil(/H} u #Fil(/H0uЁD@a)#Fil(/H0Rl)#Fil(/H} :>AX#Fil(/H0:9X: @a,#Fil(/H} x_,#Fil(/H0xP,"6X#Fil(/H0a X;  #Fil(/H0N , @ PQ#Fil(/H0fVS= #Fil(/H0r!}g@a`#Fil(/H0xaxqPL,#FT49He`l|HI}ziondoc.datnzBJbd/41z0ǾBg{d/51z"\Bkkd/61z eBR̠d/71{TBd/81&{#lBWd/91Q{  B$- d/a1y{] 8&]8r d؀mlc I$+ޝ{:wUW ;ݓzvv=OiwU_UU*B[="mCnH@N?[3L8 @u}.WM%[fe{Wgs-+iplڛ!+7!7;Bn"nIdy9U$pO=.ҺNZ7ɕMRAZ7}ܭn}8 8} B))`!PaZ;@ ܉aIA`dSm@/Ў!{Ī_*q)4Z.n*QUy[]2}n/M.tF :3%\$t eߜnƊ3-by`ga{8 B* I(B)prJlG{~0]b.4s PA&oI 4yU\li(K(TyM~jߓKf_þ.&%AlR4Lf4s93Y,ViY_с 0 !Vy49p9YBzQӈ8ϯ688888\gl~E*M>/p6ˢ>B25iYCL `7O ^*GzUTY£Hawd$U.uI(& 2/3{$_u#jZ춿b" u/; ඐM#97$I1i̖JB资"p?f0 `Z'RsXE]{<7:>0|qlpAE IڼhGkV'qBdvsG˶9X<1^²G dDYmD>O^IJ S4 e)-xjs%*ʻl X+l\^jd[iw[=$I"|~Cy~!_6-~8plyd>A(6gTފG%uSK {Qz؈,I dt,3҂dνpP?qu%s *3˓)w:s˒p!IyB$ v=D=  \2oh R[OM'([$p{@-3 p"$r_l$!>Ue4%LdeLKѴ,K*d,$rc(6 +r{%^\].%QH;'#Q.fu>src"uk&JãY e"wk: @dS&p(m] @sρ&TYư,<̞>eaJq/*U @,I$:U%zY %!S%V^@|T%NKWMHodXes)RAk8oٍaԻe}*v+肒{E*AK!(¡M-fVU29dL0-ҿETŹ,3ʏts %TYbPV~7w8W&ʮvεnZ<2oRL C|G)AEƯ{Uxg*cO /8KyȒD0+mk}{ëP$ J~Ex6Ǥ&}n+cF3Y_d>-HP]bʄU ӴI帥~BO<=Lɸ⸵jX'&h_9šI: 2ώ{;xlǎ/;Έ]X|y8}][V(1@rI`(LuuG.Ʋ9~X-^;Dݗ'+lknG40->0'&M6(&L"{[ bڼ ץ:7okS$%<~~%@s?el,IBl fNlrobto>Cٴhp˼GoJ_"4 vtw7L))ovd` H&:N ۢ|GÀwOQ$I=ɋ[x ҲnEឮ.|pI#g.TvX^lp@0.%Z[0g2G*t):A"aX'g.X+s:uf 5I?Ds7.`l>z^z/͌x QLrAG,oǧ969f,DxUMh.Fp @_|.]VxvdtӲ@W#$ "cZOq(?}A uGg *dXӅ4LMicw.pžO%SeՋ[}wS,yzds>M~?:\A.D=W![4X1隷gG}Y7M^4`V6WnI#KX rW|.I iמ繍ZjI([Luu>d,_Vu ;D.,INKpAPZ\õVTu^{QT{iX/ML.Yw6zYKþV8VQ/˷#GI/MxQI.^Gfٓ3|c0[Q߲2`p&exdrnW,?XnQY%ZZt,뛭L 2a`ZLL!TIb[̖tNV&>px7ۣa _,JjY-,dQezyƝ4Q,K1PkU,o:`:@Nי+k2$뀫 fp0zs9dYb(LӤ7m~ZkZKE6-Zqp s9ygu|Ķ gOOk y/9t{y ;+//e2[-o9ѣPR8) &ydI,GM>ooɵz`+<KqD./ f2A[#}"I<6UdXRuƀ:9Χrjxda%Y 8N`X )̣a!_?f|x6ʑ/4{:| ϧ42#,a/ [ Il!S( 0p?#D:VKP,^T^ow4GoFfJsvo\\{=E扉x0fLtՇȭ{9E3/ܦ *&z~g K熝u_ 2I2%Q:׆".w{x]lncCkEՎ&S2 詍.ͯ"ӹ"c\8KW#GϜi|>Eٴx+LxfH^珎Oqbϥrn޶Lf hG{&KpKixAʕ_f%mCodaPN`5V(aWy]߿VKN d2y6n[ϝ 9ư+Y<3É.ERHoj:eO%n+[<6U |Vë?(^ROdD5j+iHYb`JdpnY#>E§x2 *;.-1-tۮm~ώ4e}Ȳ~KllHwoãM0 zWhE[(-tB7-& ;R~"SNck Ј]e(lLpCl%) a(*~El! U8X' %N2-hXۼk yͻq YJwG(LdΗiΤK I]}Z2#—^fZKKh%.MKZ2,ݴ.iAV!66D<&%,V=};;8\m=mU1M ݲZ*HW vcnޅiY<:0Db8Kl+/e};CP tFs SHJdNN996P*k7-T๙E_*l]=|ntQ O_!od3O |.]=/p9yLNT+Gg.u?4_R@W]M&sz܄д* TbOKLIX6]xUqP2 4àT6(C es %?ݶA!\0g| c2>{oI| n,\*,Rg`^@lP֋hp/_-MAB|0.QJ%(xS2 RF n>|d/orshE_x.9T>v]"׋aYmwFU!@7 $*Rznvϝo \@"KU.ן<*u՚H{!hû$IZPt\~ M5;^`*<û@y0U4@s '_wno0MiVe~ȲBp)2Mu WЄtaW'~NNʋxi2́$JUŲmyMx^t3^ņ4bX&q{o֋i Id5i`K/4MFRY&2yCwm̅FDs &ëtoUv57,mYN oq*>M 4M(L8@xǵ!8WUx;D7ʼ!CGpsl+i H.d4$!K2"vU|Fo=ɇƮ&B]4|k ʦsT5&$1 --f_҉ipXY:K.ן\zXןf+2ph]r2GxDc3ȒDc۹w;I.)e $[,2N,N\'/ lk *V[ [2$ܸ;6a21+r|t1WXLmz.EEUdPmMloi;=ϋ瓌_V޴]-רtՇhG4 ['1aujB4r/Rn ҈F ( 0ނ*x̃HZFW+Jckg*x Qr, ן,\DzDtן<*McX1@1M ן\j߯F1:o7$b9}MAo^%\J\[E ~|"#~0WK75Ϳ{c߭)'gk1V\UEf_[k籀DՋ^`^@D<H_r|C \T~7<64m1oߝ y/v! mz"/%l9z@SgWl2r@[DiF܃6O3so)4B"pt!,Mbļ_-ήakD<ANw#]5Q9uBjhׂ2„=RRe# .BE1b x؟µBX5/}qf6A b^eh?7Y?OIbq_3{.:}|xrv~]uYL ˒rQ󱯵tȉ %=p2[d40?zn7nm_e'0$>dXdh,}ZcЂ$I4;n| FffyahxLjX5 IDAT7!LeQ}LEd?$¤;z`󻀟'{Vj|h auD< drtm{4" wVv{7n+X 6SY@m J`\s~z?{Ef%TE)mo=q7D z{a0J1Q*)Ss| ]ܻvwUU">B&XK]9M']891PGe<+)p\kWc#;= '1>rdu}xahô:U&ò٥G6`&e3(D< "=@4!\("֡vpݟ9?D<+ #D T,>(I !WZtlSy(ELjGnE77%U,2NSt2yM'q,{!u{s߅K U23^v|0$ZV!WR|ʈyEsG̟6߻wI#Ce ?a"IoFJc}_D5>ĵF_3nYY|6"pJcyjf(raD rJD>0j㸖Q;9ij2x&&"~*gNx/՟7!+#z+"rJ2|v{|WR"[/19=Ac8̛o[z{7V&x96"j]LKHdsuQtȕJ<78pLQ"nC$A¸dǽo p*oFlNc9:4ə'f_ nYt܋iqVu^D4"T6+ן,'?K "*|,dҀ0a^~/ӈ/+E+O/"j P~?YZnD<4$B|?߭;?iWn#"꽒O+|VB"kFJϢ ]28!=X$ s$ⱗܯV< |c[K߫lЍ0#޲gI?W(N领"ٹn;B[}W«e2t1-BE讯&<$坷ƍ۷>I~pnSeo7o޳ d bbk+Vy^ZM RQU?a"[ekm Yw1 R uٶfăXVbj-"j'TWm9T>oZe1ew9'W:P_Ǿ-w!ȇ,\]3ZWmx+ÇV- ~`*˼e_ ô8:<:d=>{kM8Ʊ,O7 KSZ"$ EQzCꡋrKQA8,:esD>pݺﭗZ+I n>Jx\.voX evӹ]&ZCTLvo/'?R%u)xOm:Tځ<}ۺxmrMMs3 ͡X4д,\J]P$xDeMW(uzQZCk"F@vo[08֊ "~|wSGh蓈^Ҏ~ }- yHz-Ykk^I#{vXxG [!QٽY3jD1>p) u&nٹmm^nRuLM3`Z>[n8(1MtBHcC)LR6 ,BUv܉_"[81>nt֭R1 s<7pl0H/QL%g"~P !ˡ VD' ^xwzQ5+J @}WeF-xT \`3on p*ȒD0f,TS<􂨧R" ܺ{'::P% $ wyFɖ4NL3/ Iwv[8~\bu0`Yޘ 6 Yry\eQE($ P'~qb|BYa!X41-Kd,:'yqyƕrO$d TYƴF_ցmD<WbwR { q:zp,nˬJuWc^Xq}d"X MDH1ɐ.jha2ri297O:UE!`mMU݄|>|+- <6FAtgKHcN39=E.g65G>_ RY)1 \\:Ml Iu0Ķu]r` Gʉ̤R$gii^W4d E.30:CΒ/1iVWUQe"ri zxat *nm9}g= T=B!XBռ:[K][JvR?_T{V{wT[lD*UTr c;eOctaa01ϓ4tD7 %Z^&2r "uuR׻4,\@>'We(1egv<`0H^ǞB(w"Ry4~ IczW*2sacm)uZ FuJ/RY OV9f(ixX3jy q3\U6+r=Bw$Lwdn%R"rò( &s9rFP S(pvl +TE] @[}= .Ev imYXػk +áejzCL0ΐDd߇B-bHP c&.Uv +Z0~?;{{ ?uP:_`2}B0(JRLΥɕJ 1H>vRdYƧyy%LN`5w);?{J.ϕl/ R+t|Tr] @r-AբeD:zPk #KńKQh i ,*eu z00-Bp:cJu¬Ƙ0Cۅz ֻm`޽/RUZZ ɓ. ¡ AA:#ˡ< B޹Hr.:;}v]0Gi%rüT9% E˶p`E%Qt RNW\a-?FX^;zn Nw"3;Zbw` @jA~[APfr/RY)PUB!Z+3h\n47`MT$Mh&Ju3).)(ˤ#صU]}>|^Z9q Ô4Yҙ,-[$Iwg':HDǏ395iY M-ƺL5`WC $EQ6V΢*8/$ⱗow P.;QJMT^ jX[*yj|:Y*[VRZȵ`cnUSk`8kZSWϽ$[Էucr?{s32iM$n77Og{8I6i\\쪩( 7<׻Wss W.oiov\rjhxnj<[$_B|[+VwدrǾs\;17Yյx~`rҕh&gN{'8y9{9Sn[";b&DJ Q9C'Xc^VhU+j#J= (ˌerG⢠o>8;vuUvHe`. x3?2/7p;{׬ZK" Pl~ <54Cgϒ*q)2~x|^^xcӵbՉKU쨙܅ȲFUOsi}Ϗ7?ϰL%i$V{9V7Ҩ!Gx z =`{_;ìjp9Tݎ7X 7! Uv9i}0xRZ<@@%.T9gn.݉xlEs ]f,gLqcNOMȹ$/MP4=쎆 =m͸M9srm$ UmF&XEa{[3ۭVvFy]K<=<\[uuaXEqGjJc 9|ʺ LD<H5H" >X-+'؛W8?lL"{/ x+oֽKY{@VgNR>Pn; "jPFX.K $0nZVlH}K$"p쌆hy$8=Ʋ S ;HQtGDj鎖e-ւs癴4D"92R6L2FfWcWtt cL{Qw[p Z G ,xLGhM6 uƫiMohB`T!@,|I,V]Al"ULڀ!Vf-~ aքOy|Ya7NF%BYm\}0Ȧb=~s w;b[,Ez'+~QnDPAij4[Y,^3_2qA+e|!+e /7'LbfBl!_OaPD%w4 0ۍ#\$qgdC|؎eaclbam_\ %S"Q$C~j$U.񓜞A[zֹùtDc2@Qb=o|=kWVQX,3TȦ7s[o) .oB?dKshϡ,؅om%zǩ:z\>*}XIY?'`WuV.6 eoTO!smzѱPIn ?w'.a] a_DU x/BwsY04x׃_3i\"5ƭTe2%\̭".8s'&^oF#z?__ڲOq%⺦?:*GlP{<#olR; |:e!ME$b45k̦YoaЧ Uj/9gq)2m!#4m_HVU×$h8DC8i9w\~բ.ӲgxdIh_KiiO4_$J@w$Ȟ0GGyh'օdk:Z_2xPWEe<զU*S^GiC0>^ CP9G(뺥?+Mj/( |a pOwA2r;{g˻lp(Oxqtl!ӂLInnו nJ{C.ɍOZ6jQ(K^0 B4GX.>GeʆLJ+$Afg4LoC"3S(X"߿0dn}b1jO"^ ®eDw@ 6k|)ԂWE֚.TDX}~?ՀV_iSlυe痁)ZC _Bظ5` 3*lE$[Yk!Y}c466 7,n6_a ha$Gj{]KKoC*W,;/; IDAT&$y4wAeW}ݷZY[wUS=GaX`iLȖ@"Á7`D ۄ!@Xm ml%BF{++{ʪ;k{t/2[nU9sE^pخjg{xi4€ eJ)%WkNd߼r\`,rvaZsva:;vG$Q|Z33təAA2ߋΌ#j;E}"6U:9  ;\c/b1ۖؾ~}389|-}Wdc,"G\z d|R8~eW,\S'g}'R$ɽSp^fk:nA_ͮQ:9iGܖ&u=%v8ZA+6n 7{CzB} "?/#C*OngwEAζ@6G!k}9cZk$WsON1^wfD0QZQc&T-.^iJ; 3ksXZ^QJJq{OZ,"l݇ǹ::M"/a)] ?Ϗ؏h\rgssnx2VojrOyrpTyަ{BDE"P {CSO^3V{z._s6 (TMnڼcb2]8WW[\]mIRt* k/ ,i6? jEHa)I`=)_ } n6O1VPk6A)J{h0(űrbvz7f5hG]_I!2Y\1[RR>G+)QVJa5YnGܨ6d|;mX,Nsb`_~; ߋ | }& !ӥ&OUYhvȇO/0Rٖ(HvB;I8DA@@kcp]JjњܒO[YDv8"|Jh;x2pXcN6Z3ߌXlEe|ZHvBJcbX, oE`ʿ)ژv51 9Wh@jLpQ<%NRfWVYmh$$ns.(>QqL+N$ QQ *Ah.9yJ)(B'1ax.$p]1ӄvDuum hIr'8J.yϥ<<1BV. 6nK]caHJR>$IJ\ZiIR5di֚,rwVG6!~t"{̌*QkrDk(vU4A".ӥJS^Zd^!8Xm))քLo ֹ#to`fZۿ&]ʎ{twrG{n̍f/:qtrNՅeFBG&GV7$KHrWk-囌R~䂬tsE~PwDR FַcF#@Qir@|_svKI|M-HzRvR>yRT/stqZKR%)DDb%Sv^b|r7;=%4bMLф"̰(357wOFg!MfBסυxw\MJ1Y"M'MYjiq^gs,|"0('2'D%Hi( vcFXH6&b&^p@fH8ylRCdmV"03G͂oE ; ǡR`S餚NPƴTf)|1o _Gf{!@ʮ|${<2R {E/_F gR}[D'DgbX,}5&DfY ɧ$Y$2qqP[qblunĽU̶zbż@Oxg& a6Yӎ9fmwNZ,m~:R1.2[vvlE3Htns t]^I#N@wqp6gדl$5bX,w >^=i@dGU_Pqsgӯ_{ҽ$u&'X\ҍ)25E!kBwFf8(8Z(E0 {60x+{Onj=3f VObXIxG\[32f& X1ggað8 >N!N5pdz$MtԚM$H2Gr!~1T,PꬬPJ 輨_Uo [mc3xZwNu'} 8X|!`b$++L[`4{u/ {Qu<C$rHT3qm3H@q .Zk4x13d ;T]YD7Z-3lLw'(!'{=GnCO_sqE>|F'e92uK+Uю~בcH6.RB!2Qc#>.y &/&{͠t&1loLde @(Z,b$=x#͌v8Jy"[`v4Db+'|X,{a.3Y*)Jap[1H<־֤ٺ@jQj;ҎcDໝ:J9^6swDIBVF[X%D.O?"_rɇ!R*&yG@j$̟}.vQ |ʹ|H1P@B..2mbX,o:HGoo1tSM7IӔDuЌ4.n8 qpr$PowhD1Qh p InI<+O#KvR4B1^S }:qBщc*Mm Q%)v8dO|BS  ߯p3we?ݾ#њb0&b , &,=辡OK6wL^3 m Be콜a3$a>j2;`wg_GH( `!Jd,VZ+-,5`5,ߑ0T`4F]oe?(h٧;DN KݔFj_SSْARŲܐ/Bf*ҴyoS2|K=]s\Tt1=A!G GAUT|Qr*L  K]#~_GbX,Cg@oL.1ƚF"rpH먾=)۬\-N5, }epH/B1%Sw6+~"}YbX5&+w <\:޼Xk$&$Z {1A L,T;3"PIDAT R"e1!ph֏GwM Hz%@^&041y#є/ [,=~D@Уp(ߧ Ht$kx6̨!k_d#!𐥙YB8l{ ̬ຊz(0v"#ׯYZI[X,ݎ| 'd}Ĉ~O!l]"K($|Lb 12ۼ_D"΁ $>8d#!X,eOx͛tXX,bX,bX,bX,e=IENDB`structlog-25.5.0/docs/_static/structlog_logo_horizontal.svg0000644000000000000000000006660015077624341021216 0ustar00structlog-25.5.0/docs/_static/structlog_logo_small.png0000644000000000000000000005423215077624341020120 0ustar00PNG  IHDR;= pHYsodXLIDATx1 07ڣE 3>%H %H %RI哔3gv<4w*qN`;ضm۶m۶Զo;s}|}_SJqHվr##+}rzz}cx Jkaqo`<$4PKI+&xEiA:!= Ԝr QF#h otvIDʯژ8k+3}G0RH U8d "LNI #-R*M*B DK3bAA8VnS* fFaa%eet;)㯵T''KgO9 $QC/@oN(~$aTQ"B| F.@[qAGq@]Ej k"azWΞvrtHJNol\][M"ʻuph(++ؑ#xZ C=]My2ғ=؅ K/D*rV{R@|/P'%2$1PJh_陙>53vSiisIi֗CC999ViiNz:8ᆬw9,vzz7<84\QYeafvZMGDGQh" 4,V2T$ Q5)#< "OZrx ?viSoo~a:0nAFƪZV^>99ᑑ\k++Y,"c;,@ CNm:0-͕˗^x]CCBb݅S'd;ď4Zh+wɍ<1\`H<{7?)OKK/s',ز?R$3w!!@pBn]?W\?Y-&a9k{xVuuQ[. ȘJ2 Z$n*R|ٲ{k_:򩧞y<MD Vj`B!t86Fk V i"mzm-K\kcEm~Jbk,4r3_^o߶lZ..IeQ@~JSk_Z@yͦM&kmb& R6\hCq˨%ھe8gV{T3`]Q0Ք(OI8V[YJBph'~1GU94)is)IeZi"DT̻;CD\:4@$ ɤ*B B8|є&Яw[G+0 g̅2@w6Y|泟-xD%I".R%K$IdTBnݼ9CfP!UcąJ(69"9.?1w{->z ..гJhΈD-YWY5WIvy<ݓO>ß .翫f1`BG<&&k͌dEؘgp4*"O%7 ,XM[hjŸ|n]lxO=՘M IwJ욇7-)6ѧ+.hڈZ*t{mKCK;]$ܰDȪLe*H1#aof&@}b^Q.ԪƸTǸ,TdAӱrځ`^m5ߴs'4ms>BG,+S X(#aMZ&ŭ[Z"*ѡfxvկu _u&Xm.-!(qDvJYU(%?^0~*jDav~k-ȣcDҨ!4G`pH(s\f<4a5H+(։8E\?}Wl6Aqt k54Y#2O(eUKimVJ+D‘'#b6/!CFbB YYh2ЏDeCP "/P©E-*[u¼t.t$*pVhJŧ?Lr-40x(4J,,fvY_*F9oJ+iS\qx D'kBaLm "5cDYrNM ^tIRCdǂ&Ic5n ԩӧ3+V̖R%3DthJK,d! w{Pb]u~~ \f2hݮah`_NcMDJ,b<^jhQٳ'|;{̢TAG묈H#2F y[{9gf 5uR,P]Ta-d|۱YYlհ K(֪\…Ӑc#I8L8k֤=?ZKJ2תDB:1qɅveg.=2-+9.7{Y.3 JhgP "wȘKd(mQԠG-L}z䦸9Rj  %T; U5RZKlK$p"1 Q pI749ʧ~~R8]sEA~LyX":}Vr&,2ĝo)J 1Dr ʫ "Vo C ,/H.jFDHE[zCyc X 2paD9]TZjvWdM5TeX.ʫ<2@wefX\mlOJ{8$Ax獻 7ۭ~莧yi %6oCF-I, `&(-:M< 4N.lap\". nyԬ:S3#-\Ƒ.)DpI̯7ʘZZJ$t63k&#E6>^M߷y =s`<Ď(rPPg^UXGz9"'^py{TH7q[z=1YK,tcMo29LZ4"Y!174w$Kx߆/|4ƴM5"<36O^evG.XPżq:Ȑs8t5ה[,hdhRذK-@~.;RNý56>p_z4d<'G$uIFHF]"a$GsV7(+Gp9C Ls#5Φ9̘Gjjmk xSզA+y%X%a$˫LܖkO<õY]j.l'p@ǦhH"(WX.?wq\=kY`gӆD/7(: }C źS q/*t, #v`Եɦ< l/ E=:!RgʘV/~1]jVisVح#M¢0+ˍ2LDutTKk$bhĵ.-Ѧ%uӖ[+F%~YR7ggcCp"(A$~;l\^{yXA@'7JSU *?,HAFcFf.%#rG!*) jezEIUfTuucw9w>w?/8'ΞgiԂYҜ,";+'k;R9Y_~7VKi6w >ퟝ?LSiz_氙Vm4\D<{K(UbvL{:ʕ=H"NdAE_};ˇ;^{`ė٪cr)J #J>p-)bdW\9T{Kjvʎ/l} Jxsް~scQrUΒR/#'4xB^;N˅w:Yn=+"Q eDrQK(]1yC;nM(Q'uɵ6H8[aecپ)d\"NsHOVP ZR"Y&k(b{)"P}.PBwW6ҡ|衇kzd$!#Z ?i"rJ r)PXQ sr^ _ 4ԝtɖcՠb&˲dIfff'xq94233\f^O&-YvιASuWY|O&6>8H<8D|ҫUK!»OaBKˣq߇ j=kod{gN_݁Yݝs;o|quᙩ݁l/lZ:O!pȠ_M$YzAM7': 3m?Ic O={♴bїW/R+H_lg.FӪ jED!DsHC*<ڞ%bv=7Lp:OgU)!` Jǫu:S -b5Fj->w1/)1D~AgM >]6!gDYƮQ4ʬiA*tVpPwa.aFa^avYU U>Qxy}J )tk7u:fu 3ʔiP ݱQK-*Wo7!&`ePJ.txggYМ~AoS&1<4%A ONHR(wδ:塼VKZUHgo@&Pӟ ڂ,bVFy e4R9a=m|2c/żX3' kX̕ Nj- v"cőZ|,+ I JٲγbY'":I# b \;[')GeŜ΍f@cYЄ!h>Ӌ &N rV&ؙ`=ȕ_-\HbYǡ9SdQjqRBp(K1mOf9𣏎e+/YMF,`=FՅQeEtv|Ct* $.PBI:~gkqIY'zyYDstCix3˛7pecqw>=X7A`D 9AS9)P vQA=>#>B8~';qޜbɤ`αRrnOSixO>1E_F_8b(I# $&.JpeTʊl; 1Z8v_{sZyNi:2,V aB) ra1Wm|#G"8F$#h!h8z%Sڃ*PK"2ŮVqFUwhdP!,˗kJeb֋y ;+ Hzfʷzk<) OgML(njKZ\K[E !IXPJh> <'˹D~GȠR@qqYlep\Jd%&,Z rDNf y jԊ榖zjV8VDk_PvAi݂[+r{Ƞ-GcQefƒW6։a2n;Ey?E U}]#76UJZhPxUhAS+-jjD(BI c :JdlFlX xg2;ܑ¥.G#>pG%A(h3?\[z A)E[|QX_lik,mp<ۛτ+T|m.k JٸDYQ黬@=q~ _<(/b,y{H9 D}@I@5# h|DsSVBDqr MSzJ؄+=!I|]PJY&VV:s)BXwt\Rc(կE]_"Acg%AsPZ%A`V*H):7ԫ NljN NnچzA%좈&v|ݭS\R߮)Do @ЄC򂦖PFk$7ppkni5pO"pu 7 es .d; B6صV $ƜAկ}-s#v3fȿu ۅNS LRz4)9@IvJeٙi,fTm%x@3i``j̨g%PDvio(C;uZN 3|SN4+" f?+ l,|S80v܂#w,#a AMD?B&xX z-\&^kyH1%A9F8H 874r>2Pmc5O:V#=T[w,Jl1 W^t :(ͱa21Q?M۽=(B&'el,|izEsIkU!BKo9 AMwXɴ:üOЫFjr9p @Pʼj kA#e"kA yLbJHL)?Cӂ3N66\;UcftI2#qag`u2wdLgR*_d"] A,j5$B#(Ge BE.j@jZ`ɟZC})/HUCߝe2giO ^PwQ@VHj6564$ \roQ$D3ˆ*,|}fq;j`rJC%tl\C%2'ٻ 6.ǂc33Rl+c'.33333sefSfI׺&QttrDo߷{#F$1 ڎBj6ӔjW[;$_kZ<.^Sd~tl`͜tbɓ~򫯾<}&IQݽ-?;ZL\si4uB5ԟ9 l5`Z&|])с4Jf2jx`:y\y6ԠZnV[m G$8}ݠPQvz2HDE]7bjhD |]٭$d!.31A4(gciшƎKLs3X .I2rO>3 < cTe"CE z=&A@lXPpCc@y%x1᠛C'xi3kh\6^닒tʠ#9DVc׉>~ cp^,iXcPi,tCO?Lc@CF9H,m;op=:bA,"J8t:P^5Kr[2P > q;d&[?ƀrٲe])%?n y9&S.pH2sű%qTNě(4[  (M@åXl(άT|~ Ӊ!t";lpY g0a-Ӯ gw)=u xsϸb]#6>Wq@ 08 2m ۵a0{֖\E~HP^w^w]~n$6 {_ꄘX84j:eP'j&wnR)c,_q`7k 6X_[Aqw4K/5O?L1@S w{\I W u%j9rd"*hJ,eN d8d Cp:r)m}y#Aȿe\j=* .@8D[Vnyzoq Á@zTgۗ'nt$FoL6#fM~Ff뭇i G< ȳFnXs5ΘY+V@d?C?& yA" Ew N URy-5Z>MأK/7MA،t"!KNZ?.(p\mt3j&H<kJF͇ڂ,kp N@u2IIZSs9LzqD2qwDɇ}2Ƕ)%t#Gg3ڂ,ȭ : ag(9e)BԂ>;|L ,{YWAquͭ 7c;n Au)7 kGcȉr NZ/_Rrh]|;Ǒ>˯GhP-3T *`)UfpƐ2kAOCv:\*(';V/Wg@4-(9vȦϿbrG;4897Y6_W(M>8vS@JlЪH(xURQ5le̲$]<:s!m<(m7 Sب,< Ɓ UmnM8{)P-񏵓S^Q`"FZ fUPrc6*9T`wA0\^{oTJa6é5a #H ʠ׍iDdpj% 1N) n&\uɖN-*%q~ $8\k +8H DUeCu?7nY:vH,}VM}S֪apz'NEm|hr%~__AneХC22Pi͛3{Ay엥3=fޜ9pppjłg4N`p%[[3ıTڞtꝥK (~dͪ#gnBW1M, SqdHC=[ԓ}eePt<5睋O=.*Ƥq `pln᎒%ǶXe\$7w P~G* Pw=h# ػD9,~'ʠ<J7%P>$~~D&6I5v Rܭ1H^*OkaLsϜӷWl1V) ϜU4IyNaS9qζ,&÷x(A^(f-]'kh#;[8 cs!Ha282&.W@8Aw 80b-#D I]EE&InV|SN68s?^el~_{هF 䰯JW Nz8538c ܥ mĶZW_=@yW ixD줮bp7ICe_HЀyaG7ߤ`n (;D/϶rǕW_+S )CC4~,m0ڤ֧p&@%Ն?% qeY g6@*w$.lOyG" &|L`6"+u&PHSN@YQ{c-(X z(IQpsVzWXOQrҒe?B>#˒ґ m%[+ǂ!h"v ǴBx}](ePF{%&3<Ш!_.a z,w+N2Aܚ0xX=QrA,c'Zm:u?4@m$vtuZV_'xڈ$qRF?l#PF=8Gq@U9YrSF%Q.jۗX^['.9X`;(qQ#"Y?D*{Ĵnc$]s 5"@y@)7\?pug&HQǬױf$dnAC W 1g}!(˳:k\> GQG[ w+`{?$9^¸+ɯPl8jAyٙgxE#+vH$Aд,6BҋViTƇevrOW_Ï?GP*ׇ~xܱ!qNMixfC  |eDB(uIb~P p,Y5xMShP ZX3uBDbBJ ;P*@:L.,*jݦZVb(^׌83`X:A*@Xx (_0GSU֏]@N"x-7i\NOU`TW^ye^gR]@5X. D~I0D]sMRe1^PF$(AmIUݥ$!B:Πs%؏`q&J/s5d J\8cqܨÒE,*[q6ـq.&&.:8p {GsE78/=@o[[BTTL(02(& 1ejbppoCkI^$B9N7$|]Czcw3*_X\R$v),l|. >SO=Iy}u(2g p1`bsMAKQJ"Qf<,sVkBEMFB6GlW2ǖ[huoqP@?#30$bU;dTLK pGDU' % ,=usMVOLf)oJčDXm]N+fJǘJ#NDB5Q:VFrXs͵~O&l[o>1'" pwBfݫM?υc4J`8ђNwvtNрI~,Bjbo/p Q3 &o|I'A"7T6 矿Ќquϙ={ʤIK>;ʢ ReGS)EMapsC:a!?>C54 m&^wк1 X{ZkMMVB%twO62{?SZK^~z饗?imF 0#l)`kpN1@me^(̟?/[lE R-^|ɥz衳fΜ4a nZkX{m6F].<䐃.]FT*N [U& ŭz;?l}w>ݝnT냞H[tZt7ڨ޳7 ꫯ"|iӺ̞{ _[Sk@\}'Ⱥ׾7Y=sguV[n :)SĶ|K.)֨@`P k>{1%pW,2ꭔ1no 1CR*3+s1wI阙sĹIp&V?N=Fkwjm۶c/ k׮u#^>/~ˆoͯĠ (}b2&1(HbCyΰ(˥nfbbbrr222: u@k܉RUU S^2?PZ$ȤHp39( y AnE$?Mfd/\v+V,\0>>^+AZsN[6mڲi#&?vaIbJDŎ* 8FGeE:BsAMB@`2g|c*0+`D2't(9I(FQ TXI9hRJިtq'Dj\|ӵxEKgUe%U7mhhܲe];}j+ }'^>%JWhDD7pI%1&N`]^8 /077W93Q7<AZ=O`ztyT˘ p4N_V>fG$S ޓVď͌"1-7ÿӉNGaHԣM=tL0dݏA;g#|qHDniYF'YyW^uW_EL<$po?:Z$< 5SS\N(c Èdh#6]Sf"zBdp%[Gۘ7x<3s%.ozMvp̣5PWz!.Y,T ^XpW__O@[WOCk^twߍ;uuu&'T\ 8/+ uKMNsAC"^3O>ht6o,^vO{snkEsÝAXoJw8Ѕ`+ Pxh5,H mOѝ;wvvvv$`VGd\- b%^p Ʒehw z|+,MioÊܜGza^G9U&CRP⠊]|#UcQvH E`f rrA pEZ;pxիW _@~+a8uԋsqs`+-` Q f51a7ub MKgkȱ(5klSQ'RR!561GP675ݚZҰ)g8猻t1*8d_Xp7n" m UeC[{ck;7KR0+?}Y!?O?~;+σ>|qgs)IeEŅEh,2)BC&Ƈ4#jQI ~bSiՈ]T uDbhàt ǏkfP1B"8SX NrfICH0'LoI< ]qpۻ{Nus2ϭhjضw`0-##5k0H@/{>dS[dzJ Gq{@(?7%1{ o?J"h1[KQ6J!J X_A&/`@rx\L3UjH }R@+8gi*MgBQɜFiJ hα$Cakoj_7 *w{C>t2,NYSS{5.{ @ֱړ '"!09@ nӨK%gIHaSP!u0#&t0)]+_bF|a%f7vݷx}mH7|d| MA&($h԰U tp4BTXM-=PȢ }CjLA|Ö}CK*U=fwf|?ԓ'%%?d5W\Ew4wbzU-8q"9!^OTYKލGP"0q"v!|W'4*U /4PGڛ~hiIyv^F\2q=MHZh`h_vy-Y "D1ZǚZpU i]w!Vf&/<ߡ%ū.Fx Hڷtv:pp:5>v+L\$<" 9}ykYTd ԦʬمnUpmS3wi-WC >3g ~PGgnF"rI.z}fZڑGw=CpW,xe˔ӗm۶}?"Xg}Hp^M0‘8>7!Tg0TMw R+aG]l~JXq'۠3Aՙ&`S ~?`Bs;=,_/ 8z"!&巔"KM{Xr&8P*ψ> @_ްjŁ#ǀ/)+E{W\w[ v\q?UKχg `=o*H9i(BOc>߸ˡ)vbA BaJuݦR Yu^rStB wq\֋OlϜ/@dS,CgPSldQ`VHz'8]۠*ߓLzAo04Xzw;Xbrd˖-09ov/2l< lPx'@+ zU %)(4mjإ).]xEA Ra1xÊpÊ0`+Ά칚=倧ѣoez y\˚!|'*g[۟ZM]q6fyy4fR`C8S=3e &_BNMU'1iDi6*1iHs^M$i"5G a1 aŃ'λO?= 2>lŏpD|˩>@L"gy6")f #}ˎD:"on w#jNc }OߢT63̕hYS\ʫ3)!m%j =yX[n v9(VOt-.A!˪n]Np!kD+ 7c~Lf80(#h}C0(;]i85V`-OzZ-}I .~FCGnmny'`|R}TEx#Vt(Ƞnʰ2=smm\@ ȇ>맾0fd.1^bX$nѱc4C & #L -8Iȓ LIO/(*Yh1,XA9\ί:x֖~ {e{j"v@'0TodlU+aw7<}sw:oQnJӃh K еȺna*񧃿Px"펤ʪjB(։S^`'A=:28sF5bT`1$'"W<fr&2:]V=8LdrQD͑5k"T`[nB'dMrj^aaEUt!򨥥YHeĕ!?| 9y4H ie{/_~ւN 嫯\eڵKo޼?Cz>,<ԗ5'&&8 ~x!$eLbP$&1(c2&1A1I ʘf)IzsIENDB`structlog-25.5.0/docs/_static/sponsors/FilePreviews.svg0000644000000000000000000001210515077624341020160 0ustar00 structlog-25.5.0/docs/_static/sponsors/Klaviyo.svg0000644000000000000000000001256215077624341017201 0ustar00 structlog-25.5.0/docs/_static/sponsors/Polar.svg0000644000000000000000000001262515077624341016640 0ustar00 structlog-25.5.0/docs/_static/sponsors/Privacy-Solutions.svg0000644000000000000000000001545615077624341021202 0ustar00structlog-25.5.0/docs/_static/sponsors/Sentry.svg0000644000000000000000000000355515077624341017051 0ustar00 structlog-25.5.0/docs/_static/sponsors/Tidelift.svg0000644000000000000000000000404215077624341017321 0ustar00 structlog-25.5.0/docs/_static/sponsors/Variomedia.svg0000644000000000000000000000361415077624341017641 0ustar00 structlog-25.5.0/docs/_static/sponsors/emsys-renewables.svg0000644000000000000000000005104315077624341021045 0ustar00 structlog-25.5.0/src/structlog/__init__.py0000644000000000000000000000553715077624341015530 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. from __future__ import annotations from structlog import ( contextvars, dev, processors, stdlib, testing, threadlocal, tracebacks, types, typing, ) from structlog._base import BoundLoggerBase, get_context from structlog._config import ( configure, configure_once, get_config, get_logger, getLogger, is_configured, reset_defaults, wrap_logger, ) from structlog._generic import BoundLogger from structlog._native import make_filtering_bound_logger from structlog._output import ( BytesLogger, BytesLoggerFactory, PrintLogger, PrintLoggerFactory, WriteLogger, WriteLoggerFactory, ) from structlog.exceptions import DropEvent from structlog.testing import ReturnLogger, ReturnLoggerFactory try: from structlog import twisted except ImportError: twisted = None # type: ignore[assignment] __title__ = "structlog" __author__ = "Hynek Schlawack" __license__ = "MIT or Apache License, Version 2.0" __copyright__ = "Copyright (c) 2013 " + __author__ __all__ = [ "BoundLogger", "BoundLoggerBase", "BytesLogger", "BytesLoggerFactory", "DropEvent", "PrintLogger", "PrintLoggerFactory", "ReturnLogger", "ReturnLoggerFactory", "WriteLogger", "WriteLoggerFactory", "configure", "configure_once", "contextvars", "dev", "getLogger", "get_config", "get_context", "get_logger", "is_configured", "make_filtering_bound_logger", "processors", "reset_defaults", "stdlib", "testing", "threadlocal", "tracebacks", "twisted", "types", "typing", "wrap_logger", ] def __getattr__(name: str) -> str: import warnings from importlib.metadata import metadata, version dunder_to_metadata = { "__description__": "summary", "__uri__": "", "__email__": "", "__version__": "", } if name not in dunder_to_metadata: msg = f"module {__name__} has no attribute {name}" raise AttributeError(msg) if name != "__version__": warnings.warn( f"Accessing structlog.{name} is deprecated and will be " "removed in a future release. Use importlib.metadata directly " "to query for structlog's packaging metadata.", DeprecationWarning, stacklevel=2, ) else: return version("structlog") meta = metadata("structlog") if name == "__uri__": return meta["Project-URL"].split(" ", 1)[-1] if name == "__email__": return meta["Author-email"].split("<", 1)[1].rstrip(">") return meta[dunder_to_metadata[name]] structlog-25.5.0/src/structlog/_base.py0000644000000000000000000001625115077624341015035 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Logger wrapper and helper class. """ from __future__ import annotations import sys from typing import Any, Iterable, Mapping, Sequence from structlog.exceptions import DropEvent from .typing import BindableLogger, Context, Processor, WrappedLogger if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self class BoundLoggerBase: """ Immutable context carrier. Doesn't do any actual logging; examples for useful subclasses are: - the generic `BoundLogger` that can wrap anything, - `structlog.stdlib.BoundLogger`. - `structlog.twisted.BoundLogger`, See also `custom-wrappers`. """ _logger: WrappedLogger """ Wrapped logger. .. note:: Despite underscore available **read-only** to custom wrapper classes. See also `custom-wrappers`. """ def __init__( self, logger: WrappedLogger, processors: Iterable[Processor], context: Context, ): self._logger = logger self._processors = processors self._context = context def __repr__(self) -> str: return f"<{self.__class__.__name__}(context={self._context!r}, processors={self._processors!r})>" def __eq__(self, other: object) -> bool: try: return self._context == other._context # type: ignore[attr-defined] except AttributeError: return False def __ne__(self, other: object) -> bool: return not self.__eq__(other) def bind(self, **new_values: Any) -> Self: """ Return a new logger with *new_values* added to the existing ones. """ return self.__class__( self._logger, self._processors, self._context.__class__(self._context, **new_values), ) def unbind(self, *keys: str) -> Self: """ Return a new logger with *keys* removed from the context. Raises: KeyError: If the key is not part of the context. """ bl = self.bind() for key in keys: del bl._context[key] return bl def try_unbind(self, *keys: str) -> Self: """ Like :meth:`unbind`, but best effort: missing keys are ignored. .. versionadded:: 18.2.0 """ bl = self.bind() for key in keys: bl._context.pop(key, None) return bl def new(self, **new_values: Any) -> Self: """ Clear context and binds *new_values* using `bind`. Only necessary with dict implementations that keep global state like those wrapped by `structlog.threadlocal.wrap_dict` when threads are reused. """ self._context.clear() return self.bind(**new_values) # Helper methods for sub-classing concrete BoundLoggers. def _process_event( self, method_name: str, event: str | None, event_kw: dict[str, Any] ) -> tuple[Sequence[Any], Mapping[str, Any]]: """ Combines creates an ``event_dict`` and runs the chain. Call it to combine your *event* and *context* into an event_dict and process using the processor chain. Args: method_name: The name of the logger method. Is passed into the processors. event: The event -- usually the first positional argument to a logger. event_kw: Additional event keywords. For example if someone calls ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and *event_kw* ``{"bar": 42}``. Raises: structlog.DropEvent: if log entry should be dropped. ValueError: if the final processor doesn't return a str, bytes, bytearray, tuple, or a dict. Returns: `tuple` of ``(*args, **kw)`` .. note:: Despite underscore available to custom wrapper classes. See also `custom-wrappers`. .. versionchanged:: 14.0.0 Allow final processor to return a `dict`. .. versionchanged:: 20.2.0 Allow final processor to return `bytes`. .. versionchanged:: 21.2.0 Allow final processor to return a `bytearray`. """ # We're typing it as Any, because processors can return more than an # EventDict. event_dict: Any = self._context.copy() event_dict.update(**event_kw) if event is not None: event_dict["event"] = event for proc in self._processors: event_dict = proc(self._logger, method_name, event_dict) if isinstance(event_dict, (str, bytes, bytearray)): return (event_dict,), {} if isinstance(event_dict, tuple): # In this case we assume that the last processor returned a tuple # of ``(args, kwargs)`` and pass it right through. return event_dict if isinstance(event_dict, dict): return (), event_dict msg = ( "Last processor didn't return an appropriate value. " "Valid return values are a dict, a tuple of (args, kwargs), bytes, or a str." ) raise ValueError(msg) def _proxy_to_logger( self, method_name: str, event: str | None = None, **event_kw: Any ) -> Any: """ Run processor chain on event & call *method_name* on wrapped logger. DRY convenience method that runs :func:`_process_event`, takes care of handling :exc:`structlog.DropEvent`, and finally calls *method_name* on :attr:`_logger` with the result. Args: method_name: The name of the method that's going to get called. Technically it should be identical to the method the user called because it also get passed into processors. event: The event -- usually the first positional argument to a logger. event_kw: Additional event keywords. For example if someone calls ``log.info("foo", bar=42)``, *event* would to be ``"foo"`` and *event_kw* ``{"bar": 42}``. .. note:: Despite underscore available to custom wrapper classes. See also `custom-wrappers`. """ try: args, kw = self._process_event(method_name, event, event_kw) return getattr(self._logger, method_name)(*args, **kw) except DropEvent: return None def get_context(bound_logger: BindableLogger) -> Context: """ Return *bound_logger*'s context. The type of *bound_logger* and the type returned depend on your configuration. Args: bound_logger: The bound logger whose context you want. Returns: The *actual* context from *bound_logger*. It is *not* copied first. .. versionadded:: 20.2.0 """ # This probably will get more complicated in the future. return bound_logger._context structlog-25.5.0/src/structlog/_config.py0000644000000000000000000003324615077624341015373 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Global state department. Don't reload this module or everything breaks. """ from __future__ import annotations import os import sys import warnings from typing import Any, Callable, Iterable, Sequence, Type, cast from ._native import make_filtering_bound_logger from ._output import PrintLoggerFactory from .contextvars import merge_contextvars from .dev import ConsoleRenderer, _has_colors, set_exc_info from .processors import StackInfoRenderer, TimeStamper, add_log_level from .typing import BindableLogger, Context, Processor, WrappedLogger """ Any changes to these defaults must be reflected in: - `getting-started`. - structlog.stdlib.recreate_defaults()'s docstring. """ _no_colors = os.environ.get("NO_COLOR", "") != "" _force_colors = os.environ.get("FORCE_COLOR", "") != "" _BUILTIN_DEFAULT_PROCESSORS: Sequence[Processor] = [ merge_contextvars, add_log_level, StackInfoRenderer(), set_exc_info, TimeStamper(fmt="%Y-%m-%d %H:%M:%S", utc=False), ConsoleRenderer( colors=not _no_colors and ( _force_colors or ( _has_colors and sys.stdout is not None and hasattr(sys.stdout, "isatty") and sys.stdout.isatty() ) ), force_colors=_force_colors, ), ] _BUILTIN_DEFAULT_CONTEXT_CLASS = cast(Type[Context], dict) _BUILTIN_DEFAULT_WRAPPER_CLASS = make_filtering_bound_logger(0) _BUILTIN_DEFAULT_LOGGER_FACTORY = PrintLoggerFactory() _BUILTIN_CACHE_LOGGER_ON_FIRST_USE = False class _Configuration: """ Global defaults. """ is_configured: bool = False default_processors: Iterable[Processor] = _BUILTIN_DEFAULT_PROCESSORS[:] default_context_class: type[Context] = _BUILTIN_DEFAULT_CONTEXT_CLASS default_wrapper_class: Any = _BUILTIN_DEFAULT_WRAPPER_CLASS logger_factory: Callable[..., WrappedLogger] = ( _BUILTIN_DEFAULT_LOGGER_FACTORY ) cache_logger_on_first_use: bool = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE _CONFIG = _Configuration() """ Global defaults used when arguments to `wrap_logger` are omitted. """ def is_configured() -> bool: """ Return whether *structlog* has been configured. If `False`, *structlog* is running with builtin defaults. .. versionadded: 18.1.0 """ return _CONFIG.is_configured def get_config() -> dict[str, Any]: """ Get a dictionary with the current configuration. .. note:: Changes to the returned dictionary do *not* affect *structlog*. .. versionadded: 18.1.0 """ return { "processors": _CONFIG.default_processors, "context_class": _CONFIG.default_context_class, "wrapper_class": _CONFIG.default_wrapper_class, "logger_factory": _CONFIG.logger_factory, "cache_logger_on_first_use": _CONFIG.cache_logger_on_first_use, } def get_logger(*args: Any, **initial_values: Any) -> Any: """ Convenience function that returns a logger according to configuration. >>> from structlog import get_logger >>> log = get_logger(y=23) >>> log.info("hello", x=42) y=23 x=42 event='hello' Args: args: *Optional* positional arguments that are passed unmodified to the logger factory. Therefore it depends on the factory what they mean. initial_values: Values that are used to pre-populate your contexts. Returns: A proxy that creates a correctly configured bound logger when necessary. The type of that bound logger depends on your configuration and is `structlog.BoundLogger` by default. See `configuration` for details. If you prefer CamelCase, there's an alias for your reading pleasure: `structlog.getLogger`. .. versionadded:: 0.4.0 *args* """ return wrap_logger(None, logger_factory_args=args, **initial_values) getLogger = get_logger # noqa: N816 """ CamelCase alias for `structlog.get_logger`. This function is supposed to be in every source file -- we don't want it to stick out like a sore thumb in frameworks like Twisted or Zope. """ def wrap_logger( logger: WrappedLogger | None, processors: Iterable[Processor] | None = None, wrapper_class: type[BindableLogger] | None = None, context_class: type[Context] | None = None, cache_logger_on_first_use: bool | None = None, logger_factory_args: Iterable[Any] | None = None, **initial_values: Any, ) -> Any: """ Create a new bound logger for an arbitrary *logger*. Default values for *processors*, *wrapper_class*, and *context_class* can be set using `configure`. If you set an attribute here, `configure` calls have *no* effect for the *respective* attribute. In other words: selective overwriting of the defaults while keeping some *is* possible. Args: initial_values: Values that are used to pre-populate your contexts. logger_factory_args: Values that are passed unmodified as ``*logger_factory_args`` to the logger factory if not `None`. Returns: A proxy that creates a correctly configured bound logger when necessary. See `configure` for the meaning of the rest of the arguments. .. versionadded:: 0.4.0 *logger_factory_args* """ return BoundLoggerLazyProxy( logger, wrapper_class=wrapper_class, processors=processors, context_class=context_class, cache_logger_on_first_use=cache_logger_on_first_use, initial_values=initial_values, logger_factory_args=logger_factory_args, ) def configure( processors: Iterable[Processor] | None = None, wrapper_class: type[BindableLogger] | None = None, context_class: type[Context] | None = None, logger_factory: Callable[..., WrappedLogger] | None = None, cache_logger_on_first_use: bool | None = None, ) -> None: """ Configures the **global** defaults. They are used if `wrap_logger` or `get_logger` are called without arguments. Can be called several times, keeping an argument at `None` leaves it unchanged from the current setting. After calling for the first time, `is_configured` starts returning `True`. Use `reset_defaults` to undo your changes. Args: processors: The processor chain. See :doc:`processors` for details. wrapper_class: Class to use for wrapping loggers instead of `structlog.BoundLogger`. See `standard-library`, :doc:`twisted`, and `custom-wrappers`. context_class: Class to be used for internal context keeping. The default is a `dict` and since dictionaries are ordered as of Python 3.6, there's few reasons to change this option. logger_factory: Factory to be called to create a new logger that shall be wrapped. cache_logger_on_first_use: `wrap_logger` doesn't return an actual wrapped logger but a proxy that assembles one when it's first used. If this option is set to `True`, this assembled logger is cached. See `performance`. .. versionadded:: 0.3.0 *cache_logger_on_first_use* """ _CONFIG.is_configured = True if processors is not None: _CONFIG.default_processors = processors if wrapper_class is not None: _CONFIG.default_wrapper_class = wrapper_class if context_class is not None: _CONFIG.default_context_class = context_class if logger_factory is not None: _CONFIG.logger_factory = logger_factory if cache_logger_on_first_use is not None: _CONFIG.cache_logger_on_first_use = cache_logger_on_first_use def configure_once( processors: Iterable[Processor] | None = None, wrapper_class: type[BindableLogger] | None = None, context_class: type[Context] | None = None, logger_factory: Callable[..., WrappedLogger] | None = None, cache_logger_on_first_use: bool | None = None, ) -> None: """ Configures if structlog isn't configured yet. It does *not* matter whether it was configured using `configure` or `configure_once` before. Raises: RuntimeWarning: if repeated configuration is attempted. """ if not _CONFIG.is_configured: configure( processors=processors, wrapper_class=wrapper_class, context_class=context_class, logger_factory=logger_factory, cache_logger_on_first_use=cache_logger_on_first_use, ) else: warnings.warn( "Repeated configuration attempted.", RuntimeWarning, stacklevel=2 ) def reset_defaults() -> None: """ Resets global default values to builtin defaults. `is_configured` starts returning `False` afterwards. """ _CONFIG.is_configured = False _CONFIG.default_processors = _BUILTIN_DEFAULT_PROCESSORS[:] _CONFIG.default_wrapper_class = _BUILTIN_DEFAULT_WRAPPER_CLASS _CONFIG.default_context_class = _BUILTIN_DEFAULT_CONTEXT_CLASS _CONFIG.logger_factory = _BUILTIN_DEFAULT_LOGGER_FACTORY _CONFIG.cache_logger_on_first_use = _BUILTIN_CACHE_LOGGER_ON_FIRST_USE class BoundLoggerLazyProxy: """ Instantiates a bound logger on first usage. Takes both configuration and instantiation parameters into account. The only points where a bound logger changes state are ``bind()``, ``unbind()``, and ``new()`` and that return the actual ``BoundLogger``. If and only if configuration says so, that actual bound logger is cached on first usage. .. versionchanged:: 0.4.0 Added support for *logger_factory_args*. """ # fulfill BindableLogger protocol without carrying accidental state @property def _context(self) -> dict[str, str]: return self._initial_values def __init__( self, logger: WrappedLogger | None, wrapper_class: type[BindableLogger] | None = None, processors: Iterable[Processor] | None = None, context_class: type[Context] | None = None, cache_logger_on_first_use: bool | None = None, initial_values: dict[str, Any] | None = None, logger_factory_args: Any = None, ) -> None: self._logger = logger self._wrapper_class = wrapper_class self._processors = processors self._context_class = context_class self._cache_logger_on_first_use = cache_logger_on_first_use self._initial_values = initial_values or {} self._logger_factory_args = logger_factory_args or () def __repr__(self) -> str: return ( f"" ) def bind(self, **new_values: Any) -> BindableLogger: """ Assemble a new BoundLogger from arguments and configuration. """ if self._context_class: ctx = self._context_class(self._initial_values) else: ctx = _CONFIG.default_context_class(self._initial_values) _logger = self._logger if not _logger: _logger = _CONFIG.logger_factory(*self._logger_factory_args) if self._processors is None: procs = _CONFIG.default_processors else: procs = self._processors cls = self._wrapper_class or _CONFIG.default_wrapper_class # Looks like Protocols ignore definitions of __init__ so we have to # silence Mypy here. logger = cls( _logger, processors=procs, context=ctx, # type: ignore[call-arg] ) def finalized_bind(**new_values: Any) -> BindableLogger: """ Use cached assembled logger to bind potentially new values. """ if new_values: return logger.bind(**new_values) return logger if self._cache_logger_on_first_use is True or ( self._cache_logger_on_first_use is None and _CONFIG.cache_logger_on_first_use is True ): self.bind = finalized_bind # type: ignore[method-assign] return finalized_bind(**new_values) def unbind(self, *keys: str) -> BindableLogger: """ Same as bind, except unbind *keys* first. In our case that could be only initial values. """ return self.bind().unbind(*keys) def try_unbind(self, *keys: str) -> BindableLogger: return self.bind().try_unbind(*keys) def new(self, **new_values: Any) -> BindableLogger: """ Clear context, then bind. """ if self._context_class: self._context_class().clear() else: _CONFIG.default_context_class().clear() return self.bind(**new_values) def __getattr__(self, name: str) -> Any: """ If a logging method if called on a lazy proxy, we have to create an ephemeral BoundLogger first. """ if name == "__isabstractmethod__": raise AttributeError bl = self.bind() return getattr(bl, name) def __getstate__(self) -> dict[str, Any]: """ Our __getattr__ magic makes this necessary. """ return self.__dict__ def __setstate__(self, state: dict[str, Any]) -> None: """ Our __getattr__ magic makes this necessary. """ for k, v in state.items(): setattr(self, k, v) structlog-25.5.0/src/structlog/_frames.py0000644000000000000000000000452115077624341015375 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. from __future__ import annotations import sys import traceback from io import StringIO from types import FrameType from typing import Callable from .contextvars import _ASYNC_CALLING_STACK from .typing import ExcInfo def _format_exception(exc_info: ExcInfo) -> str: """ Prettyprint an `exc_info` tuple. Shamelessly stolen from stdlib's logging module. """ sio = StringIO() traceback.print_exception(exc_info[0], exc_info[1], exc_info[2], None, sio) s = sio.getvalue() sio.close() if s[-1:] == "\n": s = s[:-1] return s def _find_first_app_frame_and_name( additional_ignores: list[str] | None = None, *, stacklevel: int | None = None, _getframe: Callable[[], FrameType] = sys._getframe, ) -> tuple[FrameType, str]: """ Remove all intra-structlog calls and return the relevant app frame. Args: additional_ignores: Additional names with which the first frame must not start. stacklevel: After getting out of structlog, skip this many frames. _getframe: Callable to find current frame. Only for testing to avoid monkeypatching of sys._getframe. Returns: tuple of (frame, name) """ ignores = ("structlog", *tuple(additional_ignores or ())) f = _ASYNC_CALLING_STACK.get(_getframe()) name = f.f_globals.get("__name__") or "?" while name.startswith(ignores): if f.f_back is None: name = "?" break f = f.f_back name = f.f_globals.get("__name__") or "?" if stacklevel is not None: for _ in range(stacklevel): if f.f_back is None: break f = f.f_back name = f.f_globals.get("__name__") or "?" return f, name def _format_stack(frame: FrameType) -> str: """ Pretty-print the stack of *frame* like logging would. """ sio = StringIO() sio.write("Stack (most recent call last):\n") traceback.print_stack(frame, file=sio) sinfo = sio.getvalue() if sinfo[-1] == "\n": sinfo = sinfo[:-1] sio.close() return sinfo structlog-25.5.0/src/structlog/_generic.py0000644000000000000000000000314415077624341015534 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Generic bound logger that can wrap anything. """ from __future__ import annotations from functools import partial from typing import Any from structlog._base import BoundLoggerBase class BoundLogger(BoundLoggerBase): """ A generic BoundLogger that can wrap anything. Every unknown method will be passed to the wrapped *logger*. If that's too much magic for you, try `structlog.stdlib.BoundLogger` or `structlog.twisted.BoundLogger` which also take advantage of knowing the wrapped class which generally results in better performance. Not intended to be instantiated by yourself. See :func:`~structlog.wrap_logger` and :func:`~structlog.get_logger`. """ def __getattr__(self, method_name: str) -> Any: """ If not done so yet, wrap the desired logger method & cache the result. """ if method_name == "__deepcopy__": return None wrapped = partial(self._proxy_to_logger, method_name) setattr(self, method_name, wrapped) return wrapped def __getstate__(self) -> dict[str, Any]: """ Our __getattr__ magic makes this necessary. """ return self.__dict__ def __setstate__(self, state: dict[str, Any]) -> None: """ Our __getattr__ magic makes this necessary. """ for k, v in state.items(): setattr(self, k, v) structlog-25.5.0/src/structlog/_greenlets.py0000644000000000000000000000226615077624341016114 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ greenlet-specific code that pretends to be a `threading.local`. Fails to import if not running under greenlet. """ from __future__ import annotations from typing import Any from weakref import WeakKeyDictionary from greenlet import getcurrent class GreenThreadLocal: """ threading.local() replacement for greenlets. """ def __init__(self) -> None: self.__dict__["_weakdict"] = WeakKeyDictionary() def __getattr__(self, name: str) -> Any: key = getcurrent() try: return self._weakdict[key][name] except KeyError: raise AttributeError(name) from None def __setattr__(self, name: str, val: Any) -> None: key = getcurrent() self._weakdict.setdefault(key, {})[name] = val def __delattr__(self, name: str) -> None: key = getcurrent() try: del self._weakdict[key][name] except KeyError: raise AttributeError(name) from None structlog-25.5.0/src/structlog/_log_levels.py0000644000000000000000000000366515077624341016263 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Extracted log level data used by both stdlib and native log level filters. """ from __future__ import annotations from typing import Any from .typing import EventDict # Adapted from the stdlib CRITICAL = 50 FATAL = CRITICAL ERROR = 40 WARNING = 30 WARN = WARNING INFO = 20 DEBUG = 10 NOTSET = 0 NAME_TO_LEVEL = { "critical": CRITICAL, "exception": ERROR, "error": ERROR, "warn": WARNING, "warning": WARNING, "info": INFO, "debug": DEBUG, "notset": NOTSET, } LEVEL_TO_NAME = { v: k for k, v in NAME_TO_LEVEL.items() if k not in ("warn", "exception", "notset") } # Keep around for backwards-compatability in case someone imported them. _LEVEL_TO_NAME = LEVEL_TO_NAME _NAME_TO_LEVEL = NAME_TO_LEVEL def map_method_name(method_name: str) -> str: # warn is just a deprecated alias in the stdlib. if method_name == "warn": return "warning" # Calling exception("") is the same as error("", exc_info=True) if method_name == "exception": return "error" return method_name def add_log_level( logger: Any, method_name: str, event_dict: EventDict ) -> EventDict: """ Add the log level to the event dict under the ``level`` key. Since that's just the log method name, this processor works with non-stdlib logging as well. Therefore it's importable both from `structlog.processors` as well as from `structlog.stdlib`. .. versionadded:: 15.0.0 .. versionchanged:: 20.2.0 Importable from `structlog.processors` (additionally to `structlog.stdlib`). .. versionchanged:: 24.1.0 Added mapping from "exception" to "error" """ event_dict["level"] = map_method_name(method_name) return event_dict structlog-25.5.0/src/structlog/_native.py0000644000000000000000000002062215077624341015406 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ structlog's native high-performance loggers. """ from __future__ import annotations import asyncio import collections import contextvars import sys from typing import Any, Callable from ._base import BoundLoggerBase from ._log_levels import ( CRITICAL, DEBUG, ERROR, INFO, LEVEL_TO_NAME, NAME_TO_LEVEL, NOTSET, WARNING, ) from .contextvars import _ASYNC_CALLING_STACK from .typing import FilteringBoundLogger def _nop(self: Any, event: str, *args: Any, **kw: Any) -> Any: return None async def _anop(self: Any, event: str, *args: Any, **kw: Any) -> Any: return None def exception( self: FilteringBoundLogger, event: str, *args: Any, **kw: Any ) -> Any: kw.setdefault("exc_info", True) return self.error(event, *args, **kw) async def aexception( self: FilteringBoundLogger, event: str, *args: Any, **kw: Any ) -> Any: """ .. versionchanged:: 23.3.0 Callsite parameters are now also collected under asyncio. """ # Exception info has to be extracted this early, because it is no longer # available once control is passed to the executor. if kw.get("exc_info", True) is True: kw["exc_info"] = sys.exc_info() scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back) # type: ignore[arg-type] ctx = contextvars.copy_context() try: runner = await asyncio.get_running_loop().run_in_executor( None, lambda: ctx.run(lambda: self.error(event, *args, **kw)), ) finally: _ASYNC_CALLING_STACK.reset(scs_token) return runner def make_filtering_bound_logger( min_level: int | str, ) -> type[FilteringBoundLogger]: """ Create a new `FilteringBoundLogger` that only logs *min_level* or higher. The logger is optimized such that log levels below *min_level* only consist of a ``return None``. All familiar log methods are present, with async variants of each that are prefixed by an ``a``. Therefore, the async version of ``log.info("hello")`` is ``await log.ainfo("hello")``. Additionally it has a ``log(self, level: int, **kw: Any)`` method to mirror `logging.Logger.log` and `structlog.stdlib.BoundLogger.log`. Compared to using *structlog*'s standard library integration and the `structlog.stdlib.filter_by_level` processor: - It's faster because once the logger is built at program start; it's a static class. - For the same reason you can't change the log level once configured. Use the dynamic approach of `standard-library` instead, if you need this feature. - You *can* have (much) more fine-grained filtering by :ref:`writing a simple processor `. Args: min_level: The log level as an integer. You can use the constants from `logging` like ``logging.INFO`` or pass the values directly. See `this table from the logging docs `_ for possible values. If you pass a string, it must be one of: ``critical``, ``error``, ``warning``, ``info``, ``debug``, ``notset`` (upper/lower case doesn't matter). .. versionadded:: 20.2.0 .. versionchanged:: 21.1.0 The returned loggers are now pickleable. .. versionadded:: 20.1.0 The ``log()`` method. .. versionadded:: 22.2.0 Async variants ``alog()``, ``adebug()``, ``ainfo()``, and so forth. .. versionchanged:: 25.1.0 *min_level* can now be a string. """ if isinstance(min_level, str): min_level = NAME_TO_LEVEL[min_level.lower()] return LEVEL_TO_FILTERING_LOGGER[min_level] def _maybe_interpolate(event: str, args: tuple[Any, ...]) -> str: """ Interpolate the event string with the given arguments. If there's exactly one argument and it's a mapping, use it for dict-based interpolation. Otherwise, use the arguments for positional interpolation. """ if not args: return event if ( len(args) == 1 and isinstance(args[0], collections.abc.Mapping) and args[0] ): return event % args[0] return event % args def _make_filtering_bound_logger(min_level: int) -> type[FilteringBoundLogger]: """ Create a new `FilteringBoundLogger` that only logs *min_level* or higher. The logger is optimized such that log levels below *min_level* only consist of a ``return None``. """ def make_method( level: int, ) -> tuple[Callable[..., Any], Callable[..., Any]]: if level < min_level: return _nop, _anop name = LEVEL_TO_NAME[level] def meth(self: Any, event: str, *args: Any, **kw: Any) -> Any: return self._proxy_to_logger( name, _maybe_interpolate(event, args), **kw ) async def ameth(self: Any, event: str, *args: Any, **kw: Any) -> Any: """ .. versionchanged:: 23.3.0 Callsite parameters are now also collected under asyncio. """ event = _maybe_interpolate(event, args) scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back) # type: ignore[arg-type] ctx = contextvars.copy_context() try: await asyncio.get_running_loop().run_in_executor( None, lambda: ctx.run( lambda: self._proxy_to_logger(name, event, **kw) ), ) finally: _ASYNC_CALLING_STACK.reset(scs_token) meth.__name__ = name ameth.__name__ = f"a{name}" return meth, ameth def log(self: Any, level: int, event: str, *args: Any, **kw: Any) -> Any: if level < min_level: return None name = LEVEL_TO_NAME[level] return self._proxy_to_logger( name, _maybe_interpolate(event, args), **kw ) async def alog( self: Any, level: int, event: str, *args: Any, **kw: Any ) -> Any: """ .. versionchanged:: 23.3.0 Callsite parameters are now also collected under asyncio. """ if level < min_level: return None name = LEVEL_TO_NAME[level] event = _maybe_interpolate(event, args) scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back) # type: ignore[arg-type] ctx = contextvars.copy_context() try: runner = await asyncio.get_running_loop().run_in_executor( None, lambda: ctx.run( lambda: self._proxy_to_logger(name, event, **kw) ), ) finally: _ASYNC_CALLING_STACK.reset(scs_token) return runner meths: dict[str, Callable[..., Any]] = {"log": log, "alog": alog} for lvl, name in LEVEL_TO_NAME.items(): meths[name], meths[f"a{name}"] = make_method(lvl) meths["exception"] = exception meths["aexception"] = aexception meths["fatal"] = meths["critical"] meths["afatal"] = meths["acritical"] meths["warn"] = meths["warning"] meths["awarn"] = meths["awarning"] meths["msg"] = meths["info"] meths["amsg"] = meths["ainfo"] # Introspection meths["is_enabled_for"] = lambda self, level: level >= min_level meths["get_effective_level"] = lambda self: min_level return type( f"BoundLoggerFilteringAt{LEVEL_TO_NAME.get(min_level, 'Notset').capitalize()}", (BoundLoggerBase,), meths, ) # Pre-create all possible filters to make them pickleable. BoundLoggerFilteringAtNotset = _make_filtering_bound_logger(NOTSET) BoundLoggerFilteringAtDebug = _make_filtering_bound_logger(DEBUG) BoundLoggerFilteringAtInfo = _make_filtering_bound_logger(INFO) BoundLoggerFilteringAtWarning = _make_filtering_bound_logger(WARNING) BoundLoggerFilteringAtError = _make_filtering_bound_logger(ERROR) BoundLoggerFilteringAtCritical = _make_filtering_bound_logger(CRITICAL) LEVEL_TO_FILTERING_LOGGER = { CRITICAL: BoundLoggerFilteringAtCritical, ERROR: BoundLoggerFilteringAtError, WARNING: BoundLoggerFilteringAtWarning, INFO: BoundLoggerFilteringAtInfo, DEBUG: BoundLoggerFilteringAtDebug, NOTSET: BoundLoggerFilteringAtNotset, } structlog-25.5.0/src/structlog/_output.py0000644000000000000000000002163315077624341015463 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Logger classes responsible for output. """ from __future__ import annotations import copy import sys import threading from pickle import PicklingError from sys import stderr, stdout from typing import IO, Any, BinaryIO, TextIO WRITE_LOCKS: dict[IO[Any], threading.Lock] = {} def _get_lock_for_file(file: IO[Any]) -> threading.Lock: lock = WRITE_LOCKS.get(file) if lock is None: lock = threading.Lock() WRITE_LOCKS[file] = lock return lock class PrintLogger: """ Print events into a file. Args: file: File to print to. (default: `sys.stdout`) >>> from structlog import PrintLogger >>> PrintLogger().info("hello") hello Useful if you follow `current logging best practices `. Also very useful for testing and examples since `logging` is finicky in doctests. .. versionchanged:: 22.1.0 The implementation has been switched to use `print` for better monkeypatchability. """ def __init__(self, file: TextIO | None = None): self._file = file or stdout self._lock = _get_lock_for_file(self._file) def __getstate__(self) -> str: """ Our __getattr__ magic makes this necessary. """ if self._file is stdout: return "stdout" if self._file is stderr: return "stderr" raise PicklingError( "Only PrintLoggers to sys.stdout and sys.stderr can be pickled." ) def __setstate__(self, state: Any) -> None: """ Our __getattr__ magic makes this necessary. """ if state == "stdout": self._file = stdout else: self._file = stderr self._lock = _get_lock_for_file(self._file) def __deepcopy__(self, memodict: dict[str, object]) -> PrintLogger: """ Create a new PrintLogger with the same attributes. Similar to pickling. """ if self._file not in (stdout, stderr): raise copy.error( "Only PrintLoggers to sys.stdout and sys.stderr " "can be deepcopied." ) newself = self.__class__(self._file) newself._lock = _get_lock_for_file(newself._file) return newself def __repr__(self) -> str: return f"" def msg(self, message: str) -> None: """ Print *message*. """ f = self._file if self._file is not stdout else None with self._lock: print(message, file=f, flush=True) log = debug = info = warn = warning = msg fatal = failure = err = error = critical = exception = msg class PrintLoggerFactory: r""" Produce `PrintLogger`\ s. To be used with `structlog.configure`\ 's ``logger_factory``. Args: file: File to print to. (default: `sys.stdout`) Positional arguments are silently ignored. .. versionadded:: 0.4.0 """ def __init__(self, file: TextIO | None = None): self._file = file def __call__(self, *args: Any) -> PrintLogger: return PrintLogger(self._file) class WriteLogger: """ Write events into a file. Args: file: File to print to. (default: `sys.stdout`) >>> from structlog import WriteLogger >>> WriteLogger().info("hello") hello Useful if you follow `current logging best practices `. Also very useful for testing and examples since `logging` is finicky in doctests. A little faster and a little less versatile than `structlog.PrintLogger`. .. versionadded:: 22.1.0 """ def __init__(self, file: TextIO | None = None): self._file = file or sys.stdout self._write = self._file.write self._flush = self._file.flush self._lock = _get_lock_for_file(self._file) def __getstate__(self) -> str: """ Our __getattr__ magic makes this necessary. """ if self._file is stdout: return "stdout" if self._file is stderr: return "stderr" raise PicklingError( "Only WriteLoggers to sys.stdout and sys.stderr can be pickled." ) def __setstate__(self, state: Any) -> None: """ Our __getattr__ magic makes this necessary. """ if state == "stdout": self._file = stdout else: self._file = stderr self._lock = _get_lock_for_file(self._file) def __deepcopy__(self, memodict: dict[str, object]) -> WriteLogger: """ Create a new WriteLogger with the same attributes. Similar to pickling. """ if self._file not in (sys.stdout, sys.stderr): raise copy.error( "Only WriteLoggers to sys.stdout and sys.stderr " "can be deepcopied." ) newself = self.__class__(self._file) newself._write = newself._file.write newself._flush = newself._file.flush newself._lock = _get_lock_for_file(newself._file) return newself def __repr__(self) -> str: return f"" def msg(self, message: str) -> None: """ Write and flush *message*. """ with self._lock: self._write(message + "\n") self._flush() log = debug = info = warn = warning = msg fatal = failure = err = error = critical = exception = msg class WriteLoggerFactory: r""" Produce `WriteLogger`\ s. To be used with `structlog.configure`\ 's ``logger_factory``. Args: file: File to print to. (default: `sys.stdout`) Positional arguments are silently ignored. .. versionadded:: 22.1.0 """ def __init__(self, file: TextIO | None = None): self._file = file def __call__(self, *args: Any) -> WriteLogger: return WriteLogger(self._file) class BytesLogger: r""" Writes bytes into a file. Args: file: File to print to. (default: `sys.stdout`\ ``.buffer``) Useful if you follow `current logging best practices ` together with a formatter that returns bytes (e.g. `orjson `_). .. versionadded:: 20.2.0 """ __slots__ = ("_file", "_flush", "_lock", "_write") def __init__(self, file: BinaryIO | None = None): self._file = file or sys.stdout.buffer self._write = self._file.write self._flush = self._file.flush self._lock = _get_lock_for_file(self._file) def __getstate__(self) -> str: """ Our __getattr__ magic makes this necessary. """ if self._file is sys.stdout.buffer: return "stdout" if self._file is sys.stderr.buffer: return "stderr" raise PicklingError( "Only BytesLoggers to sys.stdout and sys.stderr can be pickled." ) def __setstate__(self, state: Any) -> None: """ Our __getattr__ magic makes this necessary. """ if state == "stdout": self._file = sys.stdout.buffer else: self._file = sys.stderr.buffer self._write = self._file.write self._flush = self._file.flush self._lock = _get_lock_for_file(self._file) def __deepcopy__(self, memodict: dict[str, object]) -> BytesLogger: """ Create a new BytesLogger with the same attributes. Similar to pickling. """ if self._file not in (sys.stdout.buffer, sys.stderr.buffer): raise copy.error( "Only BytesLoggers to sys.stdout and sys.stderr " "can be deepcopied." ) newself = self.__class__(self._file) newself._write = newself._file.write newself._flush = newself._file.flush newself._lock = _get_lock_for_file(newself._file) return newself def __repr__(self) -> str: return f"" def msg(self, message: bytes) -> None: """ Write *message*. """ with self._lock: self._write(message + b"\n") self._flush() log = debug = info = warn = warning = msg fatal = failure = err = error = critical = exception = msg class BytesLoggerFactory: r""" Produce `BytesLogger`\ s. To be used with `structlog.configure`\ 's ``logger_factory``. Args: file: File to print to. (default: `sys.stdout`\ ``.buffer``) Positional arguments are silently ignored. .. versionadded:: 20.2.0 """ __slots__ = ("_file",) def __init__(self, file: BinaryIO | None = None): self._file = file def __call__(self, *args: Any) -> BytesLogger: return BytesLogger(self._file) structlog-25.5.0/src/structlog/_utils.py0000644000000000000000000000164515077624341015264 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Generic utilities. """ from __future__ import annotations import sys from contextlib import suppress from typing import Any def get_processname() -> str: # based on code from # https://github.com/python/cpython/blob/313f92a57bc3887026ec16adb536bb2b7580ce47/Lib/logging/__init__.py#L342-L352 processname = "n/a" mp: Any = sys.modules.get("multiprocessing") if mp is not None: # Errors may occur if multiprocessing has not finished loading # yet - e.g. if a custom import hook causes third-party code # to run when multiprocessing calls import. with suppress(Exception): processname = mp.current_process().name return processname structlog-25.5.0/src/structlog/contextvars.py0000644000000000000000000001237315077624341016345 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Primitives to deal with a concurrency supporting context, as introduced in Python 3.7 as :mod:`contextvars`. .. versionadded:: 20.1.0 .. versionchanged:: 21.1.0 Reimplemented without using a single dict as context carrier for improved isolation. Every key-value pair is a separate `contextvars.ContextVar` now. .. versionchanged:: 23.3.0 Callsite parameters are now also collected under asyncio. See :doc:`contextvars`. """ from __future__ import annotations import contextlib import contextvars from types import FrameType from typing import Any, Generator, Mapping import structlog from .typing import BindableLogger, EventDict, WrappedLogger STRUCTLOG_KEY_PREFIX = "structlog_" STRUCTLOG_KEY_PREFIX_LEN = len(STRUCTLOG_KEY_PREFIX) _ASYNC_CALLING_STACK: contextvars.ContextVar[FrameType] = ( contextvars.ContextVar("_ASYNC_CALLING_STACK") ) # For proper isolation, we have to use a dict of ContextVars instead of a # single ContextVar with a dict. # See https://github.com/hynek/structlog/pull/302 for details. _CONTEXT_VARS: dict[str, contextvars.ContextVar[Any]] = {} def get_contextvars() -> dict[str, Any]: """ Return a copy of the *structlog*-specific context-local context. .. versionadded:: 21.2.0 """ rv = {} ctx = contextvars.copy_context() for k in ctx: if k.name.startswith(STRUCTLOG_KEY_PREFIX) and ctx[k] is not Ellipsis: rv[k.name[STRUCTLOG_KEY_PREFIX_LEN:]] = ctx[k] return rv def get_merged_contextvars(bound_logger: BindableLogger) -> dict[str, Any]: """ Return a copy of the current context-local context merged with the context from *bound_logger*. .. versionadded:: 21.2.0 """ ctx = get_contextvars() ctx.update(structlog.get_context(bound_logger)) return ctx def merge_contextvars( logger: WrappedLogger, method_name: str, event_dict: EventDict ) -> EventDict: """ A processor that merges in a global (context-local) context. Use this as your first processor in :func:`structlog.configure` to ensure context-local context is included in all log calls. .. versionadded:: 20.1.0 .. versionchanged:: 21.1.0 See toplevel note. """ ctx = contextvars.copy_context() for k in ctx: if k.name.startswith(STRUCTLOG_KEY_PREFIX) and ctx[k] is not Ellipsis: event_dict.setdefault(k.name[STRUCTLOG_KEY_PREFIX_LEN:], ctx[k]) return event_dict def clear_contextvars() -> None: """ Clear the context-local context. The typical use-case for this function is to invoke it early in request- handling code. .. versionadded:: 20.1.0 .. versionchanged:: 21.1.0 See toplevel note. """ ctx = contextvars.copy_context() for k in ctx: if k.name.startswith(STRUCTLOG_KEY_PREFIX): k.set(Ellipsis) def bind_contextvars(**kw: Any) -> Mapping[str, contextvars.Token[Any]]: r""" Put keys and values into the context-local context. Use this instead of :func:`~structlog.BoundLogger.bind` when you want some context to be global (context-local). Return the mapping of `contextvars.Token`\s resulting from setting the backing :class:`~contextvars.ContextVar`\s. Suitable for passing to :func:`reset_contextvars`. .. versionadded:: 20.1.0 .. versionchanged:: 21.1.0 Return the `contextvars.Token` mapping rather than None. See also the toplevel note. """ rv = {} for k, v in kw.items(): structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}" try: var = _CONTEXT_VARS[structlog_k] except KeyError: var = contextvars.ContextVar(structlog_k, default=Ellipsis) _CONTEXT_VARS[structlog_k] = var rv[k] = var.set(v) return rv def reset_contextvars(**kw: contextvars.Token[Any]) -> None: r""" Reset contextvars corresponding to the given Tokens. .. versionadded:: 21.1.0 """ for k, v in kw.items(): structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}" var = _CONTEXT_VARS[structlog_k] var.reset(v) def unbind_contextvars(*keys: str) -> None: """ Remove *keys* from the context-local context if they are present. Use this instead of :func:`~structlog.BoundLogger.unbind` when you want to remove keys from a global (context-local) context. .. versionadded:: 20.1.0 .. versionchanged:: 21.1.0 See toplevel note. """ for k in keys: structlog_k = f"{STRUCTLOG_KEY_PREFIX}{k}" if structlog_k in _CONTEXT_VARS: _CONTEXT_VARS[structlog_k].set(Ellipsis) @contextlib.contextmanager def bound_contextvars(**kw: Any) -> Generator[None, None, None]: """ Bind *kw* to the current context-local context. Unbind or restore *kw* afterwards. Do **not** affect other keys. Can be used as a context manager or decorator. .. versionadded:: 21.4.0 """ context = get_contextvars() saved = {k: context[k] for k in context.keys() & kw.keys()} bind_contextvars(**kw) try: yield finally: unbind_contextvars(*kw.keys()) bind_contextvars(**saved) structlog-25.5.0/src/structlog/dev.py0000644000000000000000000010514115077624341014537 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Helpers that make development with *structlog* more pleasant. See also the narrative documentation in `console-output`. """ from __future__ import annotations import sys import warnings from dataclasses import dataclass from io import StringIO from types import ModuleType from typing import ( Any, Callable, Literal, Protocol, Sequence, TextIO, cast, ) from ._frames import _format_exception from .exceptions import ( MultipleConsoleRenderersConfiguredError, NoConsoleRendererConfiguredError, ) from .processors import _figure_out_exc_info from .typing import EventDict, ExceptionRenderer, ExcInfo, WrappedLogger try: import colorama except ImportError: colorama = None try: import better_exceptions except ImportError: better_exceptions = None try: import rich from rich.console import Console from rich.traceback import Traceback except ImportError: rich = None # type: ignore[assignment] __all__ = [ "ConsoleRenderer", "RichTracebackFormatter", "better_traceback", "plain_traceback", "rich_traceback", ] _IS_WINDOWS = sys.platform == "win32" _MISSING = "{who} requires the {package} package installed. " _EVENT_WIDTH = 30 # pad the event name to so many characters if _IS_WINDOWS: # pragma: no cover def _init_terminal(who: str, force_colors: bool) -> None: """ Initialize colorama on Windows systems for colorful console output. Args: who: The name of the caller for error messages. force_colors: Force colorful output even in non-interactive environments. Raises: SystemError: When colorama is not installed. """ # On Windows, we can't do colorful output without colorama. if colorama is None: raise SystemError( _MISSING.format( who=who + " with `colors=True` on Windows", package="colorama", ) ) # Colorama must be init'd on Windows, but must NOT be # init'd on other OSes, because it can break colors. if force_colors: colorama.deinit() colorama.init(strip=False) else: colorama.init() else: def _init_terminal(who: str, force_colors: bool) -> None: """ Currently, nothing to be done on non-Windows systems. """ def _pad(s: str, length: int) -> str: """ Pads *s* to length *length*. """ missing = length - len(s) return s + " " * (max(0, missing)) if colorama is not None: RESET_ALL = colorama.Style.RESET_ALL BRIGHT = colorama.Style.BRIGHT DIM = colorama.Style.DIM RED = colorama.Fore.RED BLUE = colorama.Fore.BLUE CYAN = colorama.Fore.CYAN MAGENTA = colorama.Fore.MAGENTA YELLOW = colorama.Fore.YELLOW GREEN = colorama.Fore.GREEN RED_BACK = colorama.Back.RED else: # These are the same values as the Colorama color codes. Redefining them # here allows users to specify that they want color without having to # install Colorama, which is only supposed to be necessary in Windows. RESET_ALL = "\033[0m" BRIGHT = "\033[1m" DIM = "\033[2m" RED = "\033[31m" BLUE = "\033[34m" CYAN = "\033[36m" MAGENTA = "\033[35m" YELLOW = "\033[33m" GREEN = "\033[32m" RED_BACK = "\033[41m" # On Windows, colors are only available if Colorama is installed. _has_colors = not _IS_WINDOWS or colorama is not None # Prevent breakage of packages that used the old name of the variable. _use_colors = _has_colors @dataclass(frozen=True) class ColumnStyles: """ Column styles settings for console rendering. These are console ANSI codes that are printed before the respective fields. This allows for a certain amount of customization if you don't want to configure your columns. .. versionadded:: 25.5.0 It was handled by private structures before. """ reset: str bright: str level_critical: str level_exception: str level_error: str level_warn: str level_info: str level_debug: str level_notset: str timestamp: str logger_name: str kv_key: str kv_value: str _colorful_styles = ColumnStyles( reset=RESET_ALL, bright=BRIGHT, level_critical=RED, level_exception=RED, level_error=RED, level_warn=YELLOW, level_info=GREEN, level_debug=GREEN, level_notset=RED_BACK, timestamp=DIM, logger_name=BLUE, kv_key=CYAN, kv_value=MAGENTA, ) _plain_styles = ColumnStyles( reset="", bright="", level_critical="", level_exception="", level_error="", level_warn="", level_info="", level_debug="", level_notset="", timestamp="", logger_name="", kv_key="", kv_value="", ) # Backward compatibility aliases _ColorfulStyles = _colorful_styles _PlainStyles = _plain_styles class ColumnFormatter(Protocol): """ :class:`~typing.Protocol` for column formatters. See `KeyValueColumnFormatter` and `LogLevelColumnFormatter` for examples. .. versionadded:: 23.3.0 """ def __call__(self, key: str, value: object) -> str: """ Format *value* for *key*. This method is responsible for formatting, *key*, the ``=``, and the *value*. That means that it can use any string instead of the ``=`` and it can leave out both the *key* or the *value*. If it returns an empty string, the column is omitted completely. """ @dataclass class Column: """ A column defines the way a key-value pair is formatted, and, by it's position to the *columns* argument of `ConsoleRenderer`, the order in which it is rendered. Args: key: The key for which this column is responsible. Leave empty to define it as the default formatter. formatter: The formatter for columns with *key*. .. versionadded:: 23.3.0 """ key: str formatter: ColumnFormatter @dataclass class KeyValueColumnFormatter: """ Format a key-value pair. Args: key_style: The style to apply to the key. If None, the key is omitted. value_style: The style to apply to the value. reset_style: The style to apply whenever a style is no longer needed. value_repr: A callable that returns the string representation of the value. width: The width to pad the value to. If 0, no padding is done. prefix: A string to prepend to the formatted key-value pair. May contain styles. postfix: A string to append to the formatted key-value pair. May contain styles. .. versionadded:: 23.3.0 """ key_style: str | None value_style: str reset_style: str value_repr: Callable[[object], str] width: int = 0 prefix: str = "" postfix: str = "" def __call__(self, key: str, value: object) -> str: sio = StringIO() if self.prefix: sio.write(self.prefix) sio.write(self.reset_style) if self.key_style is not None: sio.write(self.key_style) sio.write(key) sio.write(self.reset_style) sio.write("=") sio.write(self.value_style) sio.write(_pad(self.value_repr(value), self.width)) sio.write(self.reset_style) if self.postfix: sio.write(self.postfix) sio.write(self.reset_style) return sio.getvalue() class LogLevelColumnFormatter: """ Format a log level according to *level_styles*. The width is padded to the longest level name (if *level_styles* is passed -- otherwise there's no way to know the lengths of all levels). Args: level_styles: A dictionary of level names to styles that are applied to it. If None, the level is formatted as a plain ``[level]``. reset_style: What to use to reset the style after the level name. Ignored if if *level_styles* is None. width: The width to pad the level to. If 0, no padding is done. .. versionadded:: 23.3.0 .. versionadded:: 24.2.0 *width* """ level_styles: dict[str, str] | None reset_style: str width: int def __init__( self, level_styles: dict[str, str], reset_style: str, width: int | None = None, ) -> None: self.level_styles = level_styles if level_styles: self.width = ( 0 if width == 0 else len(max(self.level_styles.keys(), key=lambda e: len(e))) ) self.reset_style = reset_style else: self.width = 0 self.reset_style = "" def __call__(self, key: str, value: object) -> str: level = cast(str, value) style = ( "" if self.level_styles is None else self.level_styles.get(level, "") ) return f"[{style}{_pad(level, self.width)}{self.reset_style}]" _NOTHING = object() def plain_traceback(sio: TextIO, exc_info: ExcInfo) -> None: """ "Pretty"-print *exc_info* to *sio* using our own plain formatter. To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument. Used by default if neither Rich nor *better-exceptions* are present. .. versionadded:: 21.2.0 """ sio.write("\n" + _format_exception(exc_info)) @dataclass class RichTracebackFormatter: """ A Rich traceback renderer with the given options. Pass an instance as `ConsoleRenderer`'s ``exception_formatter`` argument. See :class:`rich.traceback.Traceback` for details on the arguments. If *width* is `None`, the terminal width is used. If the width can't be determined, fall back to 80. .. versionadded:: 23.2.0 .. versionchanged:: 25.4.0 Default *width* is ``None`` to have full width and reflow support. Passing ``-1`` as width is deprecated, use ``None`` instead. *word_wrap* is now True by default. .. versionadded:: 25.4.0 *code_width* """ color_system: Literal[ "auto", "standard", "256", "truecolor", "windows" ] = "truecolor" show_locals: bool = True max_frames: int = 100 theme: str | None = None word_wrap: bool = True extra_lines: int = 3 width: int | None = None code_width: int | None = 88 indent_guides: bool = True locals_max_length: int = 10 locals_max_string: int = 80 locals_hide_dunder: bool = True locals_hide_sunder: bool = False suppress: Sequence[str | ModuleType] = () def __call__(self, sio: TextIO, exc_info: ExcInfo) -> None: if self.width == -1: warnings.warn( "Use None to use the terminal width instead of -1.", DeprecationWarning, stacklevel=2, ) self.width = None sio.write("\n") console = Console( file=sio, color_system=self.color_system, width=self.width ) tb = Traceback.from_exception( *exc_info, show_locals=self.show_locals, max_frames=self.max_frames, theme=self.theme, word_wrap=self.word_wrap, extra_lines=self.extra_lines, width=self.width, indent_guides=self.indent_guides, locals_max_length=self.locals_max_length, locals_max_string=self.locals_max_string, locals_hide_dunder=self.locals_hide_dunder, locals_hide_sunder=self.locals_hide_sunder, suppress=self.suppress, ) if hasattr(tb, "code_width"): # `code_width` requires `rich>=13.8.0` tb.code_width = self.code_width console.print(tb) if rich is None: def rich_traceback(*args, **kw): raise ModuleNotFoundError( "RichTracebackFormatter requires Rich to be installed.", name="rich", ) else: rich_traceback = RichTracebackFormatter() """ Pretty-print *exc_info* to *sio* using the Rich package. To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument. This is a `RichTracebackFormatter` with default arguments and used by default if Rich is installed. .. versionadded:: 21.2.0 """ def better_traceback(sio: TextIO, exc_info: ExcInfo) -> None: """ Pretty-print *exc_info* to *sio* using the *better-exceptions* package. To be passed into `ConsoleRenderer`'s ``exception_formatter`` argument. Used by default if *better-exceptions* is installed and Rich is absent. .. versionadded:: 21.2.0 """ sio.write("\n" + "".join(better_exceptions.format_exception(*exc_info))) if rich is not None: default_exception_formatter = rich_traceback elif better_exceptions is not None: default_exception_formatter = better_traceback else: default_exception_formatter = plain_traceback class ConsoleRenderer: r""" Render ``event_dict`` nicely aligned, possibly in colors, and ordered. If ``event_dict`` contains a true-ish ``exc_info`` key, it will be rendered *after* the log line. If Rich_ or better-exceptions_ are present, in colors and with extra context. Tip: Since `ConsoleRenderer` is mainly a development helper, it is less strict about immutability than the rest of *structlog* for better ergonomics. Notably, the currently active instance can be obtained by calling `ConsoleRenderer.get_active()` and it offers properties to configure its behavior after instantiation. Args: columns: A list of `Column` objects defining both the order and format of the key-value pairs in the output. If passed, most other arguments become meaningless. **Must** contain a column with ``key=''`` that defines the default formatter. .. seealso:: `columns-config` pad_event_to: Pad the event to this many characters. Ignored if *columns* are passed. colors: Use colors for a nicer output. `True` by default. On Windows only if Colorama_ is installed. Ignored if *columns* are passed. force_colors: Force colors even for non-tty destinations. Use this option if your logs are stored in a file that is meant to be streamed to the console. Only meaningful on Windows. Ignored if *columns* are passed. repr_native_str: When `True`, `repr` is also applied to ``str``\ s. The ``event`` key is *never* `repr` -ed. Ignored if *columns* are passed. level_styles: When present, use these styles for colors. This must be a dict from level names (strings) to terminal sequences (for example, Colorama) styles. The default can be obtained by calling `ConsoleRenderer.get_default_level_styles`. Ignored when *columns* are passed. exception_formatter: A callable to render ``exc_infos``. If Rich_ or better-exceptions_ are installed, they are used for pretty-printing by default (rich_ taking precedence). You can also manually set it to `plain_traceback`, `better_traceback`, an instance of `RichTracebackFormatter` like `rich_traceback`, or implement your own. sort_keys: Whether to sort keys when formatting. `True` by default. Ignored if *columns* are passed. event_key: The key to look for the main log message. Needed when you rename it e.g. using `structlog.processors.EventRenamer`. Ignored if *columns* are passed. timestamp_key: The key to look for timestamp of the log message. Needed when you rename it e.g. using `structlog.processors.EventRenamer`. Ignored if *columns* are passed. pad_level: Whether to pad log level with blanks to the longest amongst all level label. Requires the Colorama_ package if *colors* is `True` **on Windows**. Raises: ValueError: If there's not exactly one default column formatter. .. _Colorama: https://pypi.org/project/colorama/ .. _better-exceptions: https://pypi.org/project/better-exceptions/ .. _Rich: https://pypi.org/project/rich/ .. versionadded:: 16.0.0 .. versionadded:: 16.1.0 *colors* .. versionadded:: 17.1.0 *repr_native_str* .. versionadded:: 18.1.0 *force_colors* .. versionadded:: 18.1.0 *level_styles* .. versionchanged:: 19.2.0 Colorama now initializes lazily to avoid unwanted initializations as ``ConsoleRenderer`` is used by default. .. versionchanged:: 19.2.0 Can be pickled now. .. versionchanged:: 20.1.0 Colorama does not initialize lazily on Windows anymore because it breaks rendering. .. versionchanged:: 21.1.0 It is additionally possible to set the logger name using the ``logger_name`` key in the ``event_dict``. .. versionadded:: 21.2.0 *exception_formatter* .. versionchanged:: 21.2.0 `ConsoleRenderer` now handles the ``exc_info`` event dict key itself. Do **not** use the `structlog.processors.format_exc_info` processor together with `ConsoleRenderer` anymore! It will keep working, but you can't have customize exception formatting and a warning will be raised if you ask for it. .. versionchanged:: 21.2.0 The colors keyword now defaults to True on non-Windows systems, and either True or False in Windows depending on whether Colorama is installed. .. versionadded:: 21.3.0 *sort_keys* .. versionadded:: 22.1.0 *event_key* .. versionadded:: 23.2.0 *timestamp_key* .. versionadded:: 23.3.0 *columns* .. versionadded:: 24.2.0 *pad_level* """ _default_column_formatter: ColumnFormatter def __init__( self, pad_event_to: int = _EVENT_WIDTH, colors: bool = _has_colors, force_colors: bool = False, repr_native_str: bool = False, level_styles: dict[str, str] | None = None, exception_formatter: ExceptionRenderer = default_exception_formatter, sort_keys: bool = True, event_key: str = "event", timestamp_key: str = "timestamp", columns: list[Column] | None = None, pad_level: bool = True, pad_event: int | None = None, ): if pad_event is not None: if pad_event_to != _EVENT_WIDTH: raise ValueError( "Cannot set both `pad_event` and `pad_event_to`." ) warnings.warn( "The `pad_event` argument is deprecated. Use `pad_event_to` instead.", DeprecationWarning, stacklevel=2, ) pad_event_to = pad_event # Store all settings in case the user later switches from columns to # defaults. self.exception_formatter = exception_formatter self._sort_keys = sort_keys self._repr_native_str = repr_native_str self._styles = self.get_default_column_styles(colors, force_colors) self._colors = colors self._force_colors = force_colors self._level_styles = ( self.get_default_level_styles(colors) if level_styles is None else level_styles ) self._pad_event_to = pad_event_to self._timestamp_key = timestamp_key self._event_key = event_key self._pad_level = pad_level if columns is None: self._configure_columns() return self.columns = columns to_warn = [] def add_meaningless_arg(arg: str) -> None: to_warn.append( f"The `{arg}` argument is ignored when passing `columns`.", ) if pad_event_to != _EVENT_WIDTH: add_meaningless_arg("pad_event_to") if colors != _has_colors: add_meaningless_arg("colors") if force_colors is not False: add_meaningless_arg("force_colors") if repr_native_str is not False: add_meaningless_arg("repr_native_str") if level_styles is not None: add_meaningless_arg("level_styles") if event_key != "event": add_meaningless_arg("event_key") if timestamp_key != "timestamp": add_meaningless_arg("timestamp_key") for w in to_warn: warnings.warn(w, stacklevel=2) @classmethod def get_active(cls) -> ConsoleRenderer: """ If *structlog* is configured to use `ConsoleRenderer`, it's returned. It does not have to be the last processor. Raises: NoConsoleRendererConfiguredError: If no ConsoleRenderer is found in the current configuration. MultipleConsoleRenderersConfiguredError: If more than one is found in the current configuration. This is almost certainly a bug. .. versionadded:: 25.5.0 """ from ._config import get_config cr = None for p in get_config()["processors"]: if isinstance(p, ConsoleRenderer): if cr is not None: raise MultipleConsoleRenderersConfiguredError cr = p if cr is None: raise NoConsoleRendererConfiguredError return cr @classmethod def get_default_column_styles( cls, colors: bool, force_colors: bool = False ) -> ColumnStyles: """ Configure and return the appropriate styles class for console output. This method handles the setup of colorful or plain styles, including proper colorama initialization on Windows systems when colors are enabled. Args: colors: Whether to use colorful output styles. force_colors: Force colorful output even in non-interactive environments. Only relevant on Windows with colorama. Returns: The configured styles. Raises: SystemError: On Windows when colors=True but colorama is not installed. .. versionadded:: 25.5.0 """ if not colors: return _plain_styles _init_terminal(cls.__name__, force_colors) return _colorful_styles @staticmethod def get_default_level_styles(colors: bool = True) -> dict[str, str]: """ Get the default styles for log levels This is intended to be used with `ConsoleRenderer`'s ``level_styles`` parameter. For example, if you are adding custom levels in your home-grown :func:`~structlog.stdlib.add_log_level` you could do:: my_styles = ConsoleRenderer.get_default_level_styles() my_styles["EVERYTHING_IS_ON_FIRE"] = my_styles["critical"] renderer = ConsoleRenderer(level_styles=my_styles) Args: colors: Whether to use colorful styles. This must match the *colors* parameter to `ConsoleRenderer`. Default: `True`. """ styles: ColumnStyles styles = _colorful_styles if colors else _plain_styles return { "critical": styles.level_critical, "exception": styles.level_exception, "error": styles.level_error, "warn": styles.level_warn, "warning": styles.level_warn, "info": styles.level_info, "debug": styles.level_debug, "notset": styles.level_notset, } def _configure_columns(self) -> None: """ Re-configure self._columns and self._default_column_formatter according to our current settings. Overwrite existing columns settings, regardless of whether they were explicitly passed by the user or derived by us. """ level_to_color = self._level_styles.copy() for key in level_to_color: level_to_color[key] += self._styles.bright self._longest_level = len( max(level_to_color.keys(), key=lambda e: len(e)) ) self._default_column_formatter = KeyValueColumnFormatter( self._styles.kv_key, self._styles.kv_value, self._styles.reset, value_repr=self._repr, width=0, ) logger_name_formatter = KeyValueColumnFormatter( key_style=None, value_style=self._styles.bright + self._styles.logger_name, reset_style=self._styles.reset, value_repr=str, prefix="[", postfix="]", ) level_width = 0 if not self._pad_level else None self._columns = [ Column( self._timestamp_key, KeyValueColumnFormatter( key_style=None, value_style=self._styles.timestamp, reset_style=self._styles.reset, value_repr=str, ), ), Column( "level", LogLevelColumnFormatter( level_to_color, reset_style=self._styles.reset, width=level_width, ), ), Column( self._event_key, KeyValueColumnFormatter( key_style=None, value_style=self._styles.bright, reset_style=self._styles.reset, value_repr=str, width=self._pad_event_to, ), ), Column("logger", logger_name_formatter), Column("logger_name", logger_name_formatter), ] def _repr(self, val: Any) -> str: """ Determine representation of *val* depending on its type & self._repr_native_str. """ if self._repr_native_str is True: return repr(val) if isinstance(val, str): if set(val) & {" ", "\t", "=", "\r", "\n", '"', "'"}: return repr(val) return val return repr(val) def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> str: stack = event_dict.pop("stack", None) exc = event_dict.pop("exception", None) exc_info = event_dict.pop("exc_info", None) kvs = [ col.formatter(col.key, val) for col in self.columns if (val := event_dict.pop(col.key, _NOTHING)) is not _NOTHING ] + [ self._default_column_formatter(key, event_dict[key]) for key in (sorted(event_dict) if self._sort_keys else event_dict) ] sio = StringIO() sio.write((" ".join(kv for kv in kvs if kv)).rstrip(" ")) if stack is not None: sio.write("\n" + stack) if exc_info or exc is not None: sio.write("\n\n" + "=" * 79 + "\n") exc_info = _figure_out_exc_info(exc_info) if exc_info: self._exception_formatter(sio, exc_info) elif exc is not None: if self._exception_formatter is not plain_traceback: warnings.warn( "Remove `format_exc_info` from your processor chain " "if you want pretty exceptions.", stacklevel=2, ) sio.write("\n" + exc) return sio.getvalue() @property def exception_formatter(self) -> ExceptionRenderer: """ The exception formatter used by this console renderer. .. versionadded:: 25.5.0 """ return self._exception_formatter @exception_formatter.setter def exception_formatter(self, value: ExceptionRenderer) -> None: """ .. versionadded:: 25.5.0 """ self._exception_formatter = value @property def sort_keys(self) -> bool: """ Whether to sort keys when formatting. .. versionadded:: 25.5.0 """ return self._sort_keys @sort_keys.setter def sort_keys(self, value: bool) -> None: """ .. versionadded:: 25.5.0 """ # _sort_keys is a format-time setting, so we can just set it directly. self._sort_keys = value @property def columns(self) -> list[Column]: """ The columns configuration for this console renderer. Warning: Just like with passing *columns* argument, many of the other arguments you may have passed are ignored. Args: value: A list of `Column` objects defining both the order and format of the key-value pairs in the output. **Must** contain a column with ``key=''`` that defines the default formatter. Raises: ValueError: If there's not exactly one default column formatter. .. versionadded:: 25.5.0 """ return [Column("", self._default_column_formatter), *self._columns] @columns.setter def columns(self, value: list[Column]) -> None: """ .. versionadded:: 25.5.0 """ defaults = [col for col in value if col.key == ""] if not defaults: raise ValueError( "Must pass a default column formatter (a column with `key=''`)." ) if len(defaults) > 1: raise ValueError("Only one default column formatter allowed.") self._default_column_formatter = defaults[0].formatter self._columns = [col for col in value if col.key] @property def colors(self) -> bool: """ Whether to use colorful output styles. Setting this will update the renderer's styles immediately and reset level styles to the defaults according to the new color setting -- even if the color value is the same. .. versionadded:: 25.5.0 """ return self._colors @colors.setter def colors(self, value: bool) -> None: """ .. versionadded:: 25.5.0 """ self._colors = value self._styles = self.get_default_column_styles( value, self._force_colors ) self._level_styles = self.get_default_level_styles(value) self._configure_columns() @property def force_colors(self) -> bool: """ Force colorful output even in non-interactive environments. Setting this will update the renderer's styles immediately and reset level styles to the defaults according to the current color setting -- even if the value is the same. .. versionadded:: 25.5.0 """ return self._force_colors @force_colors.setter def force_colors(self, value: bool) -> None: """ .. versionadded:: 25.5.0 """ self._force_colors = value self._styles = self.get_default_column_styles(self._colors, value) self._level_styles = self.get_default_level_styles(self._colors) self._configure_columns() @property def level_styles(self) -> dict[str, str]: """ The level styles mapping for this console renderer. Setting this property will reset to defaults if set to None, otherwise it applies the provided mapping. In all cases, columns are rebuilt to reflect the change. .. versionadded:: 25.5.0 """ return self._level_styles @level_styles.setter def level_styles(self, value: dict[str, str] | None) -> None: """ .. versionadded:: 25.5.0 """ self._level_styles = ( self.get_default_level_styles(self._colors) if value is None else value ) self._configure_columns() @property def pad_level(self) -> bool: """ Whether to pad log level with blanks to the longest amongst all level labels. Setting this will rebuild columns to reflect the change. .. versionadded:: 25.5.0 """ return self._pad_level @pad_level.setter def pad_level(self, value: bool) -> None: """ .. versionadded:: 25.5.0 """ self._pad_level = value self._configure_columns() @property def pad_event_to(self) -> int: """ The number of characters to pad the event to. Setting this will rebuild columns to reflect the change. .. versionadded:: 25.5.0 """ return self._pad_event_to @pad_event_to.setter def pad_event_to(self, value: int) -> None: """ .. versionadded:: 25.5.0 """ self._pad_event_to = value self._configure_columns() @property def event_key(self) -> str: """ The key to look for the main log message. Setting this will rebuild columns to reflect the change. .. versionadded:: 25.5.0 """ return self._event_key @event_key.setter def event_key(self, value: str) -> None: """ .. versionadded:: 25.5.0 """ self._event_key = value self._configure_columns() @property def timestamp_key(self) -> str: """ The key to look for the timestamp of the log message. Setting this will rebuild columns to reflect the change. .. versionadded:: 25.5.0 """ return self._timestamp_key @timestamp_key.setter def timestamp_key(self, value: str) -> None: """ .. versionadded:: 25.5.0 """ self._timestamp_key = value self._configure_columns() @property def repr_native_str(self) -> bool: """ Whether native strings are passed through repr() in non-event values. .. versionadded:: 25.5.0 """ return self._repr_native_str @repr_native_str.setter def repr_native_str(self, value: bool) -> None: """ .. versionadded:: 25.5.0 """ self._repr_native_str = value _SENTINEL = object() def set_exc_info( logger: WrappedLogger, method_name: str, event_dict: EventDict ) -> EventDict: """ Set ``event_dict["exc_info"] = True`` if *method_name* is ``"exception"``. Do nothing if the name is different or ``exc_info`` is already set. .. versionadded:: 19.2.0 """ if ( method_name != "exception" or event_dict.get("exc_info", _SENTINEL) is not _SENTINEL ): return event_dict event_dict["exc_info"] = True return event_dict structlog-25.5.0/src/structlog/exceptions.py0000644000000000000000000000160315077624341016140 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Exceptions factored out to avoid import loops. """ from __future__ import annotations class DropEvent(BaseException): """ If raised by an processor, the event gets silently dropped. Derives from BaseException because it's technically not an error. """ class NoConsoleRendererConfiguredError(Exception): """ A user asked for the current `structlog.dev.ConsoleRenderer` but none is configured. .. versionadded:: 25.5.0 """ class MultipleConsoleRenderersConfiguredError(Exception): """ A user asked for the current `structlog.dev.ConsoleRenderer` and more than one is configured. .. versionadded:: 25.5.0 """ structlog-25.5.0/src/structlog/processors.py0000644000000000000000000007317515077624341016176 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Processors useful regardless of the logging framework. """ from __future__ import annotations import datetime import enum import json import logging import operator import os import sys import threading import time from types import FrameType, TracebackType from typing import ( Any, Callable, ClassVar, Collection, NamedTuple, Sequence, TextIO, cast, ) from ._frames import ( _find_first_app_frame_and_name, _format_exception, _format_stack, ) from ._log_levels import NAME_TO_LEVEL, add_log_level from ._utils import get_processname from .tracebacks import ExceptionDictTransformer from .typing import ( EventDict, ExceptionTransformer, ExcInfo, WrappedLogger, ) __all__ = [ "NAME_TO_LEVEL", # some people rely on it being here "CallsiteParameter", "CallsiteParameterAdder", "EventRenamer", "ExceptionPrettyPrinter", "JSONRenderer", "KeyValueRenderer", "LogfmtRenderer", "StackInfoRenderer", "TimeStamper", "UnicodeDecoder", "UnicodeEncoder", "add_log_level", "dict_tracebacks", "format_exc_info", ] class KeyValueRenderer: """ Render ``event_dict`` as a list of ``Key=repr(Value)`` pairs. Args: sort_keys: Whether to sort keys when formatting. key_order: List of keys that should be rendered in this exact order. Missing keys will be rendered as ``None``, extra keys depending on *sort_keys* and the dict class. drop_missing: When ``True``, extra keys in *key_order* will be dropped rather than rendered as ``None``. repr_native_str: When ``True``, :func:`repr()` is also applied to native strings. .. versionadded:: 0.2.0 *key_order* .. versionadded:: 16.1.0 *drop_missing* .. versionadded:: 17.1.0 *repr_native_str* """ def __init__( self, sort_keys: bool = False, key_order: Sequence[str] | None = None, drop_missing: bool = False, repr_native_str: bool = True, ): self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing) if repr_native_str is True: self._repr = repr else: def _repr(inst: Any) -> str: if isinstance(inst, str): return inst return repr(inst) self._repr = _repr def __call__( self, _: WrappedLogger, __: str, event_dict: EventDict ) -> str: return " ".join( k + "=" + self._repr(v) for k, v in self._ordered_items(event_dict) ) class LogfmtRenderer: """ Render ``event_dict`` using the logfmt_ format. .. _logfmt: https://brandur.org/logfmt Args: sort_keys: Whether to sort keys when formatting. key_order: List of keys that should be rendered in this exact order. Missing keys are rendered with empty values, extra keys depending on *sort_keys* and the dict class. drop_missing: When ``True``, extra keys in *key_order* will be dropped rather than rendered with empty values. bool_as_flag: When ``True``, render ``{"flag": True}`` as ``flag``, instead of ``flag=true``. ``{"flag": False}`` is always rendered as ``flag=false``. Raises: ValueError: If a key contains non-printable or whitespace characters. .. versionadded:: 21.5.0 """ def __init__( self, sort_keys: bool = False, key_order: Sequence[str] | None = None, drop_missing: bool = False, bool_as_flag: bool = True, ): self._ordered_items = _items_sorter(sort_keys, key_order, drop_missing) self.bool_as_flag = bool_as_flag def __call__( self, _: WrappedLogger, __: str, event_dict: EventDict ) -> str: elements: list[str] = [] for key, value in self._ordered_items(event_dict): if any(c <= " " for c in key): msg = f'Invalid key: "{key}"' raise ValueError(msg) if value is None: elements.append(f"{key}=") continue if isinstance(value, bool): if self.bool_as_flag and value: elements.append(f"{key}") continue value = "true" if value else "false" value = str(value) backslashes_need_escaping = ( " " in value or "=" in value or '"' in value ) if backslashes_need_escaping and "\\" in value: value = value.replace("\\", "\\\\") value = value.replace('"', '\\"').replace("\n", "\\n") if backslashes_need_escaping: value = f'"{value}"' elements.append(f"{key}={value}") return " ".join(elements) def _items_sorter( sort_keys: bool, key_order: Sequence[str] | None, drop_missing: bool, ) -> Callable[[EventDict], list[tuple[str, object]]]: """ Return a function to sort items from an ``event_dict``. See `KeyValueRenderer` for an explanation of the parameters. """ # Use an optimized version for each case. if key_order and sort_keys: def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]: items = [] for key in key_order: value = event_dict.pop(key, None) if value is not None or not drop_missing: items.append((key, value)) items += sorted(event_dict.items()) return items elif key_order: def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]: items = [] for key in key_order: value = event_dict.pop(key, None) if value is not None or not drop_missing: items.append((key, value)) items += event_dict.items() return items elif sort_keys: def ordered_items(event_dict: EventDict) -> list[tuple[str, Any]]: return sorted(event_dict.items()) else: ordered_items = operator.methodcaller( # type: ignore[assignment] "items" ) return ordered_items class UnicodeEncoder: """ Encode unicode values in ``event_dict``. Args: encoding: Encoding to encode to (default: ``"utf-8"``). errors: How to cope with encoding errors (default ``"backslashreplace"``). Just put it in the processor chain before the renderer. .. note:: Not very useful in a Python 3-only world. """ _encoding: str _errors: str def __init__( self, encoding: str = "utf-8", errors: str = "backslashreplace" ) -> None: self._encoding = encoding self._errors = errors def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: for key, value in event_dict.items(): if isinstance(value, str): event_dict[key] = value.encode(self._encoding, self._errors) return event_dict class UnicodeDecoder: """ Decode byte string values in ``event_dict``. Args: encoding: Encoding to decode from (default: ``"utf-8"``). errors: How to cope with encoding errors (default: ``"replace"``). Useful to prevent ``b"abc"`` being rendered as as ``'b"abc"'``. Just put it in the processor chain before the renderer. .. versionadded:: 15.4.0 """ _encoding: str _errors: str def __init__( self, encoding: str = "utf-8", errors: str = "replace" ) -> None: self._encoding = encoding self._errors = errors def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: for key, value in event_dict.items(): if isinstance(value, bytes): event_dict[key] = value.decode(self._encoding, self._errors) return event_dict class JSONRenderer: """ Render the ``event_dict`` using ``serializer(event_dict, **dumps_kw)``. Args: dumps_kw: Are passed unmodified to *serializer*. If *default* is passed, it will disable support for ``__structlog__``-based serialization. serializer: A :func:`json.dumps`-compatible callable that will be used to format the string. This can be used to use alternative JSON encoders (default: :func:`json.dumps`). .. seealso:: :doc:`performance` for examples. .. versionadded:: 0.2.0 Support for ``__structlog__`` serialization method. .. versionadded:: 15.4.0 *serializer* parameter. .. versionadded:: 18.2.0 Serializer's *default* parameter can be overwritten now. """ def __init__( self, serializer: Callable[..., str | bytes] = json.dumps, **dumps_kw: Any, ) -> None: dumps_kw.setdefault("default", _json_fallback_handler) self._dumps_kw = dumps_kw self._dumps = serializer def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> str | bytes: """ The return type of this depends on the return type of self._dumps. """ return self._dumps(event_dict, **self._dumps_kw) def _json_fallback_handler(obj: Any) -> Any: """ Serialize custom datatypes and pass the rest to __structlog__ & repr(). """ # circular imports :( from structlog.threadlocal import _ThreadLocalDictWrapper if isinstance(obj, _ThreadLocalDictWrapper): return obj._dict try: return obj.__structlog__() except AttributeError: return repr(obj) class ExceptionRenderer: """ Replace an ``exc_info`` field with an ``exception`` field which is rendered by *exception_formatter*. The contents of the ``exception`` field depends on the return value of the *exception_formatter* that is passed: - The default produces a formatted string via Python's built-in traceback formatting (this is :obj:`.format_exc_info`). - If you pass a :class:`~structlog.tracebacks.ExceptionDictTransformer`, it becomes a list of stack dicts that can be serialized to JSON. If *event_dict* contains the key ``exc_info``, there are three possible behaviors: 1. If the value is a tuple, render it into the key ``exception``. 2. If the value is an Exception render it into the key ``exception``. 3. If the value true but no tuple, obtain exc_info ourselves and render that. If there is no ``exc_info`` key, the *event_dict* is not touched. This behavior is analog to the one of the stdlib's logging. Args: exception_formatter: A callable that is used to format the exception from the ``exc_info`` field into the ``exception`` field. .. seealso:: :doc:`exceptions` for a broader explanation of *structlog*'s exception features. .. versionadded:: 22.1.0 """ def __init__( self, exception_formatter: ExceptionTransformer = _format_exception, ) -> None: self.format_exception = exception_formatter def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None)) if exc_info: event_dict["exception"] = self.format_exception(exc_info) return event_dict format_exc_info = ExceptionRenderer() """ Replace an ``exc_info`` field with an ``exception`` string field using Python's built-in traceback formatting. If *event_dict* contains the key ``exc_info``, there are three possible behaviors: 1. If the value is a tuple, render it into the key ``exception``. 2. If the value is an Exception render it into the key ``exception``. 3. If the value is true but no tuple, obtain exc_info ourselves and render that. If there is no ``exc_info`` key, the *event_dict* is not touched. This behavior is analog to the one of the stdlib's logging. .. seealso:: :doc:`exceptions` for a broader explanation of *structlog*'s exception features. """ dict_tracebacks = ExceptionRenderer(ExceptionDictTransformer()) """ Replace an ``exc_info`` field with an ``exception`` field containing structured tracebacks suitable for, e.g., JSON output. It is a shortcut for :class:`ExceptionRenderer` with a :class:`~structlog.tracebacks.ExceptionDictTransformer`. The treatment of the ``exc_info`` key is identical to `format_exc_info`. .. versionadded:: 22.1.0 .. seealso:: :doc:`exceptions` for a broader explanation of *structlog*'s exception features. """ class TimeStamper: """ Add a timestamp to ``event_dict``. Args: fmt: strftime format string, or ``"iso"`` for `ISO 8601 `_, or `None` for a `UNIX timestamp `_. utc: Whether timestamp should be in UTC or local time. key: Target key in *event_dict* for added timestamps. .. versionchanged:: 19.2.0 Can be pickled now. """ __slots__ = ("_stamper", "fmt", "key", "utc") def __init__( self, fmt: str | None = None, utc: bool = True, key: str = "timestamp", ) -> None: self.fmt, self.utc, self.key = fmt, utc, key self._stamper = _make_stamper(fmt, utc, key) def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: return self._stamper(event_dict) def __getstate__(self) -> dict[str, Any]: return {"fmt": self.fmt, "utc": self.utc, "key": self.key} def __setstate__(self, state: dict[str, Any]) -> None: self.fmt = state["fmt"] self.utc = state["utc"] self.key = state["key"] self._stamper = _make_stamper(**state) def _make_stamper( fmt: str | None, utc: bool, key: str ) -> Callable[[EventDict], EventDict]: """ Create a stamper function. """ if fmt is None and not utc: msg = "UNIX timestamps are always UTC." raise ValueError(msg) now: Callable[[], datetime.datetime] if utc: def now() -> datetime.datetime: return datetime.datetime.now(tz=datetime.timezone.utc) else: def now() -> datetime.datetime: # We don't need the TZ for our own formatting. We add it only for # user-defined formats later. return datetime.datetime.now() # noqa: DTZ005 if fmt is None: def stamper_unix(event_dict: EventDict) -> EventDict: event_dict[key] = time.time() return event_dict return stamper_unix if fmt.upper() == "ISO": def stamper_iso_local(event_dict: EventDict) -> EventDict: event_dict[key] = now().isoformat() return event_dict def stamper_iso_utc(event_dict: EventDict) -> EventDict: event_dict[key] = now().isoformat().replace("+00:00", "Z") return event_dict if utc: return stamper_iso_utc return stamper_iso_local def stamper_fmt_local(event_dict: EventDict) -> EventDict: event_dict[key] = now().astimezone().strftime(fmt) return event_dict def stamper_fmt_utc(event_dict: EventDict) -> EventDict: event_dict[key] = now().strftime(fmt) return event_dict if utc: return stamper_fmt_utc return stamper_fmt_local class MaybeTimeStamper: """ A timestamper that only adds a timestamp if there is none. This allows you to overwrite the ``timestamp`` key in the event dict for example when the event is coming from another system. It takes the same arguments as `TimeStamper`. .. versionadded:: 23.2.0 """ __slots__ = ("stamper",) def __init__( self, fmt: str | None = None, utc: bool = True, key: str = "timestamp", ): self.stamper = TimeStamper(fmt=fmt, utc=utc, key=key) def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: if self.stamper.key not in event_dict: return self.stamper(logger, name, event_dict) return event_dict def _figure_out_exc_info(v: Any) -> ExcInfo | None: """ Try to convert *v* into an ``exc_info`` tuple. Return ``None`` if *v* does not represent an exception or if there is no current exception. """ if isinstance(v, BaseException): return (v.__class__, v, v.__traceback__) if isinstance(v, tuple) and len(v) == 3: has_type = isinstance(v[0], type) and issubclass(v[0], BaseException) has_exc = isinstance(v[1], BaseException) has_tb = v[2] is None or isinstance(v[2], TracebackType) if has_type and has_exc and has_tb: return v if v: result = sys.exc_info() if result == (None, None, None): return None return cast(ExcInfo, result) return None class ExceptionPrettyPrinter: """ Pretty print exceptions rendered by *exception_formatter* and remove them from the ``event_dict``. Args: file: Target file for output (default: ``sys.stdout``). exception_formatter: A callable that is used to format the exception from the ``exc_info`` field into the ``exception`` field. This processor is mostly for development and testing so you can read exceptions properly formatted. It behaves like `format_exc_info`, except that it removes the exception data from the event dictionary after printing it using the passed *exception_formatter*, which defaults to Python's built-in traceback formatting. It's tolerant to having `format_exc_info` in front of itself in the processor chain but doesn't require it. In other words, it handles both ``exception`` as well as ``exc_info`` keys. .. versionadded:: 0.4.0 .. versionchanged:: 16.0.0 Added support for passing exceptions as ``exc_info`` on Python 3. .. versionchanged:: 25.4.0 Fixed *exception_formatter* so that it overrides the default if set. """ def __init__( self, file: TextIO | None = None, exception_formatter: ExceptionTransformer = _format_exception, ) -> None: self.format_exception = exception_formatter if file is not None: self._file = file else: self._file = sys.stdout def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: exc = event_dict.pop("exception", None) if exc is None: exc_info = _figure_out_exc_info(event_dict.pop("exc_info", None)) if exc_info: exc = self.format_exception(exc_info) if exc: print(exc, file=self._file) return event_dict class StackInfoRenderer: """ Add stack information with key ``stack`` if ``stack_info`` is `True`. Useful when you want to attach a stack dump to a log entry without involving an exception and works analogously to the *stack_info* argument of the Python standard library logging. Args: additional_ignores: By default, stack frames coming from *structlog* are ignored. With this argument you can add additional names that are ignored, before the stack starts being rendered. They are matched using ``startswith()``, so they don't have to match exactly. The names are used to find the first relevant name, therefore once a frame is found that doesn't start with *structlog* or one of *additional_ignores*, **no filtering** is applied to subsequent frames. .. versionadded:: 0.4.0 .. versionadded:: 22.1.0 *additional_ignores* """ __slots__ = ("_additional_ignores",) def __init__(self, additional_ignores: list[str] | None = None) -> None: self._additional_ignores = additional_ignores def __call__( self, logger: WrappedLogger, name: str, event_dict: EventDict ) -> EventDict: if event_dict.pop("stack_info", None): event_dict["stack"] = _format_stack( _find_first_app_frame_and_name(self._additional_ignores)[0] ) return event_dict class CallsiteParameter(enum.Enum): """ Callsite parameters that can be added to an event dictionary with the `structlog.processors.CallsiteParameterAdder` processor class. The string values of the members of this enum will be used as the keys for the callsite parameters in the event dictionary. .. versionadded:: 21.5.0 .. versionadded:: 25.5.0 `QUAL_NAME` parameter. """ #: The full path to the python source file of the callsite. PATHNAME = "pathname" #: The basename part of the full path to the python source file of the #: callsite. FILENAME = "filename" #: The python module the callsite was in. This mimics the module attribute #: of `logging.LogRecord` objects and will be the basename, without #: extension, of the full path to the python source file of the callsite. MODULE = "module" #: The name of the function that the callsite was in. FUNC_NAME = "func_name" #: The qualified name of the callsite (includes scope and class names). #: Requires Python 3.11+. QUAL_NAME = "qual_name" #: The line number of the callsite. LINENO = "lineno" #: The ID of the thread the callsite was executed in. THREAD = "thread" #: The name of the thread the callsite was executed in. THREAD_NAME = "thread_name" #: The ID of the process the callsite was executed in. PROCESS = "process" #: The name of the process the callsite was executed in. PROCESS_NAME = "process_name" def _get_callsite_pathname(module: str, frame: FrameType) -> Any: return frame.f_code.co_filename def _get_callsite_filename(module: str, frame: FrameType) -> Any: return os.path.basename(frame.f_code.co_filename) def _get_callsite_module(module: str, frame: FrameType) -> Any: return os.path.splitext(os.path.basename(frame.f_code.co_filename))[0] def _get_callsite_func_name(module: str, frame: FrameType) -> Any: return frame.f_code.co_name def _get_callsite_qual_name(module: str, frame: FrameType) -> Any: return frame.f_code.co_qualname # will crash on Python <3.11 def _get_callsite_lineno(module: str, frame: FrameType) -> Any: return frame.f_lineno def _get_callsite_thread(module: str, frame: FrameType) -> Any: return threading.get_ident() def _get_callsite_thread_name(module: str, frame: FrameType) -> Any: return threading.current_thread().name def _get_callsite_process(module: str, frame: FrameType) -> Any: return os.getpid() def _get_callsite_process_name(module: str, frame: FrameType) -> Any: return get_processname() class CallsiteParameterAdder: """ Adds parameters of the callsite that an event dictionary originated from to the event dictionary. This processor can be used to enrich events dictionaries with information such as the function name, line number and filename that an event dictionary originated from. If the event dictionary has an embedded `logging.LogRecord` object and did not originate from *structlog* then the callsite information will be determined from the `logging.LogRecord` object. For event dictionaries without an embedded `logging.LogRecord` object the callsite will be determined from the stack trace, ignoring all intra-structlog calls, calls from the `logging` module, and stack frames from modules with names that start with values in ``additional_ignores``, if it is specified. The keys used for callsite parameters in the event dictionary are the string values of `CallsiteParameter` enum members. Args: parameters: A collection of `CallsiteParameter` values that should be added to the event dictionary. additional_ignores: Additional names with which a stack frame's module name must not start for it to be considered when determening the callsite. .. note:: When used with `structlog.stdlib.ProcessorFormatter` the most efficient configuration is to either use this processor in ``foreign_pre_chain`` of `structlog.stdlib.ProcessorFormatter` and in ``processors`` of `structlog.configure`, or to use it in ``processors`` of `structlog.stdlib.ProcessorFormatter` without using it in ``processors`` of `structlog.configure` and ``foreign_pre_chain`` of `structlog.stdlib.ProcessorFormatter`. .. versionadded:: 21.5.0 """ _handlers: ClassVar[ dict[CallsiteParameter, Callable[[str, FrameType], Any]] ] = { # We can't use lambda functions here because they are not pickleable. CallsiteParameter.PATHNAME: _get_callsite_pathname, CallsiteParameter.FILENAME: _get_callsite_filename, CallsiteParameter.MODULE: _get_callsite_module, CallsiteParameter.FUNC_NAME: _get_callsite_func_name, CallsiteParameter.QUAL_NAME: _get_callsite_qual_name, CallsiteParameter.LINENO: _get_callsite_lineno, CallsiteParameter.THREAD: _get_callsite_thread, CallsiteParameter.THREAD_NAME: _get_callsite_thread_name, CallsiteParameter.PROCESS: _get_callsite_process, CallsiteParameter.PROCESS_NAME: _get_callsite_process_name, } _record_attribute_map: ClassVar[dict[CallsiteParameter, str]] = { CallsiteParameter.PATHNAME: "pathname", CallsiteParameter.FILENAME: "filename", CallsiteParameter.MODULE: "module", CallsiteParameter.FUNC_NAME: "funcName", CallsiteParameter.LINENO: "lineno", CallsiteParameter.THREAD: "thread", CallsiteParameter.THREAD_NAME: "threadName", CallsiteParameter.PROCESS: "process", CallsiteParameter.PROCESS_NAME: "processName", } _all_parameters: ClassVar[set[CallsiteParameter]] = set(CallsiteParameter) class _RecordMapping(NamedTuple): event_dict_key: str record_attribute: str __slots__ = ("_active_handlers", "_additional_ignores", "_record_mappings") def __init__( self, parameters: Collection[CallsiteParameter] = _all_parameters, additional_ignores: list[str] | None = None, ) -> None: if additional_ignores is None: additional_ignores = [] # Ignore stack frames from the logging module. They will occur if this # processor is used in ProcessorFormatter, and additionally the logging # module should not be logging using structlog. self._additional_ignores = ["logging", *additional_ignores] self._active_handlers: list[ tuple[CallsiteParameter, Callable[[str, FrameType], Any]] ] = [] self._record_mappings: list[CallsiteParameterAdder._RecordMapping] = [] for parameter in parameters: self._active_handlers.append( (parameter, self._handlers[parameter]) ) if ( record_attr := self._record_attribute_map.get(parameter) ) is not None: self._record_mappings.append( self._RecordMapping( parameter.value, record_attr, ) ) def __call__( self, logger: logging.Logger, name: str, event_dict: EventDict ) -> EventDict: record: logging.LogRecord | None = event_dict.get("_record") from_structlog: bool = event_dict.get("_from_structlog", False) # If the event dictionary has a record, but it comes from structlog, # then the callsite parameters of the record will not be correct. if record is not None and not from_structlog: for mapping in self._record_mappings: event_dict[mapping.event_dict_key] = record.__dict__[ mapping.record_attribute ] return event_dict frame, module = _find_first_app_frame_and_name( additional_ignores=self._additional_ignores ) for parameter, handler in self._active_handlers: event_dict[parameter.value] = handler(module, frame) return event_dict class EventRenamer: r""" Rename the ``event`` key in event dicts. This is useful if you want to use consistent log message keys across platforms and/or use the ``event`` key for something custom. .. warning:: It's recommended to put this processor right before the renderer, since some processors may rely on the presence and meaning of the ``event`` key. Args: to: Rename ``event_dict["event"]`` to ``event_dict[to]`` replace_by: Rename ``event_dict[replace_by]`` to ``event_dict["event"]``. *replace_by* missing from ``event_dict`` is handled gracefully. .. versionadded:: 22.1.0 See also the :ref:`rename-event` recipe. """ def __init__(self, to: str, replace_by: str | None = None): self.to = to self.replace_by = replace_by def __call__( self, logger: logging.Logger, name: str, event_dict: EventDict ) -> EventDict: event = event_dict.pop("event") event_dict[self.to] = event if self.replace_by is not None: replace_by = event_dict.pop(self.replace_by, None) if replace_by is not None: event_dict["event"] = replace_by return event_dict structlog-25.5.0/src/structlog/py.typed0000644000000000000000000000000015077624341015072 0ustar00structlog-25.5.0/src/structlog/stdlib.py0000644000000000000000000011731715077624341015252 0ustar00# This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Processors and helpers specific to the :mod:`logging` module from the `Python standard library `_. See also :doc:`structlog's standard library support `. """ from __future__ import annotations import asyncio import contextvars import functools import logging import sys import warnings from functools import partial from typing import Any, Callable, Collection, Dict, Iterable, Sequence, cast if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self from . import _config from ._base import BoundLoggerBase from ._frames import _find_first_app_frame_and_name, _format_stack from ._log_levels import LEVEL_TO_NAME, NAME_TO_LEVEL, add_log_level from .contextvars import _ASYNC_CALLING_STACK, merge_contextvars from .exceptions import DropEvent from .processors import StackInfoRenderer from .typing import ( Context, EventDict, ExcInfo, Processor, ProcessorReturnValue, WrappedLogger, ) __all__ = [ "BoundLogger", "ExtraAdder", "LoggerFactory", "PositionalArgumentsFormatter", "ProcessorFormatter", "add_log_level", "add_log_level_number", "add_logger_name", "filter_by_level", "get_logger", "recreate_defaults", "render_to_log_args_and_kwargs", "render_to_log_kwargs", ] def recreate_defaults(*, log_level: int | None = logging.NOTSET) -> None: """ Recreate defaults on top of standard library's logging. The output looks the same, but goes through `logging`. As with vanilla defaults, the backwards-compatibility guarantees don't apply to the settings applied here. Args: log_level: If `None`, don't configure standard library logging **at all**. Otherwise configure it to log to `sys.stdout` at *log_level* (``logging.NOTSET`` being the default). If you need more control over `logging`, pass `None` here and configure it yourself. .. versionadded:: 22.1.0 .. versionchanged:: 23.3.0 Added `add_logger_name`. .. versionchanged:: 25.1.0 Added `PositionalArgumentsFormatter`. """ if log_level is not None: kw = {"force": True} logging.basicConfig( format="%(message)s", stream=sys.stdout, level=log_level, **kw, # type: ignore[arg-type] ) _config.reset_defaults() _config.configure( processors=[ PositionalArgumentsFormatter(), # handled by native loggers merge_contextvars, add_log_level, add_logger_name, StackInfoRenderer(), _config._BUILTIN_DEFAULT_PROCESSORS[-2], # TimeStamper _config._BUILTIN_DEFAULT_PROCESSORS[-1], # ConsoleRenderer ], wrapper_class=BoundLogger, logger_factory=LoggerFactory(), ) _SENTINEL = object() class _FixedFindCallerLogger(logging.Logger): """ Change the behavior of `logging.Logger.findCaller` to cope with *structlog*'s extra frames. """ def findCaller( self, stack_info: bool = False, stacklevel: int = 1 ) -> tuple[str, int, str, str | None]: """ Finds the first caller frame outside of structlog so that the caller info is populated for wrapping stdlib. This logger gets set as the default one when using LoggerFactory. """ sinfo: str | None # stdlib logging passes stacklevel=1 from log methods like .warning(), # but we've already skipped those frames by ignoring "logging", so we # need to adjust stacklevel down by 1. We need to manually drop # logging frames, because there's cases where we call logging methods # from within structlog and the stacklevel offsets don't work anymore. adjusted_stacklevel = max(0, stacklevel - 1) if stacklevel else None f, _name = _find_first_app_frame_and_name( ["logging"], stacklevel=adjusted_stacklevel ) sinfo = _format_stack(f) if stack_info else None return f.f_code.co_filename, f.f_lineno, f.f_code.co_name, sinfo class BoundLogger(BoundLoggerBase): """ Python Standard Library version of `structlog.BoundLogger`. Works exactly like the generic one except that it takes advantage of knowing the logging methods in advance. Use it like:: structlog.configure( wrapper_class=structlog.stdlib.BoundLogger, ) It also contains a bunch of properties that pass-through to the wrapped `logging.Logger` which should make it work as a drop-in replacement. .. versionadded:: 23.1.0 Async variants `alog()`, `adebug()`, `ainfo()`, and so forth. .. versionchanged:: 24.2.0 Callsite parameters are now also collected by `structlog.processors.CallsiteParameterAdder` for async log methods. """ _logger: logging.Logger def bind(self, **new_values: Any) -> Self: """ Return a new logger with *new_values* added to the existing ones. """ return super().bind(**new_values) def unbind(self, *keys: str) -> Self: """ Return a new logger with *keys* removed from the context. Raises: KeyError: If the key is not part of the context. """ return super().unbind(*keys) def try_unbind(self, *keys: str) -> Self: """ Like :meth:`unbind`, but best effort: missing keys are ignored. .. versionadded:: 18.2.0 """ return super().try_unbind(*keys) def new(self, **new_values: Any) -> Self: """ Clear context and binds *initial_values* using `bind`. Only necessary with dict implementations that keep global state like those wrapped by `structlog.threadlocal.wrap_dict` when threads are reused. """ return super().new(**new_values) def debug(self, event: str | None = None, *args: Any, **kw: Any) -> Any: """ Process event and call `logging.Logger.debug` with the result. """ return self._proxy_to_logger("debug", event, *args, **kw) def info(self, event: str | None = None, *args: Any, **kw: Any) -> Any: """ Process event and call `logging.Logger.info` with the result. """ return self._proxy_to_logger("info", event, *args, **kw) def warning(self, event: str | None = None, *args: Any, **kw: Any) -> Any: """ Process event and call `logging.Logger.warning` with the result. """ return self._proxy_to_logger("warning", event, *args, **kw) warn = warning def error(self, event: str | None = None, *args: Any, **kw: Any) -> Any: """ Process event and call `logging.Logger.error` with the result. """ return self._proxy_to_logger("error", event, *args, **kw) def critical(self, event: str | None = None, *args: Any, **kw: Any) -> Any: """ Process event and call `logging.Logger.critical` with the result. """ return self._proxy_to_logger("critical", event, *args, **kw) def fatal(self, event: str | None = None, *args: Any, **kw: Any) -> Any: """ Process event and call `logging.Logger.critical` with the result. """ return self._proxy_to_logger("critical", event, *args, **kw) def exception( self, event: str | None = None, *args: Any, **kw: Any ) -> Any: """ Process event and call `logging.Logger.exception` with the result, after setting ``exc_info`` to `True` if it's not already set. """ kw.setdefault("exc_info", True) return self._proxy_to_logger("exception", event, *args, **kw) def log( self, level: int, event: str | None = None, *args: Any, **kw: Any ) -> Any: """ Process *event* and call the appropriate logging method depending on *level*. """ return self._proxy_to_logger(LEVEL_TO_NAME[level], event, *args, **kw) def _proxy_to_logger( self, method_name: str, event: str | None = None, *event_args: str, **event_kw: Any, ) -> Any: """ Propagate a method call to the wrapped logger. This is the same as the superclass implementation, except that it also preserves positional arguments in the ``event_dict`` so that the stdlib's support for format strings can be used. """ if event_args: event_kw["positional_args"] = event_args return super()._proxy_to_logger(method_name, event=event, **event_kw) # Pass-through attributes and methods to mimic the stdlib's logger # interface. @property def name(self) -> str: """ Returns :attr:`logging.Logger.name` """ return self._logger.name @property def level(self) -> int: """ Returns :attr:`logging.Logger.level` """ return self._logger.level @property def parent(self) -> Any: """ Returns :attr:`logging.Logger.parent` """ return self._logger.parent @property def propagate(self) -> bool: """ Returns :attr:`logging.Logger.propagate` """ return self._logger.propagate @property def handlers(self) -> Any: """ Returns :attr:`logging.Logger.handlers` """ return self._logger.handlers @property def disabled(self) -> int: """ Returns :attr:`logging.Logger.disabled` """ return self._logger.disabled def setLevel(self, level: int) -> None: """ Calls :meth:`logging.Logger.setLevel` with unmodified arguments. """ self._logger.setLevel(level) def findCaller( self, stack_info: bool = False, stacklevel: int = 1 ) -> tuple[str, int, str, str | None]: """ Calls :meth:`logging.Logger.findCaller` with unmodified arguments. """ # No need for stacklevel-adjustments since we're within structlog and # our frames are ignored unconditionally. return self._logger.findCaller( stack_info=stack_info, stacklevel=stacklevel ) def makeRecord( self, name: str, level: int, fn: str, lno: int, msg: str, args: tuple[Any, ...], exc_info: ExcInfo, func: str | None = None, extra: Any = None, ) -> logging.LogRecord: """ Calls :meth:`logging.Logger.makeRecord` with unmodified arguments. """ return self._logger.makeRecord( name, level, fn, lno, msg, args, exc_info, func=func, extra=extra ) def handle(self, record: logging.LogRecord) -> None: """ Calls :meth:`logging.Logger.handle` with unmodified arguments. """ self._logger.handle(record) def addHandler(self, hdlr: logging.Handler) -> None: """ Calls :meth:`logging.Logger.addHandler` with unmodified arguments. """ self._logger.addHandler(hdlr) def removeHandler(self, hdlr: logging.Handler) -> None: """ Calls :meth:`logging.Logger.removeHandler` with unmodified arguments. """ self._logger.removeHandler(hdlr) def hasHandlers(self) -> bool: """ Calls :meth:`logging.Logger.hasHandlers` with unmodified arguments. Exists only in Python 3. """ return self._logger.hasHandlers() def callHandlers(self, record: logging.LogRecord) -> None: """ Calls :meth:`logging.Logger.callHandlers` with unmodified arguments. """ self._logger.callHandlers(record) def getEffectiveLevel(self) -> int: """ Calls :meth:`logging.Logger.getEffectiveLevel` with unmodified arguments. """ return self._logger.getEffectiveLevel() def isEnabledFor(self, level: int) -> bool: """ Calls :meth:`logging.Logger.isEnabledFor` with unmodified arguments. """ return self._logger.isEnabledFor(level) def getChild(self, suffix: str) -> logging.Logger: """ Calls :meth:`logging.Logger.getChild` with unmodified arguments. """ return self._logger.getChild(suffix) # Non-Standard Async async def _dispatch_to_sync( self, meth: Callable[..., Any], event: str, args: tuple[Any, ...], kw: dict[str, Any], ) -> None: """ Merge contextvars and log using the sync logger in a thread pool. """ scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back) # type: ignore[union-attr, arg-type, unused-ignore] ctx = contextvars.copy_context() try: await asyncio.get_running_loop().run_in_executor( None, lambda: ctx.run(lambda: meth(event, *args, **kw)), ) finally: _ASYNC_CALLING_STACK.reset(scs_token) async def adebug(self, event: str, *args: Any, **kw: Any) -> None: """ Log using `debug()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ await self._dispatch_to_sync(self.debug, event, args, kw) async def ainfo(self, event: str, *args: Any, **kw: Any) -> None: """ Log using `info()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ await self._dispatch_to_sync(self.info, event, args, kw) async def awarning(self, event: str, *args: Any, **kw: Any) -> None: """ Log using `warning()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ await self._dispatch_to_sync(self.warning, event, args, kw) async def aerror(self, event: str, *args: Any, **kw: Any) -> None: """ Log using `error()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ await self._dispatch_to_sync(self.error, event, args, kw) async def acritical(self, event: str, *args: Any, **kw: Any) -> None: """ Log using `critical()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ await self._dispatch_to_sync(self.critical, event, args, kw) async def afatal(self, event: str, *args: Any, **kw: Any) -> None: """ Log using `critical()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ await self._dispatch_to_sync(self.critical, event, args, kw) async def aexception(self, event: str, *args: Any, **kw: Any) -> None: """ Log using `exception()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ # To make `log.exception("foo") work, we have to check if the user # passed an explicit exc_info and if not, supply our own. if kw.get("exc_info", True) is True and kw.get("exception") is None: kw["exc_info"] = sys.exc_info() await self._dispatch_to_sync(self.exception, event, args, kw) async def alog( self, level: Any, event: str, *args: Any, **kw: Any ) -> None: """ Log using `log()`, but asynchronously in a separate thread. .. versionadded:: 23.1.0 """ await self._dispatch_to_sync(partial(self.log, level), event, args, kw) def get_logger(*args: Any, **initial_values: Any) -> BoundLogger: """ Only calls `structlog.get_logger`, but has the correct type hints. .. warning:: Does **not** check whether -- or ensure that -- you've configured *structlog* for standard library :mod:`logging`! See :doc:`standard-library` for details. .. versionadded:: 20.2.0 """ return _config.get_logger(*args, **initial_values) class AsyncBoundLogger: """ Wraps a `BoundLogger` & exposes its logging methods as ``async`` versions. Instead of blocking the program, they are run asynchronously in a thread pool executor. This means more computational overhead per log call. But it also means that the processor chain (e.g. JSON serialization) and I/O won't block your whole application. Only available for Python 3.7 and later. .. versionadded:: 20.2.0 .. versionchanged:: 20.2.0 fix _dispatch_to_sync contextvars usage .. deprecated:: 23.1.0 Use the regular `BoundLogger` with its a-prefixed methods instead. .. versionchanged:: 23.3.0 Callsite parameters are now also collected for async log methods. """ __slots__ = ("_loop", "sync_bl") #: The wrapped synchronous logger. It is useful to be able to log #: synchronously occasionally. sync_bl: BoundLogger _executor = None _bound_logger_factory = BoundLogger def __init__( self, logger: logging.Logger, processors: Iterable[Processor], context: Context, *, # Only as an optimization for binding! _sync_bl: Any = None, # *vroom vroom* over purity. _loop: Any = None, ): if _sync_bl: self.sync_bl = _sync_bl self._loop = _loop return self.sync_bl = self._bound_logger_factory( logger=logger, processors=processors, context=context ) self._loop = asyncio.get_running_loop() # Instances would've been correctly recognized as such, however the class # not and we need the class in `structlog.configure()`. @property def _context(self) -> Context: return self.sync_bl._context def bind(self, **new_values: Any) -> Self: return self.__class__( # logger, processors and context are within sync_bl. These # arguments are ignored if _sync_bl is passed. *vroom vroom* over # purity. logger=None, # type: ignore[arg-type] processors=(), context={}, _sync_bl=self.sync_bl.bind(**new_values), _loop=self._loop, ) def new(self, **new_values: Any) -> Self: return self.__class__( # c.f. comment in bind logger=None, # type: ignore[arg-type] processors=(), context={}, _sync_bl=self.sync_bl.new(**new_values), _loop=self._loop, ) def unbind(self, *keys: str) -> Self: return self.__class__( # c.f. comment in bind logger=None, # type: ignore[arg-type] processors=(), context={}, _sync_bl=self.sync_bl.unbind(*keys), _loop=self._loop, ) def try_unbind(self, *keys: str) -> Self: return self.__class__( # c.f. comment in bind logger=None, # type: ignore[arg-type] processors=(), context={}, _sync_bl=self.sync_bl.try_unbind(*keys), _loop=self._loop, ) async def _dispatch_to_sync( self, meth: Callable[..., Any], event: str, args: tuple[Any, ...], kw: dict[str, Any], ) -> None: """ Merge contextvars and log using the sync logger in a thread pool. """ scs_token = _ASYNC_CALLING_STACK.set(sys._getframe().f_back.f_back) # type: ignore[union-attr, arg-type, unused-ignore] ctx = contextvars.copy_context() try: await asyncio.get_running_loop().run_in_executor( self._executor, lambda: ctx.run(lambda: meth(event, *args, **kw)), ) finally: _ASYNC_CALLING_STACK.reset(scs_token) async def debug(self, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync(self.sync_bl.debug, event, args, kw) async def info(self, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync(self.sync_bl.info, event, args, kw) async def warning(self, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync(self.sync_bl.warning, event, args, kw) async def warn(self, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync(self.sync_bl.warning, event, args, kw) async def error(self, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync(self.sync_bl.error, event, args, kw) async def critical(self, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync(self.sync_bl.critical, event, args, kw) async def fatal(self, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync(self.sync_bl.critical, event, args, kw) async def exception(self, event: str, *args: Any, **kw: Any) -> None: # To make `log.exception("foo") work, we have to check if the user # passed an explicit exc_info and if not, supply our own. ei = kw.pop("exc_info", None) if ei is None and kw.get("exception") is None: ei = sys.exc_info() kw["exc_info"] = ei await self._dispatch_to_sync(self.sync_bl.exception, event, args, kw) async def log(self, level: Any, event: str, *args: Any, **kw: Any) -> None: await self._dispatch_to_sync( partial(self.sync_bl.log, level), event, args, kw ) class LoggerFactory: """ Build a standard library logger when an *instance* is called. Sets a custom logger using :func:`logging.setLoggerClass` so variables in log format are expanded properly. >>> from structlog import configure >>> from structlog.stdlib import LoggerFactory >>> configure(logger_factory=LoggerFactory()) Args: ignore_frame_names: When guessing the name of a logger, skip frames whose names *start* with one of these. For example, in pyramid applications you'll want to set it to ``["venusian", "pyramid.config"]``. This argument is called *additional_ignores* in other APIs throughout *structlog*. """ def __init__(self, ignore_frame_names: list[str] | None = None): self._ignore = ignore_frame_names logging.setLoggerClass(_FixedFindCallerLogger) def __call__(self, *args: Any) -> logging.Logger: """ Deduce the caller's module name and create a stdlib logger. If an optional argument is passed, it will be used as the logger name instead of guesswork. This optional argument would be passed from the :func:`structlog.get_logger` call. For example ``structlog.get_logger("foo")`` would cause this method to be called with ``"foo"`` as its first positional argument. .. versionchanged:: 0.4.0 Added support for optional positional arguments. Using the first one for naming the constructed logger. """ if args: return logging.getLogger(args[0]) # We skip all frames that originate from within structlog or one of the # configured names. _, name = _find_first_app_frame_and_name(self._ignore) return logging.getLogger(name) class PositionalArgumentsFormatter: """ Apply stdlib-like string formatting to the ``event`` key. If the ``positional_args`` key in the event dict is set, it must contain a tuple that is used for formatting (using the ``%s`` string formatting operator) of the value from the ``event`` key. This works in the same way as the stdlib handles arguments to the various log methods: if the tuple contains only a single `dict` argument it is used for keyword placeholders in the ``event`` string, otherwise it will be used for positional placeholders. ``positional_args`` is populated by `structlog.stdlib.BoundLogger` or can be set manually. The *remove_positional_args* flag can be set to `False` to keep the ``positional_args`` key in the event dict; by default it will be removed from the event dict after formatting a message. """ def __init__(self, remove_positional_args: bool = True) -> None: self.remove_positional_args = remove_positional_args def __call__( self, _: WrappedLogger, __: str, event_dict: EventDict ) -> EventDict: args = event_dict.get("positional_args") # Mimic the formatting behaviour of the stdlib's logging module, which # accepts both positional arguments and a single dict argument. The # "single dict" check is the same one as the stdlib's logging module # performs in LogRecord.__init__(). if args: if len(args) == 1 and isinstance(args[0], dict) and args[0]: args = args[0] event_dict["event"] %= args if self.remove_positional_args and args is not None: del event_dict["positional_args"] return event_dict def filter_by_level( logger: logging.Logger, method_name: str, event_dict: EventDict ) -> EventDict: """ Check whether logging is configured to accept messages from this log level. Should be the first processor if stdlib's filtering by level is used so possibly expensive processors like exception formatters are avoided in the first place. >>> import logging >>> from structlog.stdlib import filter_by_level >>> logging.basicConfig(level=logging.WARN) >>> logger = logging.getLogger() >>> filter_by_level(logger, 'warn', {}) {} >>> filter_by_level(logger, 'debug', {}) Traceback (most recent call last): ... DropEvent """ if ( # We can't use logger.isEnabledFor() because it's always disabled when # a log entry is in flight on Python 3.14 and later, not logger.disabled and NAME_TO_LEVEL[method_name] >= logger.getEffectiveLevel() ): return event_dict raise DropEvent def add_log_level_number( logger: logging.Logger, method_name: str, event_dict: EventDict ) -> EventDict: """ Add the log level number to the event dict. Log level numbers map to the log level names. The Python stdlib uses them for filtering logic. This adds the same numbers so users can leverage similar filtering. Compare:: level in ("warning", "error", "critical") level_number >= 30 The mapping of names to numbers is in ``structlog.stdlib._log_levels._NAME_TO_LEVEL``. .. versionadded:: 18.2.0 """ event_dict["level_number"] = NAME_TO_LEVEL[method_name] return event_dict def add_logger_name( logger: logging.Logger, method_name: str, event_dict: EventDict ) -> EventDict: """ Add the logger name to the event dict. """ record = event_dict.get("_record") if record is None: event_dict["logger"] = logger.name else: event_dict["logger"] = record.name return event_dict _LOG_RECORD_KEYS = logging.LogRecord( "name", 0, "pathname", 0, "msg", (), None ).__dict__.keys() class ExtraAdder: """ Add extra attributes of `logging.LogRecord` objects to the event dictionary. This processor can be used for adding data passed in the ``extra`` parameter of the `logging` module's log methods to the event dictionary. Args: allow: An optional collection of attributes that, if present in `logging.LogRecord` objects, will be copied to event dictionaries. If ``allow`` is None all attributes of `logging.LogRecord` objects that do not exist on a standard `logging.LogRecord` object will be copied to event dictionaries. .. versionadded:: 21.5.0 """ __slots__ = ("_copier",) def __init__(self, allow: Collection[str] | None = None) -> None: self._copier: Callable[[EventDict, logging.LogRecord], None] if allow is not None: # The contents of allow is copied to a new list so that changes to # the list passed into the constructor does not change the # behaviour of this processor. self._copier = functools.partial(self._copy_allowed, [*allow]) else: self._copier = self._copy_all def __call__( self, logger: logging.Logger, name: str, event_dict: EventDict ) -> EventDict: record: logging.LogRecord | None = event_dict.get("_record") if record is not None: self._copier(event_dict, record) return event_dict @classmethod def _copy_all( cls, event_dict: EventDict, record: logging.LogRecord ) -> None: for key, value in record.__dict__.items(): if key not in _LOG_RECORD_KEYS: event_dict[key] = value @classmethod def _copy_allowed( cls, allow: Collection[str], event_dict: EventDict, record: logging.LogRecord, ) -> None: for key in allow: if key in record.__dict__: event_dict[key] = record.__dict__[key] LOG_KWARG_NAMES = ("exc_info", "stack_info", "stacklevel") def render_to_log_args_and_kwargs( _: logging.Logger, __: str, event_dict: EventDict ) -> tuple[tuple[Any, ...], dict[str, Any]]: """ Render ``event_dict`` into positional and keyword arguments for `logging.Logger` logging methods. See `logging.Logger.debug` method for keyword arguments reference. The ``event`` field is passed in the first positional argument, positional arguments from ``positional_args`` field are passed in subsequent positional arguments, keyword arguments are extracted from the *event_dict* and the rest of the *event_dict* is added as ``extra``. This allows you to defer formatting to `logging`. .. versionadded:: 25.1.0 """ args = (event_dict.pop("event"), *event_dict.pop("positional_args", ())) kwargs = { kwarg_name: event_dict.pop(kwarg_name) for kwarg_name in LOG_KWARG_NAMES if kwarg_name in event_dict } if event_dict: kwargs["extra"] = event_dict return args, kwargs def render_to_log_kwargs( _: logging.Logger, __: str, event_dict: EventDict ) -> EventDict: """ Render ``event_dict`` into keyword arguments for `logging.Logger` logging methods. See `logging.Logger.debug` method for keyword arguments reference. The ``event`` field is translated into ``msg``, keyword arguments are extracted from the *event_dict* and the rest of the *event_dict* is added as ``extra``. This allows you to defer formatting to `logging`. .. versionadded:: 17.1.0 .. versionchanged:: 22.1.0 ``exc_info``, ``stack_info``, and ``stacklevel`` are passed as proper kwargs and not put into ``extra``. .. versionchanged:: 24.2.0 ``stackLevel`` corrected to ``stacklevel``. """ return { "msg": event_dict.pop("event"), "extra": event_dict, **{ kw: event_dict.pop(kw) for kw in LOG_KWARG_NAMES if kw in event_dict }, } class ProcessorFormatter(logging.Formatter): r""" Call *structlog* processors on `logging.LogRecord`\s. This is an implementation of a `logging.Formatter` that can be used to format log entries from both *structlog* and `logging`. Its static method `wrap_for_formatter` must be the final processor in *structlog*'s processor chain. Please refer to :ref:`processor-formatter` for examples. Args: foreign_pre_chain: If not `None`, it is used as a processor chain that is applied to **non**-*structlog* log entries before the event dictionary is passed to *processors*. (default: `None`) processors: A chain of *structlog* processors that is used to process **all** log entries. The last one must render to a `str` which then gets passed on to `logging` for output. Compared to *structlog*'s regular processor chains, there's a few differences: - The event dictionary contains two additional keys: #. ``_record``: a `logging.LogRecord` that either was created using `logging` APIs, **or** is a wrapped *structlog* log entry created by `wrap_for_formatter`. #. ``_from_structlog``: a `bool` that indicates whether or not ``_record`` was created by a *structlog* logger. Since you most likely don't want ``_record`` and ``_from_structlog`` in your log files, we've added the static method `remove_processors_meta` to ``ProcessorFormatter`` that you can add just before your renderer. - Since this is a `logging` *formatter*, raising `structlog.DropEvent` will crash your application. keep_exc_info: ``exc_info`` on `logging.LogRecord`\ s is added to the ``event_dict`` and removed afterwards. Set this to ``True`` to keep it on the `logging.LogRecord`. (default: False) keep_stack_info: Same as *keep_exc_info* except for ``stack_info``. (default: False) logger: Logger which we want to push through the *structlog* processor chain. This parameter is necessary for some of the processors like `filter_by_level`. (default: None) pass_foreign_args: If True, pass a foreign log record's ``args`` attribute to the ``event_dict`` under ``positional_args`` key. (default: False) processor: A single *structlog* processor used for rendering the event dictionary before passing it off to `logging`. Must return a `str`. The event dictionary does **not** contain ``_record`` and ``_from_structlog``. This parameter exists for historic reasons. Please use *processors* instead. use_get_message: If True, use ``record.getMessage`` to get a fully rendered log message, otherwise use ``str(record.msg)``. (default: True) Raises: TypeError: If both or neither *processor* and *processors* are passed. .. versionadded:: 17.1.0 .. versionadded:: 17.2.0 *keep_exc_info* and *keep_stack_info* .. versionadded:: 19.2.0 *logger* .. versionadded:: 19.2.0 *pass_foreign_args* .. versionadded:: 21.3.0 *processors* .. deprecated:: 21.3.0 *processor* (singular) in favor of *processors* (plural). Removal is not planned. .. versionadded:: 23.3.0 *use_get_message* """ def __init__( self, processor: Processor | None = None, processors: Sequence[Processor] | None = (), foreign_pre_chain: Sequence[Processor] | None = None, keep_exc_info: bool = False, keep_stack_info: bool = False, logger: logging.Logger | None = None, pass_foreign_args: bool = False, use_get_message: bool = True, *args: Any, **kwargs: Any, ) -> None: fmt = kwargs.pop("fmt", "%(message)s") super().__init__(*args, fmt=fmt, **kwargs) # type: ignore[misc] if processor and processors: msg = ( "The `processor` and `processors` arguments are mutually" " exclusive." ) raise TypeError(msg) self.processors: Sequence[Processor] if processor is not None: self.processors = (self.remove_processors_meta, processor) elif processors: self.processors = processors else: msg = "Either `processor` or `processors` must be passed." raise TypeError(msg) self.foreign_pre_chain = foreign_pre_chain self.keep_exc_info = keep_exc_info self.keep_stack_info = keep_stack_info self.logger = logger self.pass_foreign_args = pass_foreign_args self.use_get_message = use_get_message def format(self, record: logging.LogRecord) -> str: """ Extract *structlog*'s `event_dict` from ``record.msg`` and format it. *record* has been patched by `wrap_for_formatter` first though, so the type isn't quite right. """ # Make a shallow copy of the record to let other handlers/formatters # process the original one record = logging.makeLogRecord(record.__dict__) logger = getattr(record, "_logger", _SENTINEL) meth_name = getattr(record, "_name", "__structlog_sentinel__") ed: ProcessorReturnValue if logger is not _SENTINEL and meth_name != "__structlog_sentinel__": # Both attached by wrap_for_formatter if self.logger is not None: logger = self.logger meth_name = cast(str, record._name) # type:ignore[attr-defined] # We need to copy because it's possible that the same record gets # processed by multiple logging formatters. LogRecord.getMessage # would transform our dict into a str. ed = cast(Dict[str, Any], record.msg).copy() ed["_record"] = record ed["_from_structlog"] = True else: logger = self.logger meth_name = record.levelname.lower() ed = { "event": ( record.getMessage() if self.use_get_message else str(record.msg) ), "_record": record, "_from_structlog": False, } if self.pass_foreign_args: ed["positional_args"] = record.args record.args = () # Add stack-related attributes to the event dict if record.exc_info: ed["exc_info"] = record.exc_info if record.stack_info: ed["stack_info"] = record.stack_info # Non-structlog allows to run through a chain to prepare it for the # final processor (e.g. adding timestamps and log levels). for proc in self.foreign_pre_chain or (): ed = cast(EventDict, proc(logger, meth_name, ed)) # If required, unset stack-related attributes on the record copy so # that the base implementation doesn't append stacktraces to the # output. if not self.keep_exc_info: record.exc_text = None record.exc_info = None if not self.keep_stack_info: record.stack_info = None for p in self.processors: ed = p(logger, meth_name, ed) # type: ignore[arg-type] if not isinstance(ed, str): warnings.warn( "The last processor in ProcessorFormatter.processors must " f"return a string, but {self.processors[-1]} returned a " f"{type(ed)} instead.", category=RuntimeWarning, stacklevel=1, ) ed = cast(str, ed) record.msg = ed return super().format(record) @staticmethod def wrap_for_formatter( logger: logging.Logger, name: str, event_dict: EventDict ) -> tuple[tuple[EventDict], dict[str, dict[str, Any]]]: """ Wrap *logger*, *name*, and *event_dict*. The result is later unpacked by `ProcessorFormatter` when formatting log entries. Use this static method as the renderer (in other words, final processor) if you want to use `ProcessorFormatter` in your `logging` configuration. """ return (event_dict,), {"extra": {"_logger": logger, "_name": name}} @staticmethod def remove_processors_meta( _: WrappedLogger, __: str, event_dict: EventDict ) -> EventDict: """ Remove ``_record`` and ``_from_structlog`` from *event_dict*. These keys are added to the event dictionary, before `ProcessorFormatter`'s *processors* are run. .. versionadded:: 21.3.0 """ del event_dict["_record"] del event_dict["_from_structlog"] return event_dict structlog-25.5.0/src/structlog/testing.py0000644000000000000000000001316115077624341015436 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Helpers to test your application's logging behavior. .. versionadded:: 20.1.0 See :doc:`testing`. """ from __future__ import annotations from contextlib import contextmanager from typing import Any, Generator, Iterable, NamedTuple, NoReturn from ._config import configure, get_config from ._log_levels import map_method_name from .exceptions import DropEvent from .typing import EventDict, Processor, WrappedLogger __all__ = [ "CapturedCall", "CapturingLogger", "CapturingLoggerFactory", "LogCapture", "ReturnLogger", "ReturnLoggerFactory", "capture_logs", ] class LogCapture: """ Class for capturing log messages in its entries list. Generally you should use `structlog.testing.capture_logs`, but you can use this class if you want to capture logs with other patterns. :ivar List[structlog.typing.EventDict] entries: The captured log entries. .. versionadded:: 20.1.0 .. versionchanged:: 24.3.0 Added mapping from "exception" to "error" Added mapping from "warn" to "warning" """ entries: list[EventDict] def __init__(self) -> None: self.entries = [] def __call__( self, _: WrappedLogger, method_name: str, event_dict: EventDict ) -> NoReturn: event_dict["log_level"] = map_method_name(method_name) self.entries.append(event_dict) raise DropEvent @contextmanager def capture_logs( processors: Iterable[Processor] = (), ) -> Generator[list[EventDict], None, None]: """ Context manager that appends all logging statements to its yielded list while it is active. Disables all configured processors for the duration of the context manager. Attention: this is **not** thread-safe! Args: processors: Processors to apply before the logs are captured. .. versionadded:: 20.1.0 .. versionadded:: 25.5.0 *processors* parameter """ cap = LogCapture() # Modify `_Configuration.default_processors` set via `configure` but always # keep the list instance intact to not break references held by bound # loggers. configured_processors = get_config()["processors"] old_processors = configured_processors.copy() try: # clear processors list and use LogCapture for testing configured_processors.clear() configured_processors.extend(processors) configured_processors.append(cap) configure(processors=configured_processors) yield cap.entries finally: # remove LogCapture and restore original processors configured_processors.clear() configured_processors.extend(old_processors) configure(processors=configured_processors) class ReturnLogger: """ Return the arguments that it's called with. >>> from structlog import ReturnLogger >>> ReturnLogger().info("hello") 'hello' >>> ReturnLogger().info("hello", when="again") (('hello',), {'when': 'again'}) .. versionchanged:: 0.3.0 Allow for arbitrary arguments and keyword arguments to be passed in. """ def msg(self, *args: Any, **kw: Any) -> Any: """ Return tuple of ``args, kw`` or just ``args[0]`` if only one arg passed """ # Slightly convoluted for backwards compatibility. if len(args) == 1 and not kw: return args[0] return args, kw log = debug = info = warn = warning = msg fatal = failure = err = error = critical = exception = msg class ReturnLoggerFactory: r""" Produce and cache `ReturnLogger`\ s. To be used with `structlog.configure`\ 's *logger_factory*. Positional arguments are silently ignored. .. versionadded:: 0.4.0 """ def __init__(self) -> None: self._logger = ReturnLogger() def __call__(self, *args: Any) -> ReturnLogger: return self._logger class CapturedCall(NamedTuple): """ A call as captured by `CapturingLogger`. Can also be unpacked like a tuple. Args: method_name: The method name that got called. args: A tuple of the positional arguments. kwargs: A dict of the keyword arguments. .. versionadded:: 20.2.0 """ method_name: str args: tuple[Any, ...] kwargs: dict[str, Any] class CapturingLogger: """ Store the method calls that it's been called with. This is nicer than `ReturnLogger` for unit tests because the bound logger doesn't have to cooperate. **Any** method name is supported. .. versionadded:: 20.2.0 """ calls: list[CapturedCall] def __init__(self) -> None: self.calls = [] def __repr__(self) -> str: return f"" def __getattr__(self, name: str) -> Any: """ Capture call to `calls` """ def log(*args: Any, **kw: Any) -> None: self.calls.append(CapturedCall(name, args, kw)) return log class CapturingLoggerFactory: r""" Produce and cache `CapturingLogger`\ s. Each factory produces and reuses only **one** logger. You can access it via the ``logger`` attribute. To be used with `structlog.configure`\ 's *logger_factory*. Positional arguments are silently ignored. .. versionadded:: 20.2.0 """ logger: CapturingLogger def __init__(self) -> None: self.logger = CapturingLogger() def __call__(self, *args: Any) -> CapturingLogger: return self.logger structlog-25.5.0/src/structlog/threadlocal.py0000644000000000000000000002174115077624341016246 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ **Deprecated** primitives to keep context global but thread (and greenlet) local. See `thread-local`, but please use :doc:`contextvars` instead. .. deprecated:: 22.1.0 """ from __future__ import annotations import contextlib import sys import threading import uuid import warnings from typing import Any, Generator, Iterator, TypeVar import structlog from ._config import BoundLoggerLazyProxy from .typing import BindableLogger, Context, EventDict, WrappedLogger def _determine_threadlocal() -> type[Any]: """ Return a dict-like threadlocal storage depending on whether we run with greenlets or not. """ try: from ._greenlets import GreenThreadLocal except ImportError: from threading import local return local return GreenThreadLocal # pragma: no cover ThreadLocal = _determine_threadlocal() def _deprecated() -> None: """ Raise a warning with best-effort stacklevel adjustment. """ callsite = "" with contextlib.suppress(Exception): f = sys._getframe() callsite = f.f_back.f_back.f_globals[ # type: ignore[union-attr] "__name__" ] # Avoid double warnings if TL functions call themselves. if callsite == "structlog.threadlocal": return stacklevel = 3 # If a function is used as a decorator, we need to add two stack levels. # This logic will probably break eventually, but it's not worth any more # complexity. if callsite == "contextlib": stacklevel += 2 warnings.warn( "`structlog.threadlocal` is deprecated, please use " "`structlog.contextvars` instead.", DeprecationWarning, stacklevel=stacklevel, ) def wrap_dict(dict_class: type[Context]) -> type[Context]: """ Wrap a dict-like class and return the resulting class. The wrapped class and used to keep global in the current thread. Args: dict_class: Class used for keeping context. .. deprecated:: 22.1.0 """ _deprecated() Wrapped = type( "WrappedDict-" + str(uuid.uuid4()), (_ThreadLocalDictWrapper,), {} ) Wrapped._tl = ThreadLocal() # type: ignore[attr-defined] Wrapped._dict_class = dict_class # type: ignore[attr-defined] return Wrapped TLLogger = TypeVar("TLLogger", bound=BindableLogger) def as_immutable(logger: TLLogger) -> TLLogger: """ Extract the context from a thread local logger into an immutable logger. Args: logger (structlog.typing.BindableLogger): A logger with *possibly* thread local state. Returns: :class:`~structlog.BoundLogger` with an immutable context. .. deprecated:: 22.1.0 """ _deprecated() if isinstance(logger, BoundLoggerLazyProxy): logger = logger.bind() try: ctx = logger._context._tl.dict_.__class__( # type: ignore[union-attr] logger._context._dict # type: ignore[union-attr] ) bl = logger.__class__( logger._logger, # type: ignore[attr-defined, call-arg] processors=logger._processors, # type: ignore[attr-defined] context={}, ) bl._context = ctx # type: ignore[misc] return bl except AttributeError: return logger @contextlib.contextmanager def tmp_bind( logger: TLLogger, **tmp_values: Any ) -> Generator[TLLogger, None, None]: """ Bind *tmp_values* to *logger* & memorize current state. Rewind afterwards. Only works with `structlog.threadlocal.wrap_dict`-based contexts. Use :func:`~structlog.threadlocal.bound_threadlocal` for new code. .. deprecated:: 22.1.0 """ _deprecated() if isinstance(logger, BoundLoggerLazyProxy): logger = logger.bind() saved = as_immutable(logger)._context try: yield logger.bind(**tmp_values) finally: logger._context.clear() logger._context.update(saved) class _ThreadLocalDictWrapper: """ Wrap a dict-like class and keep the state *global* but *thread-local*. Attempts to re-initialize only updates the wrapped dictionary. Useful for short-lived threaded applications like requests in web app. Use :func:`wrap` to instantiate and use :func:`structlog.BoundLogger.new` to clear the context. """ _tl: Any _dict_class: type[dict[str, Any]] def __init__(self, *args: Any, **kw: Any) -> None: """ We cheat. A context dict gets never recreated. """ if args and isinstance(args[0], self.__class__): # our state is global, no need to look at args[0] if it's of our # class self._dict.update(**kw) else: self._dict.update(*args, **kw) @property def _dict(self) -> Context: """ Return or create and return the current context. """ try: return self.__class__._tl.dict_ except AttributeError: self.__class__._tl.dict_ = self.__class__._dict_class() return self.__class__._tl.dict_ def __repr__(self) -> str: return f"<{self.__class__.__name__}({self._dict!r})>" def __eq__(self, other: object) -> bool: # Same class == same dictionary return self.__class__ == other.__class__ def __ne__(self, other: object) -> bool: return not self.__eq__(other) # Proxy methods necessary for structlog. # Dunder methods don't trigger __getattr__ so we need to proxy by hand. def __iter__(self) -> Iterator[str]: return self._dict.__iter__() def __setitem__(self, key: str, value: Any) -> None: self._dict[key] = value def __delitem__(self, key: str) -> None: self._dict.__delitem__(key) def __len__(self) -> int: return self._dict.__len__() def __getattr__(self, name: str) -> Any: return getattr(self._dict, name) _CONTEXT = threading.local() def get_threadlocal() -> Context: """ Return a copy of the current thread-local context. .. versionadded:: 21.2.0 .. deprecated:: 22.1.0 """ _deprecated() return _get_context().copy() def get_merged_threadlocal(bound_logger: BindableLogger) -> Context: """ Return a copy of the current thread-local context merged with the context from *bound_logger*. .. versionadded:: 21.2.0 .. deprecated:: 22.1.0 """ _deprecated() ctx = _get_context().copy() ctx.update(structlog.get_context(bound_logger)) return ctx def merge_threadlocal( logger: WrappedLogger, method_name: str, event_dict: EventDict ) -> EventDict: """ A processor that merges in a global (thread-local) context. Use this as your first processor in :func:`structlog.configure` to ensure thread-local context is included in all log calls. .. versionadded:: 19.2.0 .. versionchanged:: 20.1.0 This function used to be called ``merge_threadlocal_context`` and that name is still kept around for backward compatibility. .. deprecated:: 22.1.0 """ _deprecated() context = _get_context().copy() context.update(event_dict) return context # Alias that shouldn't be used anymore. merge_threadlocal_context = merge_threadlocal def clear_threadlocal() -> None: """ Clear the thread-local context. The typical use-case for this function is to invoke it early in request-handling code. .. versionadded:: 19.2.0 .. deprecated:: 22.1.0 """ _deprecated() _CONTEXT.context = {} def bind_threadlocal(**kw: Any) -> None: """ Put keys and values into the thread-local context. Use this instead of :func:`~structlog.BoundLogger.bind` when you want some context to be global (thread-local). .. versionadded:: 19.2.0 .. deprecated:: 22.1.0 """ _deprecated() _get_context().update(kw) def unbind_threadlocal(*keys: str) -> None: """ Tries to remove bound *keys* from threadlocal logging context if present. .. versionadded:: 20.1.0 .. deprecated:: 22.1.0 """ _deprecated() context = _get_context() for key in keys: context.pop(key, None) @contextlib.contextmanager def bound_threadlocal(**kw: Any) -> Generator[None, None, None]: """ Bind *kw* to the current thread-local context. Unbind or restore *kw* afterwards. Do **not** affect other keys. Can be used as a context manager or decorator. .. versionadded:: 21.4.0 .. deprecated:: 22.1.0 """ _deprecated() context = get_threadlocal() saved = {k: context[k] for k in context.keys() & kw.keys()} bind_threadlocal(**kw) try: yield finally: unbind_threadlocal(*kw.keys()) bind_threadlocal(**saved) def _get_context() -> Context: try: return _CONTEXT.context except AttributeError: _CONTEXT.context = {} return _CONTEXT.context structlog-25.5.0/src/structlog/tracebacks.py0000644000000000000000000004025115077624341016063 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Extract a structured traceback from an exception. Based on work by Will McGugan `_ from `rich.traceback `_. """ from __future__ import annotations import os import os.path import sys from dataclasses import asdict, dataclass, field from traceback import walk_tb from types import ModuleType, TracebackType from typing import Any, Iterable, Sequence, Tuple, Union try: import rich import rich.pretty except ImportError: rich = None # type: ignore[assignment] from .typing import ExcInfo __all__ = [ "ExceptionDictTransformer", "Frame", "Stack", "SyntaxError_", "Trace", "extract", "safe_str", "to_repr", ] SHOW_LOCALS = True LOCALS_MAX_LENGTH = 10 LOCALS_MAX_STRING = 80 MAX_FRAMES = 50 OptExcInfo = Union[ExcInfo, Tuple[None, None, None]] @dataclass class Frame: """ Represents a single stack frame. """ filename: str lineno: int name: str locals: dict[str, str] | None = None @dataclass class SyntaxError_: # noqa: N801 """ Contains detailed information about :exc:`SyntaxError` exceptions. """ offset: int filename: str line: str lineno: int msg: str @dataclass class Stack: """ Represents an exception and a list of stack frames. .. versionchanged:: 25.2.0 Added the *exc_notes* field. .. versionchanged:: 25.4.0 Added the *is_group* and *exceptions* fields. """ exc_type: str exc_value: str exc_notes: list[str] = field(default_factory=list) syntax_error: SyntaxError_ | None = None is_cause: bool = False frames: list[Frame] = field(default_factory=list) is_group: bool = False exceptions: list[Trace] = field(default_factory=list) @dataclass class Trace: """ Container for a list of stack traces. """ stacks: list[Stack] def safe_str(_object: Any) -> str: """Don't allow exceptions from __str__ to propagate.""" try: return str(_object) except Exception as error: # noqa: BLE001 return f"" def to_repr( obj: Any, max_length: int | None = None, max_string: int | None = None, use_rich: bool = True, ) -> str: """ Get repr string for an object, but catch errors. :func:`repr()` is used for strings, too, so that secret wrappers that inherit from :func:`str` and overwrite ``__repr__()`` are handled correctly (i.e. secrets are not logged in plain text). Args: obj: Object to get a string representation for. max_length: Maximum length of containers before abbreviating, or ``None`` for no abbreviation. max_string: Maximum length of string before truncating, or ``None`` to disable truncating. use_rich: If ``True`` (the default), use rich_ to compute the repr. If ``False`` or if rich_ is not installed, fall back to a simpler algorithm. Returns: The string representation of *obj*. .. versionchanged:: 24.3.0 Added *max_length* argument. Use :program:`rich` to render locals if it is available. Call :func:`repr()` on strings in fallback implementation. """ if use_rich and rich is not None: # Let rich render the repr if it is available. # It produces much better results for containers and dataclasses/attrs. obj_repr = rich.pretty.traverse( obj, max_length=max_length, max_string=max_string ).render() else: # Generate a (truncated) repr if rich is not available. # Handle str/bytes differently to get better results for truncated # representations. Also catch all errors, similarly to "safe_str()". try: if isinstance(obj, (str, bytes)): if max_string is not None and len(obj) > max_string: truncated = len(obj) - max_string obj_repr = f"{obj[:max_string]!r}+{truncated}" else: obj_repr = repr(obj) else: obj_repr = repr(obj) if max_string is not None and len(obj_repr) > max_string: truncated = len(obj_repr) - max_string obj_repr = f"{obj_repr[:max_string]!r}+{truncated}" except Exception as error: # noqa: BLE001 obj_repr = f"" return obj_repr def extract( exc_type: type[BaseException], exc_value: BaseException, traceback: TracebackType | None, *, show_locals: bool = False, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, locals_hide_dunder: bool = True, locals_hide_sunder: bool = False, use_rich: bool = True, _seen: set[int] | None = None, ) -> Trace: """ Extract traceback information. Args: exc_type: Exception type. exc_value: Exception value. traceback: Python Traceback object. show_locals: Enable display of local variables. Defaults to False. locals_max_length: Maximum length of containers before abbreviating, or ``None`` for no abbreviation. locals_max_string: Maximum length of string before truncating, or ``None`` to disable truncating. locals_hide_dunder: Hide locals prefixed with double underscore. Defaults to True. locals_hide_sunder: Hide locals prefixed with single underscore. This implies hiding *locals_hide_dunder*. Defaults to False. use_rich: If ``True`` (the default), use rich_ to compute the repr. If ``False`` or if rich_ is not installed, fall back to a simpler algorithm. Returns: A Trace instance with structured information about all exceptions. .. versionadded:: 22.1.0 .. versionchanged:: 24.3.0 Added *locals_max_length*, *locals_hide_sunder*, *locals_hide_dunder* and *use_rich* arguments. .. versionchanged:: 25.4.0 Handle exception groups. .. versionchanged:: 25.5.0 Handle loops in exception cause chain. """ stacks: list[Stack] = [] is_cause = False if _seen is None: _seen = set() while True: exc_id = id(exc_value) if exc_id in _seen: break _seen.add(exc_id) stack = Stack( exc_type=safe_str(exc_type.__name__), exc_value=safe_str(exc_value), exc_notes=[ safe_str(note) for note in getattr(exc_value, "__notes__", ()) ], is_cause=is_cause, ) if sys.version_info >= (3, 11): if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)): # noqa: F821 stack.is_group = True for exception in exc_value.exceptions: stack.exceptions.append( extract( type(exception), exception, exception.__traceback__, show_locals=show_locals, locals_max_length=locals_max_length, locals_max_string=locals_max_string, locals_hide_dunder=locals_hide_dunder, locals_hide_sunder=locals_hide_sunder, use_rich=use_rich, _seen=_seen, ) ) if isinstance(exc_value, SyntaxError): stack.syntax_error = SyntaxError_( offset=exc_value.offset or 0, filename=exc_value.filename or "?", lineno=exc_value.lineno or 0, line=exc_value.text or "", msg=exc_value.msg, ) stacks.append(stack) append = stack.frames.append # pylint: disable=no-member def get_locals( iter_locals: Iterable[tuple[str, object]], ) -> Iterable[tuple[str, object]]: """Extract locals from an iterator of key pairs.""" if not (locals_hide_dunder or locals_hide_sunder): yield from iter_locals return for key, value in iter_locals: if locals_hide_dunder and key.startswith("__"): continue if locals_hide_sunder and key.startswith("_"): continue yield key, value for frame_summary, line_no in walk_tb(traceback): filename = frame_summary.f_code.co_filename if filename and not filename.startswith("<"): filename = os.path.abspath(filename) # Rich has this, but we are not rich and like to keep all frames: # if frame_summary.f_locals.get("_rich_traceback_omit", False): # continue # noqa: ERA001 frame = Frame( filename=filename or "?", lineno=line_no, name=frame_summary.f_code.co_name, locals=( { key: to_repr( value, max_length=locals_max_length, max_string=locals_max_string, use_rich=use_rich, ) for key, value in get_locals( frame_summary.f_locals.items() ) } if show_locals else None ), ) append(frame) cause = getattr(exc_value, "__cause__", None) if cause and cause.__traceback__: exc_type = cause.__class__ exc_value = cause traceback = cause.__traceback__ is_cause = True continue cause = exc_value.__context__ if ( cause and cause.__traceback__ and not getattr(exc_value, "__suppress_context__", False) ): exc_type = cause.__class__ exc_value = cause traceback = cause.__traceback__ is_cause = False continue # No cover, code is reached but coverage doesn't recognize it. break # pragma: no cover return Trace(stacks=stacks) class ExceptionDictTransformer: """ Return a list of exception stack dictionaries for an exception. These dictionaries are based on :class:`Stack` instances generated by :func:`extract()` and can be dumped to JSON. Args: show_locals: Whether or not to include the values of a stack frame's local variables. locals_max_length: Maximum length of containers before abbreviating, or ``None`` for no abbreviation. locals_max_string: Maximum length of string before truncating, or ``None`` to disable truncating. locals_hide_dunder: Hide locals prefixed with double underscore. Defaults to True. locals_hide_sunder: Hide locals prefixed with single underscore. This implies hiding *locals_hide_dunder*. Defaults to False. suppress: Optional sequence of modules or paths for which to suppress the display of locals even if *show_locals* is ``True``. max_frames: Maximum number of frames in each stack. Frames are removed from the inside out. The idea is, that the first frames represent your code responsible for the exception and last frames the code where the exception actually happened. With larger web frameworks, this does not always work, so you should stick with the default. use_rich: If ``True`` (the default), use rich_ to compute the repr of locals. If ``False`` or if rich_ is not installed, fall back to a simpler algorithm. .. seealso:: :doc:`exceptions` for a broader explanation of *structlog*'s exception features. .. versionchanged:: 24.3.0 Added *locals_max_length*, *locals_hide_sunder*, *locals_hide_dunder*, *suppress* and *use_rich* arguments. .. versionchanged:: 25.1.0 *locals_max_length* and *locals_max_string* may be None to disable truncation. .. versionchanged:: 25.4.0 Handle exception groups. """ def __init__( self, *, show_locals: bool = SHOW_LOCALS, locals_max_length: int = LOCALS_MAX_LENGTH, locals_max_string: int = LOCALS_MAX_STRING, locals_hide_dunder: bool = True, locals_hide_sunder: bool = False, suppress: Iterable[str | ModuleType] = (), max_frames: int = MAX_FRAMES, use_rich: bool = True, ) -> None: if locals_max_length is not None and locals_max_length < 0: msg = f'"locals_max_length" must be >= 0: {locals_max_length}' raise ValueError(msg) if locals_max_string is not None and locals_max_string < 0: msg = f'"locals_max_string" must be >= 0: {locals_max_string}' raise ValueError(msg) if max_frames < 2: msg = f'"max_frames" must be >= 2: {max_frames}' raise ValueError(msg) self.show_locals = show_locals self.locals_max_length = locals_max_length self.locals_max_string = locals_max_string self.locals_hide_dunder = locals_hide_dunder self.locals_hide_sunder = locals_hide_sunder self.suppress: Sequence[str] = [] for suppress_entity in suppress: if not isinstance(suppress_entity, str): if suppress_entity.__file__ is None: msg = ( f'"suppress" item {suppress_entity!r} must be a ' f"module with '__file__' attribute" ) raise ValueError(msg) path = os.path.dirname(suppress_entity.__file__) else: path = suppress_entity path = os.path.normpath(os.path.abspath(path)) self.suppress.append(path) self.max_frames = max_frames self.use_rich = use_rich def __call__(self, exc_info: ExcInfo) -> list[dict[str, Any]]: trace = extract( *exc_info, show_locals=self.show_locals, locals_max_length=self.locals_max_length, locals_max_string=self.locals_max_string, locals_hide_dunder=self.locals_hide_dunder, locals_hide_sunder=self.locals_hide_sunder, use_rich=self.use_rich, ) for stack in trace.stacks: if len(stack.frames) <= self.max_frames: continue half = ( self.max_frames // 2 ) # Force int division to handle odd numbers correctly fake_frame = Frame( filename="", lineno=-1, name=f"Skipped frames: {len(stack.frames) - (2 * half)}", ) stack.frames[:] = [ *stack.frames[:half], fake_frame, *stack.frames[-half:], ] return self._as_dict(trace) def _as_dict(self, trace: Trace) -> list[dict[str, Any]]: stack_dicts = [] for stack in trace.stacks: stack_dict = asdict(stack) for frame_dict in stack_dict["frames"]: if frame_dict["locals"] is None or any( frame_dict["filename"].startswith(path) for path in self.suppress ): del frame_dict["locals"] if stack.is_group: stack_dict["exceptions"] = [ self._as_dict(t) for t in stack.exceptions ] stack_dicts.append(stack_dict) return stack_dicts structlog-25.5.0/src/structlog/twisted.py0000644000000000000000000002355315077624341015452 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Processors and tools specific to the `Twisted `_ networking engine. See also :doc:`structlog's Twisted support `. """ from __future__ import annotations import json import sys from typing import Any, Callable, Sequence, TextIO from twisted.python import log from twisted.python.failure import Failure from twisted.python.log import ILogObserver, textFromEventDict from zope.interface import implementer from ._base import BoundLoggerBase from ._config import _BUILTIN_DEFAULT_PROCESSORS from .processors import JSONRenderer as GenericJSONRenderer from .typing import EventDict, WrappedLogger class BoundLogger(BoundLoggerBase): """ Twisted-specific version of `structlog.BoundLogger`. Works exactly like the generic one except that it takes advantage of knowing the logging methods in advance. Use it like:: configure( wrapper_class=structlog.twisted.BoundLogger, ) """ def msg(self, event: str | None = None, **kw: Any) -> Any: """ Process event and call ``log.msg()`` with the result. """ return self._proxy_to_logger("msg", event, **kw) def err(self, event: str | None = None, **kw: Any) -> Any: """ Process event and call ``log.err()`` with the result. """ return self._proxy_to_logger("err", event, **kw) class LoggerFactory: """ Build a Twisted logger when an *instance* is called. >>> from structlog import configure >>> from structlog.twisted import LoggerFactory >>> configure(logger_factory=LoggerFactory()) """ def __call__(self, *args: Any) -> WrappedLogger: """ Positional arguments are silently ignored. :rvalue: A new Twisted logger. .. versionchanged:: 0.4.0 Added support for optional positional arguments. """ return log _FAIL_TYPES = (BaseException, Failure) def _extractStuffAndWhy(eventDict: EventDict) -> tuple[Any, Any, EventDict]: """ Removes all possible *_why*s and *_stuff*s, analyzes exc_info and returns a tuple of ``(_stuff, _why, eventDict)``. **Modifies** *eventDict*! """ _stuff = eventDict.pop("_stuff", None) _why = eventDict.pop("_why", None) event = eventDict.pop("event", None) if isinstance(_stuff, _FAIL_TYPES) and isinstance(event, _FAIL_TYPES): raise ValueError("Both _stuff and event contain an Exception/Failure.") # `log.err('event', _why='alsoEvent')` is ambiguous. if _why and isinstance(event, str): raise ValueError("Both `_why` and `event` supplied.") # Two failures are ambiguous too. if not isinstance(_stuff, _FAIL_TYPES) and isinstance(event, _FAIL_TYPES): _why = _why or "error" _stuff = event if isinstance(event, str): _why = event if not _stuff and sys.exc_info() != (None, None, None): _stuff = Failure() # type: ignore[no-untyped-call] # Either we used the error ourselves or the user supplied one for # formatting. Avoid log.err() to dump another traceback into the log. if isinstance(_stuff, BaseException) and not isinstance(_stuff, Failure): _stuff = Failure(_stuff) # type: ignore[no-untyped-call] return _stuff, _why, eventDict class ReprWrapper: """ Wrap a string and return it as the ``__repr__``. This is needed for ``twisted.python.log.err`` that calls `repr` on ``_stuff``: >>> repr("foo") "'foo'" >>> repr(ReprWrapper("foo")) 'foo' Note the extra quotes in the unwrapped example. """ def __init__(self, string: str) -> None: self.string = string def __eq__(self, other: object) -> bool: """ Check for equality, just for tests. """ return ( isinstance(other, self.__class__) and self.string == other.string ) def __repr__(self) -> str: return self.string class JSONRenderer(GenericJSONRenderer): """ Behaves like `structlog.processors.JSONRenderer` except that it formats tracebacks and failures itself if called with ``err()``. .. note:: This ultimately means that the messages get logged out using ``msg()``, and *not* ``err()`` which renders failures in separate lines. Therefore it will break your tests that contain assertions using `flushLoggedErrors `_. *Not* an adapter like `EventAdapter` but a real formatter. Also does *not* require to be adapted using it. Use together with a `JSONLogObserverWrapper`-wrapped Twisted logger like `plainJSONStdOutLogger` for pure-JSON logs. """ def __call__( # type: ignore[override] self, logger: WrappedLogger, name: str, eventDict: EventDict, ) -> tuple[Sequence[Any], dict[str, Any]]: _stuff, _why, eventDict = _extractStuffAndWhy(eventDict) if name == "err": eventDict["event"] = _why if isinstance(_stuff, Failure): eventDict["exception"] = _stuff.getTraceback(detail="verbose") _stuff.cleanFailure() # type: ignore[no-untyped-call] else: eventDict["event"] = _why return ( ( ReprWrapper( GenericJSONRenderer.__call__( # type: ignore[arg-type] self, logger, name, eventDict ) ), ), {"_structlog": True}, ) @implementer(ILogObserver) class PlainFileLogObserver: """ Write only the plain message without timestamps or anything else. Great to just print JSON to stdout where you catch it with something like runit. Args: file: File to print to. .. versionadded:: 0.2.0 """ def __init__(self, file: TextIO) -> None: self._write = file.write self._flush = file.flush def __call__(self, eventDict: EventDict) -> None: self._write( textFromEventDict(eventDict) # type: ignore[arg-type, operator] + "\n", ) self._flush() @implementer(ILogObserver) class JSONLogObserverWrapper: """ Wrap a log *observer* and render non-`JSONRenderer` entries to JSON. Args: observer (ILogObserver): Twisted log observer to wrap. For example :class:`PlainFileObserver` or Twisted's stock `FileLogObserver `_ .. versionadded:: 0.2.0 """ def __init__(self, observer: Any) -> None: self._observer = observer def __call__(self, eventDict: EventDict) -> str: if "_structlog" not in eventDict: eventDict["message"] = ( json.dumps( { "event": textFromEventDict( eventDict # type: ignore[arg-type] ), "system": eventDict.get("system"), } ), ) eventDict["_structlog"] = True return self._observer(eventDict) def plainJSONStdOutLogger() -> JSONLogObserverWrapper: """ Return a logger that writes only the message to stdout. Transforms non-`JSONRenderer` messages to JSON. Ideal for JSONifying log entries from Twisted plugins and libraries that are outside of your control:: $ twistd -n --logger structlog.twisted.plainJSONStdOutLogger web {"event": "Log opened.", "system": "-"} {"event": "twistd 13.1.0 (python 2.7.3) starting up.", "system": "-"} {"event": "reactor class: twisted...EPollReactor.", "system": "-"} {"event": "Site starting on 8080", "system": "-"} {"event": "Starting factory ", ...} ... Composes `PlainFileLogObserver` and `JSONLogObserverWrapper` to a usable logger. .. versionadded:: 0.2.0 """ return JSONLogObserverWrapper(PlainFileLogObserver(sys.stdout)) class EventAdapter: """ Adapt an ``event_dict`` to Twisted logging system. Particularly, make a wrapped `twisted.python.log.err `_ behave as expected. Args: dictRenderer: Renderer that is used for the actual log message. Please note that structlog comes with a dedicated `JSONRenderer`. **Must** be the last processor in the chain and requires a *dictRenderer* for the actual formatting as an constructor argument in order to be able to fully support the original behaviors of ``log.msg()`` and ``log.err()``. """ def __init__( self, dictRenderer: ( Callable[[WrappedLogger, str, EventDict], str] | None ) = None, ) -> None: self._dictRenderer = dictRenderer or _BUILTIN_DEFAULT_PROCESSORS[-1] def __call__( self, logger: WrappedLogger, name: str, eventDict: EventDict ) -> Any: if name == "err": # This aspires to handle the following cases correctly: # 1. log.err(failure, _why='event', **kw) # 2. log.err('event', **kw) # 3. log.err(_stuff=failure, _why='event', **kw) _stuff, _why, eventDict = _extractStuffAndWhy(eventDict) eventDict["event"] = _why return ( (), { "_stuff": _stuff, "_why": self._dictRenderer(logger, name, eventDict), }, ) return self._dictRenderer(logger, name, eventDict) structlog-25.5.0/src/structlog/types.py0000644000000000000000000000137415077624341015130 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Deprecated name for :mod:`structlog.typing`. .. versionadded:: 20.2.0 .. deprecated:: 22.2.0 """ from __future__ import annotations from .typing import ( BindableLogger, Context, EventDict, ExceptionRenderer, ExceptionTransformer, ExcInfo, FilteringBoundLogger, Processor, WrappedLogger, ) __all__ = ( "BindableLogger", "Context", "EventDict", "ExcInfo", "ExceptionRenderer", "ExceptionTransformer", "FilteringBoundLogger", "Processor", "WrappedLogger", ) structlog-25.5.0/src/structlog/typing.py0000644000000000000000000002054515077624341015277 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Type information used throughout *structlog*. For now, they are considered provisional. Especially `BindableLogger` will probably change to something more elegant. .. versionadded:: 22.2.0 """ from __future__ import annotations import sys from types import TracebackType from typing import ( Any, Callable, Dict, Mapping, MutableMapping, Optional, Protocol, TextIO, Tuple, Type, Union, runtime_checkable, ) if sys.version_info >= (3, 11): from typing import Self else: from typing_extensions import Self WrappedLogger = Any """ A logger that is wrapped by a bound logger and is ultimately responsible for the output of the log entries. *structlog* makes *no* assumptions about it. .. versionadded:: 20.2.0 """ Context = Union[Dict[str, Any], Dict[Any, Any]] """ A dict-like context carrier. .. versionadded:: 20.2.0 """ EventDict = MutableMapping[str, Any] """ An event dictionary as it is passed into processors. It's created by copying the configured `Context` but doesn't need to support copy itself. .. versionadded:: 20.2.0 """ ProcessorReturnValue = Union[ Mapping[str, Any], str, bytes, bytearray, Tuple[Any, ...] ] """ A value returned by a processor. """ Processor = Callable[[WrappedLogger, str, EventDict], ProcessorReturnValue] """ A callable that is part of the processor chain. See :doc:`processors`. .. versionadded:: 20.2.0 """ ExcInfo = Tuple[Type[BaseException], BaseException, Optional[TracebackType]] """ An exception info tuple as returned by `sys.exc_info`. .. versionadded:: 20.2.0 """ ExceptionRenderer = Callable[[TextIO, ExcInfo], None] """ A callable that pretty-prints an `ExcInfo` into a file-like object. Used by `structlog.dev.ConsoleRenderer`. .. versionadded:: 21.2.0 """ @runtime_checkable class ExceptionTransformer(Protocol): """ **Protocol:** A callable that transforms an `ExcInfo` into another datastructure. The result should be something that your renderer can work with, e.g., a ``str`` or a JSON-serializable ``dict``. Used by `structlog.processors.format_exc_info()` and `structlog.processors.ExceptionPrettyPrinter`. Args: exc_info: Is the exception tuple to format Returns: Anything that can be rendered by the last processor in your chain, for example, a string or a JSON-serializable structure. .. versionadded:: 22.1.0 """ def __call__(self, exc_info: ExcInfo) -> Any: ... @runtime_checkable class BindableLogger(Protocol): """ **Protocol**: Methods shared among all bound loggers and that are relied on by *structlog*. .. versionadded:: 20.2.0 """ @property def _context(self) -> Context: ... def bind(self, **new_values: Any) -> Self: ... def unbind(self, *keys: str) -> Self: ... def try_unbind(self, *keys: str) -> Self: ... def new(self, **new_values: Any) -> Self: ... class FilteringBoundLogger(BindableLogger, Protocol): """ **Protocol**: A `BindableLogger` that filters by a level. The only way to instantiate one is using `make_filtering_bound_logger`. .. versionadded:: 20.2.0 .. versionadded:: 22.2.0 String interpolation using positional arguments. .. versionadded:: 22.2.0 Async variants ``alog()``, ``adebug()``, ``ainfo()``, and so forth. .. versionchanged:: 22.3.0 String interpolation is only attempted if positional arguments are passed. .. versionadded:: 25.5.0 String interpolation using dictionary-based arguments if the first and only argument is a mapping. """ def bind(self, **new_values: Any) -> FilteringBoundLogger: """ Return a new logger with *new_values* added to the existing ones. .. versionadded:: 22.1.0 """ def unbind(self, *keys: str) -> FilteringBoundLogger: """ Return a new logger with *keys* removed from the context. .. versionadded:: 22.1.0 """ def try_unbind(self, *keys: str) -> FilteringBoundLogger: """ Like :meth:`unbind`, but best effort: missing keys are ignored. .. versionadded:: 22.1.0 """ def new(self, **new_values: Any) -> FilteringBoundLogger: """ Clear context and binds *initial_values* using `bind`. .. versionadded:: 22.1.0 """ def is_enabled_for(self, level: int) -> bool: """ Check whether the logger is enabled for *level*. .. versionadded:: 25.1.0 """ def get_effective_level(self) -> int: """ Return the effective level of the logger. .. versionadded:: 25.1.0 """ def debug(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **debug** level. """ async def adebug(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **debug** level. ..versionadded:: 22.2.0 """ def info(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **info** level. """ async def ainfo(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **info** level. ..versionadded:: 22.2.0 """ def warning(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **warn** level. """ async def awarning(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **warn** level. ..versionadded:: 22.2.0 """ def warn(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **warn** level. """ async def awarn(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **warn** level. ..versionadded:: 22.2.0 """ def error(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **error** level. """ async def aerror(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **error** level. ..versionadded:: 22.2.0 """ def err(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **error** level. """ def fatal(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **critical** level. """ async def afatal(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **critical** level. ..versionadded:: 22.2.0 """ def exception(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **error** level and ensure that ``exc_info`` is set in the event dictionary. """ async def aexception(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **error** level and ensure that ``exc_info`` is set in the event dictionary. ..versionadded:: 22.2.0 """ def critical(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **critical** level. """ async def acritical(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **critical** level. ..versionadded:: 22.2.0 """ def msg(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **info** level. """ async def amsg(self, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at **info** level. """ def log(self, level: int, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at *level*. """ async def alog(self, level: int, event: str, *args: Any, **kw: Any) -> Any: """ Log ``event % args`` with **kw** at *level*. """ structlog-25.5.0/tests/__init__.py0000644000000000000000000000034215077624341014042 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. structlog-25.5.0/tests/additional_frame.py0000644000000000000000000000075015077624341015570 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Helper function for testing the deduction of stdlib logger names. Since the logger factories are called from within structlog._config, they have to skip a frame. Calling them here emulates that. """ def additional_frame(callable): return callable() structlog-25.5.0/tests/conftest.py0000644000000000000000000000267115077624341014137 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import logging from io import StringIO import pytest import structlog from structlog._log_levels import NAME_TO_LEVEL from structlog.testing import CapturingLogger try: import twisted except ImportError: twisted = None LOGGER = logging.getLogger() @pytest.fixture(autouse=True) def _ensure_logging_framework_not_altered(): """ Prevents 'ValueError: I/O operation on closed file.' errors. """ before_handlers = list(LOGGER.handlers) yield LOGGER.handlers = before_handlers @pytest.fixture(name="sio") def _sio(): """ A new StringIO instance. """ return StringIO() @pytest.fixture(name="event_dict") def _event_dict(): """ An example event dictionary with multiple value types w/o the event itself. """ class A: def __repr__(self): return r"" return {"a": A(), "b": [3, 4], "x": 7, "y": "test", "z": (1, 2)} @pytest.fixture( name="stdlib_log_method", params=[m for m in NAME_TO_LEVEL if m != "notset"], ) def _stdlib_log_methods(request): return request.param @pytest.fixture(name="cl") def _cl(): return CapturingLogger() @pytest.fixture(autouse=True) def _reset_config(): structlog.reset_defaults() structlog-25.5.0/tests/helpers.py0000644000000000000000000000171715077624341013754 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Shared test utilities. """ from unittest.mock import Mock, seal from structlog._log_levels import NAME_TO_LEVEL stdlib_log_methods = [m for m in NAME_TO_LEVEL if m != "notset"] class CustomError(Exception): """ Custom exception for testing purposes. """ def stub(**kwargs): """ Create a restrictive mock that prevents new attributes after creation. Similar to pretend.stub(). """ m = Mock(**kwargs) seal(m) return m def raiser(exception): """ Create a mock that raises the given exception when called. """ return Mock(side_effect=exception) def call_recorder(func): """ Create a mock that records calls while executing func. """ return Mock(side_effect=func) structlog-25.5.0/tests/test_base.py0000644000000000000000000001615415077624341014264 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import pytest from structlog import get_context from structlog._base import BoundLoggerBase from structlog._config import _CONFIG from structlog.exceptions import DropEvent from structlog.processors import KeyValueRenderer from structlog.testing import ReturnLogger from .helpers import CustomError, raiser, stub def build_bl(logger=None, processors=None, context=None): """ Convenience function to build BoundLoggerBases with sane defaults. """ return BoundLoggerBase( logger if logger is not None else ReturnLogger(), processors if processors is not None else _CONFIG.default_processors, context if context is not None else _CONFIG.default_context_class(), ) class TestBinding: def test_repr(self): """ repr() of a BoundLoggerBase shows its context and processors. """ bl = build_bl(processors=[1, 2, 3], context={"A": "B"}) assert ( "" ) == repr(bl) def test_binds_independently(self): """ Ensure BoundLogger is immutable by default. """ b = build_bl(processors=[KeyValueRenderer(sort_keys=True)]) b = b.bind(x=42, y=23) b1 = b.bind(foo="bar") b2 = b.bind(foo="qux") assert b._context != b1._context != b2._context def test_new_clears_state(self): """ Calling new() on a logger clears the context. """ b = build_bl() b = b.bind(x=42) assert 42 == get_context(b)["x"] b = b.bind() assert 42 == get_context(b)["x"] b = b.new() assert {} == dict(get_context(b)) def test_comparison(self): """ Two bound loggers are equal if their context is equal. """ b = build_bl() assert b == b.bind() assert b is not b.bind() assert b != b.bind(x=5) assert b != "test" def test_bind_keeps_class(self): """ Binding values does not change the type of the bound logger. """ class Wrapper(BoundLoggerBase): pass b = Wrapper(None, [], {}) assert isinstance(b.bind(), Wrapper) def test_new_keeps_class(self): """ Clearing context does not change the type of the bound logger. """ class Wrapper(BoundLoggerBase): pass b = Wrapper(None, [], {}) assert isinstance(b.new(), Wrapper) def test_unbind(self): """ unbind() removes keys from context. """ b = build_bl().bind(x=42, y=23).unbind("x", "y") assert {} == b._context def test_unbind_fail(self): """ unbind() raises KeyError if the key is missing. """ with pytest.raises(KeyError): build_bl().bind(x=42, y=23).unbind("x", "z") def test_try_unbind(self): """ try_unbind() removes keys from context. """ b = build_bl().bind(x=42, y=23).try_unbind("x", "y") assert {} == b._context def test_try_unbind_fail(self): """ try_unbind() does nothing if the key is missing. """ b = build_bl().bind(x=42, y=23).try_unbind("x", "z") assert {"y": 23} == b._context class TestProcessing: def test_event_empty_string(self): """ Empty strings are a valid event. """ b = build_bl(processors=[], context={}) args, kw = b._process_event("meth", "", {"foo": "bar"}) assert () == args assert {"event": "", "foo": "bar"} == kw def test_copies_context_before_processing(self): """ BoundLoggerBase._process_event() gets called before relaying events to wrapped loggers. """ def chk(_, __, event_dict): assert b._context is not event_dict return "" b = build_bl(processors=[chk]) assert (("",), {}) == b._process_event("", "event", {}) assert "event" not in b._context def test_chain_does_not_swallow_all_exceptions(self): """ If the chain raises anything else than DropEvent, the error is not swallowed. """ b = build_bl(processors=[raiser(CustomError)]) with pytest.raises(CustomError): b._process_event("", "boom", {}) def test_last_processor_returns_string(self): """ If the final processor returns a string, ``(the_string,), {}`` is returned. """ logger = stub(msg=lambda *args, **kw: (args, kw)) b = build_bl(logger, processors=[lambda *_: "foo"]) assert (("foo",), {}) == b._process_event("", "foo", {}) def test_last_processor_returns_bytes(self): """ If the final processor returns bytes, ``(the_bytes,), {}`` is returned. """ logger = stub(msg=lambda *args, **kw: (args, kw)) b = build_bl(logger, processors=[lambda *_: b"foo"]) assert ((b"foo",), {}) == b._process_event(None, "name", {}) def test_last_processor_returns_bytearray(self): """ If the final processor returns a bytearray, ``(the_array,), {}`` is returned. """ logger = stub(msg=lambda *args, **kw: (args, kw)) b = build_bl(logger, processors=[lambda *_: bytearray(b"foo")]) assert ((bytearray(b"foo"),), {}) == b._process_event(None, "name", {}) def test_last_processor_returns_tuple(self): """ If the final processor returns a tuple, it is just passed through. """ logger = stub(msg=lambda *args, **kw: (args, kw)) b = build_bl( logger, processors=[lambda *_: (("foo",), {"key": "value"})] ) assert (("foo",), {"key": "value"}) == b._process_event("", "foo", {}) def test_last_processor_returns_dict(self): """ If the final processor returns a dict, ``(), the_dict`` is returned. """ logger = stub(msg=lambda *args, **kw: (args, kw)) b = build_bl(logger, processors=[lambda *_: {"event": "foo"}]) assert ((), {"event": "foo"}) == b._process_event("", "foo", {}) def test_last_processor_returns_unknown_value(self): """ If the final processor returns something unexpected, raise ValueError with a helpful error message. """ logger = stub(msg=lambda *args, **kw: (args, kw)) b = build_bl(logger, processors=[lambda *_: object()]) with pytest.raises(ValueError, match="Last processor didn't return"): b._process_event("", "foo", {}) class TestProxying: def test_processor_raising_DropEvent_silently_aborts_chain(self, capsys): """ If a processor raises DropEvent, the chain is aborted and nothing is proxied to the logger. """ b = build_bl(processors=[raiser(DropEvent), raiser(ValueError)]) b._proxy_to_logger("", None, x=5) assert ("", "") == capsys.readouterr() structlog-25.5.0/tests/test_config.py0000644000000000000000000003173415077624341014620 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import abc import pickle import warnings from unittest.mock import call import pytest import structlog from structlog._base import BoundLoggerBase from structlog._config import ( _BUILTIN_DEFAULT_CONTEXT_CLASS, _BUILTIN_DEFAULT_LOGGER_FACTORY, _BUILTIN_DEFAULT_PROCESSORS, _BUILTIN_DEFAULT_WRAPPER_CLASS, _CONFIG, BoundLoggerLazyProxy, configure, configure_once, get_logger, wrap_logger, ) from structlog.typing import BindableLogger from .helpers import call_recorder, stub @pytest.fixture(name="proxy") def _proxy(): """ Returns a BoundLoggerLazyProxy constructed w/o parameters & None as logger. """ return BoundLoggerLazyProxy(None) class Wrapper(BoundLoggerBase): """ Custom wrapper class for testing. """ def test_lazy_logger_is_not_detected_as_abstract_method(): """ If someone defines an attribute on an ABC with a logger, that logger is not detected as an abstract method. See https://github.com/hynek/structlog/issues/229 """ class Foo(metaclass=abc.ABCMeta): # noqa: B024 log = structlog.get_logger() Foo() def test_lazy_logger_is_an_instance_of_bindable_logger(): """ The BoundLoggerLazyProxy returned by get_logger fulfills the BindableLogger protocol. See https://github.com/hynek/structlog/issues/560 """ assert isinstance(get_logger(), BindableLogger) def test_lazy_logger_context_is_initial_values(): """ If a user asks for _context (e.g., using get_context) return initial_values. """ logger = get_logger(context="a") assert {"context": "a"} == structlog.get_context(logger) def test_default_context_class(): """ Default context class is dict. """ assert dict is _BUILTIN_DEFAULT_CONTEXT_CLASS class TestConfigure: def test_get_config_is_configured(self): """ Return value of structlog.get_config() works as input for structlog.configure(). is_configured() reflects the state of configuration. """ assert False is structlog.is_configured() structlog.configure(**structlog.get_config()) assert True is structlog.is_configured() structlog.reset_defaults() assert False is structlog.is_configured() def test_configure_all(self, proxy): """ All configurations are applied and land on the bound logger. """ x = stub() configure(processors=[x], context_class=dict) b = proxy.bind() assert [x] == b._processors assert dict is b._context.__class__ def test_reset(self, proxy): """ Reset resets all settings to their default values. """ x = stub() configure(processors=[x], context_class=dict, wrapper_class=Wrapper) structlog.reset_defaults() b = proxy.bind() assert [x] != b._processors assert _BUILTIN_DEFAULT_PROCESSORS == b._processors assert isinstance(b, _BUILTIN_DEFAULT_WRAPPER_CLASS) assert _BUILTIN_DEFAULT_CONTEXT_CLASS == b._context.__class__ assert _BUILTIN_DEFAULT_LOGGER_FACTORY is _CONFIG.logger_factory def test_just_processors(self, proxy): """ It's possible to only configure processors. """ x = stub() configure(processors=[x]) b = proxy.bind() assert [x] == b._processors assert _BUILTIN_DEFAULT_PROCESSORS != b._processors assert _BUILTIN_DEFAULT_CONTEXT_CLASS == b._context.__class__ def test_just_context_class(self, proxy): """ It's possible to only configure the context class. """ configure(context_class=dict) b = proxy.bind() assert dict is b._context.__class__ assert _BUILTIN_DEFAULT_PROCESSORS == b._processors def test_configure_sets_is_configured(self): """ After configure() is_configured() returns True. """ assert False is _CONFIG.is_configured configure() assert True is _CONFIG.is_configured def test_configures_logger_factory(self): """ It's possible to configure the logger factory. """ def f(): pass configure(logger_factory=f) assert f is _CONFIG.logger_factory class TestBoundLoggerLazyProxy: def test_repr(self): """ repr reflects all attributes. """ p = BoundLoggerLazyProxy( None, processors=[1, 2, 3], context_class=dict, initial_values={"foo": 42}, logger_factory_args=(4, 5), ) assert ( ", " "initial_values={'foo': 42}, " "logger_factory_args=(4, 5))>" ) == repr(p) def test_returns_bound_logger_on_bind(self, proxy): """ bind gets proxied to the wrapped bound logger. """ assert isinstance(proxy.bind(), BoundLoggerBase) def test_returns_bound_logger_on_new(self, proxy): """ new gets proxied to the wrapped bound logger. """ assert isinstance(proxy.new(), BoundLoggerBase) def test_returns_bound_logger_on_try_unbind(self, proxy): """ try_unbind gets proxied to the wrapped bound logger. """ assert isinstance(proxy.try_unbind(), BoundLoggerBase) def test_prefers_args_over_config(self): """ Configuration can be overridden by passing arguments. """ p = BoundLoggerLazyProxy( None, processors=[1, 2, 3], context_class=dict ) b = p.bind() assert isinstance(b._context, dict) assert [1, 2, 3] == b._processors class Class: def __init__(self, *args, **kw): pass def update(self, *args, **kw): pass configure(processors=[4, 5, 6], context_class=Class) b = p.bind() assert not isinstance(b._context, Class) assert [1, 2, 3] == b._processors def test_falls_back_to_config(self, proxy): """ Configuration is used if no arguments are passed. """ b = proxy.bind() assert isinstance(b._context, _CONFIG.default_context_class) assert _CONFIG.default_processors == b._processors def test_bind_honors_initial_values(self): """ Passed initial_values are merged on binds. """ p = BoundLoggerLazyProxy(None, initial_values={"a": 1, "b": 2}) b = p.bind() assert {"a": 1, "b": 2} == b._context b = p.bind(c=3) assert {"a": 1, "b": 2, "c": 3} == b._context def test_bind_binds_new_values(self, proxy): """ Values passed to bind arrive in the context. """ b = proxy.bind(c=3) assert {"c": 3} == b._context def test_unbind_unbinds_from_initial_values(self): """ It's possible to unbind a value that came from initial_values. """ p = BoundLoggerLazyProxy(None, initial_values={"a": 1, "b": 2}) b = p.unbind("a") assert {"b": 2} == b._context def test_honors_wrapper_class(self): """ Passed wrapper_class is used. """ p = BoundLoggerLazyProxy(None, wrapper_class=Wrapper) b = p.bind() assert isinstance(b, Wrapper) def test_honors_wrapper_from_config(self, proxy): """ Configured wrapper_class is used if not overridden. """ configure(wrapper_class=Wrapper) b = proxy.bind() assert isinstance(b, Wrapper) def test_new_binds_only_initial_values_implicit_ctx_class(self, proxy): """ new() doesn't clear initial_values if context_class comes from config. """ proxy = BoundLoggerLazyProxy(None, initial_values={"a": 1, "b": 2}) b = proxy.new(foo=42) assert {"a": 1, "b": 2, "foo": 42} == b._context def test_new_binds_only_initial_values_explicit_ctx_class(self, proxy): """ new() doesn't clear initial_values if context_class is passed explicitly.. """ proxy = BoundLoggerLazyProxy( None, initial_values={"a": 1, "b": 2}, context_class=dict ) b = proxy.new(foo=42) assert {"a": 1, "b": 2, "foo": 42} == b._context def test_rebinds_bind_method(self, proxy): """ To save time, be rebind the bind method once the logger has been cached. """ configure(cache_logger_on_first_use=True) bind = proxy.bind proxy.bind() assert bind != proxy.bind def test_does_not_cache_by_default(self, proxy): """ Proxy's bind method doesn't change by default. """ bind = proxy.bind proxy.bind() assert bind == proxy.bind @pytest.mark.parametrize("cache", [True, False]) def test_argument_takes_precedence_over_configuration(self, cache): """ Passing cache_logger_on_first_use as an argument overrides config. """ configure(cache_logger_on_first_use=cache) proxy = BoundLoggerLazyProxy(None, cache_logger_on_first_use=not cache) bind = proxy.bind proxy.bind() if cache: assert bind == proxy.bind else: assert bind != proxy.bind def test_bind_doesnt_cache_logger(self): """ Calling configure() changes BoundLoggerLazyProxys immediately. Previous uses of the BoundLoggerLazyProxy don't interfere. """ class F: "New logger factory with a new attribute" def info(self, *args): return 5 proxy = BoundLoggerLazyProxy(None) proxy.bind() configure(logger_factory=F) new_b = proxy.bind() assert new_b.info("test") == 5 def test_emphemeral(self): """ Calling an unknown method proxy creates a new wrapped bound logger first. """ class Foo(BoundLoggerBase): def foo(self): return 42 proxy = BoundLoggerLazyProxy( None, wrapper_class=Foo, cache_logger_on_first_use=False ) assert 42 == proxy.foo() @pytest.mark.parametrize("proto", range(pickle.HIGHEST_PROTOCOL + 1)) def test_pickle(self, proto): """ Can be pickled and unpickled. """ bllp = BoundLoggerLazyProxy(None) assert repr(bllp) == repr(pickle.loads(pickle.dumps(bllp, proto))) class TestFunctions: def test_wrap_passes_args(self): """ wrap_logger propagates all arguments to the wrapped bound logger. """ logger = object() p = wrap_logger(logger, processors=[1, 2, 3], context_class=dict) assert logger is p._logger assert [1, 2, 3] == p._processors assert dict is p._context_class def test_empty_processors(self): """ An empty list is a valid value for processors so it must be preserved. """ # We need to do a bind such that we get an actual logger and not just # a lazy proxy. logger = wrap_logger(object(), processors=[]).new() assert [] == logger._processors def test_wrap_returns_proxy(self): """ wrap_logger always returns a lazy proxy. """ assert isinstance(wrap_logger(None), BoundLoggerLazyProxy) def test_configure_once_issues_warning_on_repeated_call(self): """ configure_once raises a warning when it's after configuration. """ with warnings.catch_warnings(record=True) as warns: configure_once() assert 0 == len(warns) with warnings.catch_warnings(record=True) as warns: configure_once() assert 1 == len(warns) assert RuntimeWarning is warns[0].category assert "Repeated configuration attempted." == warns[0].message.args[0] def test_get_logger_configures_according_to_config(self): """ get_logger returns a correctly configured bound logger. """ b = get_logger().bind() assert isinstance( b._logger, _BUILTIN_DEFAULT_LOGGER_FACTORY().__class__ ) assert _BUILTIN_DEFAULT_PROCESSORS == b._processors assert isinstance(b, _BUILTIN_DEFAULT_WRAPPER_CLASS) assert _BUILTIN_DEFAULT_CONTEXT_CLASS == b._context.__class__ def test_get_logger_passes_positional_arguments_to_logger_factory(self): """ Ensure `get_logger` passes optional positional arguments through to the logger factory. """ factory = call_recorder(lambda *args: object()) configure(logger_factory=factory) get_logger("test").bind(x=42) assert [call("test")] == factory.call_args_list structlog-25.5.0/tests/test_contextvars.py0000644000000000000000000002157715077624341015737 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import asyncio import inspect import secrets import pytest import structlog from structlog.contextvars import ( _CONTEXT_VARS, bind_contextvars, bound_contextvars, clear_contextvars, get_contextvars, get_merged_contextvars, merge_contextvars, reset_contextvars, unbind_contextvars, ) @pytest.fixture(autouse=True) def _clear_contextvars(): """ Make sure all tests start with a clean slate. """ clear_contextvars() class TestContextvars: async def test_bind(self): """ Binding a variable causes it to be included in the result of merge_contextvars. """ event_loop = asyncio.get_running_loop() async def coro(): bind_contextvars(a=1) return merge_contextvars(None, None, {"b": 2}) assert {"a": 1, "b": 2} == await event_loop.create_task(coro()) async def test_multiple_binds(self): """ Multiple calls to bind_contextvars accumulate values instead of replacing them. But they override redefined ones. """ event_loop = asyncio.get_running_loop() async def coro(): bind_contextvars(a=1, c=3) bind_contextvars(c=333, d=4) return merge_contextvars(None, None, {"b": 2}) assert { "a": 1, "b": 2, "c": 333, "d": 4, } == await event_loop.create_task(coro()) async def test_reset(self): """ reset_contextvars allows resetting contexvars to previously-set values. """ event_loop = asyncio.get_running_loop() async def coro(): bind_contextvars(a=1) assert {"a": 1} == get_contextvars() await event_loop.create_task(nested_coro()) async def nested_coro(): tokens = bind_contextvars(a=2, b=3) assert {"a": 2, "b": 3} == get_contextvars() reset_contextvars(**tokens) assert {"a": 1} == get_contextvars() await event_loop.create_task(coro()) async def test_nested_async_bind(self): """ Context is passed correctly between "nested" concurrent operations. """ event_loop = asyncio.get_running_loop() async def coro(): bind_contextvars(a=1) return await event_loop.create_task(nested_coro()) async def nested_coro(): bind_contextvars(c=3) return merge_contextvars(None, None, {"b": 2}) assert {"a": 1, "b": 2, "c": 3} == await event_loop.create_task(coro()) async def test_merge_works_without_bind(self): """ merge_contextvars returns values as normal even when there has been no previous calls to bind_contextvars. """ event_loop = asyncio.get_running_loop() async def coro(): return merge_contextvars(None, None, {"b": 2}) assert {"b": 2} == await event_loop.create_task(coro()) async def test_merge_overrides_bind(self): """ Variables included in merge_contextvars override previously bound variables. """ event_loop = asyncio.get_running_loop() async def coro(): bind_contextvars(a=1) return merge_contextvars(None, None, {"a": 111, "b": 2}) assert {"a": 111, "b": 2} == await event_loop.create_task(coro()) async def test_clear(self): """ The context-local context can be cleared, causing any previously bound variables to not be included in merge_contextvars's result. """ event_loop = asyncio.get_running_loop() async def coro(): bind_contextvars(a=1) clear_contextvars() return merge_contextvars(None, None, {"b": 2}) assert {"b": 2} == await event_loop.create_task(coro()) async def test_clear_without_bind(self): """ The context-local context can be cleared, causing any previously bound variables to not be included in merge_contextvars's result. """ event_loop = asyncio.get_running_loop() async def coro(): clear_contextvars() return merge_contextvars(None, None, {}) assert {} == await event_loop.create_task(coro()) async def test_unbind(self): """ Unbinding a previously bound variable causes it to be removed from the result of merge_contextvars. """ event_loop = asyncio.get_running_loop() async def coro(): bind_contextvars(a=1) unbind_contextvars("a") return merge_contextvars(None, None, {"b": 2}) assert {"b": 2} == await event_loop.create_task(coro()) async def test_unbind_not_bound(self): """ Unbinding a not bound variable causes doesn't raise an exception. """ event_loop = asyncio.get_running_loop() async def coro(): # Since unbinding means "setting to Ellipsis", we have to make # some effort to ensure that the ContextVar never existed. unbind_contextvars("a" + secrets.token_hex()) return merge_contextvars(None, None, {"b": 2}) assert {"b": 2} == await event_loop.create_task(coro()) async def test_parallel_binds(self): """ Binding a variable causes it to be included in the result of merge_contextvars. """ event_loop = asyncio.get_running_loop() coro1_bind = asyncio.Event() coro2_bind = asyncio.Event() bind_contextvars(c=3) async def coro1(): bind_contextvars(a=1) coro1_bind.set() await coro2_bind.wait() return merge_contextvars(None, None, {"b": 2}) async def coro2(): bind_contextvars(a=2) await coro1_bind.wait() coro2_bind.set() return merge_contextvars(None, None, {"b": 2}) coro1_task = event_loop.create_task(coro1()) coro2_task = event_loop.create_task(coro2()) assert {"a": 1, "b": 2, "c": 3} == await coro1_task assert {"a": 2, "b": 2, "c": 3} == await coro2_task def test_get_only_gets_structlog_without_deleted(self): """ get_contextvars returns only the structlog-specific key-values with the prefix removed. Deleted keys (= Ellipsis) are ignored. """ bind_contextvars(a=1, b=2) unbind_contextvars("b") _CONTEXT_VARS["foo"] = "bar" assert {"a": 1} == get_contextvars() def test_get_merged_merges_context(self): """ get_merged_contextvars merges a bound context into the copy. """ bind_contextvars(x=1) log = structlog.get_logger().bind(y=2) assert {"x": 1, "y": 2} == get_merged_contextvars(log) class TestBoundContextvars: def test_cleanup(self): """ Bindings are cleaned up """ with bound_contextvars(x=42, y="foo"): assert {"x": 42, "y": "foo"} == get_contextvars() assert {} == get_contextvars() def test_cleanup_conflict(self): """ Overwritten keys are restored after the clean up """ bind_contextvars(x="original", z="unrelated") with bound_contextvars(x=42, y="foo"): assert {"x": 42, "y": "foo", "z": "unrelated"} == get_contextvars() assert {"x": "original", "z": "unrelated"} == get_contextvars() def test_preserve_independent_bind(self): """ New bindings inside bound_contextvars are preserved after the clean up """ with bound_contextvars(x=42): bind_contextvars(y="foo") assert {"x": 42, "y": "foo"} == get_contextvars() assert {"y": "foo"} == get_contextvars() def test_nesting_works(self): """ bound_contextvars binds and unbinds even when nested """ with bound_contextvars(l1=1): assert {"l1": 1} == get_contextvars() with bound_contextvars(l2=2): assert {"l1": 1, "l2": 2} == get_contextvars() assert {"l1": 1} == get_contextvars() assert {} == get_contextvars() def test_as_decorator(self): """ bound_contextvars can be used as a decorator and it preserves the name, signature and documentation of the wrapped function. """ @bound_contextvars(x=42) def wrapped(arg1): """Wrapped documentation""" bind_contextvars(y=arg1) assert {"x": 42, "y": arg1} == get_contextvars() wrapped(23) assert "wrapped" == wrapped.__name__ assert "(arg1)" == str(inspect.signature(wrapped)) assert "Wrapped documentation" == wrapped.__doc__ structlog-25.5.0/tests/test_dev.py0000644000000000000000000010223615077624341014125 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import pickle import sys from io import StringIO from unittest import mock import pytest import structlog from structlog import dev class TestPad: def test_normal(self): """ If chars are missing, adequate number of " " are added. """ assert 100 == len(dev._pad("test", 100)) def test_negative(self): """ If string is already too long, don't do anything. """ assert len("test") == len(dev._pad("test", 2)) @pytest.fixture(name="cr", scope="session") def _cr(): return dev.ConsoleRenderer( colors=dev._has_colors, exception_formatter=dev.plain_traceback ) @pytest.fixture(name="styles", scope="session") def _styles(cr): return cr._styles @pytest.fixture(name="padded", scope="session") def _padded(styles): return styles.bright + dev._pad("test", dev._EVENT_WIDTH) + styles.reset class TestConsoleRenderer: @pytest.mark.skipif(dev.colorama, reason="Colorama must be missing.") @pytest.mark.skipif( not dev._IS_WINDOWS, reason="Must be running on Windows." ) def test_missing_colorama(self): """ ConsoleRenderer(colors=True) raises SystemError on initialization if Colorama is missing and _IS_WINDOWS is True. """ with pytest.raises(SystemError) as e: dev.ConsoleRenderer(colors=True) assert ( "ConsoleRenderer with `colors=True` requires the Colorama package " "installed." ) in e.value.args[0] def test_plain(self, cr, padded): """ Works with a plain event_dict with only the event. """ rv = cr(None, None, {"event": "test"}) assert padded == rv def test_timestamp(self, cr, styles, padded): """ Timestamps get prepended. """ rv = cr(None, None, {"event": "test", "timestamp": 42}) assert (styles.timestamp + "42" + styles.reset + " " + padded) == rv def test_event_stringified(self, cr, padded): """ Event is cast to string. """ not_a_string = Exception("test") rv = cr(None, None, {"event": not_a_string}) assert padded == rv def test_event_renamed(self): """ The main event key can be renamed. """ cr = dev.ConsoleRenderer(colors=False, event_key="msg") assert "new event name event='something custom'" == cr( None, None, {"msg": "new event name", "event": "something custom"} ) def test_timestamp_renamed(self): """ The timestamp key can be renamed. """ cr = dev.ConsoleRenderer(colors=False, timestamp_key="ts") assert ( "2023-09-07 le event" == cr( None, None, {"ts": "2023-09-07", "event": "le event"}, ).rstrip() ) def test_event_key_property(self): """ The event_key property can be set and retrieved. """ cr = dev.ConsoleRenderer(colors=False) assert cr.event_key == "event" cr.event_key = "msg" assert cr.event_key == "msg" assert "new event name event='something custom'" == cr( None, None, {"msg": "new event name", "event": "something custom"}, ) def test_timestamp_key_property(self): """ The timestamp_key property can be set and retrieved. """ cr = dev.ConsoleRenderer(colors=False) assert cr.timestamp_key == "timestamp" cr.timestamp_key = "ts" assert cr.timestamp_key == "ts" assert ( "2023-09-07 le event" == cr( None, None, {"ts": "2023-09-07", "event": "le event"}, ).rstrip() ) def test_level(self, cr, styles, padded): """ Levels are rendered aligned, in square brackets, and color-coded. """ rv = cr( None, None, {"event": "test", "level": "critical", "foo": "bar"} ) assert ( "[" + dev.RED + styles.bright + dev._pad("critical", cr._longest_level) + styles.reset + "] " + padded + " " + styles.kv_key + "foo" + styles.reset + "=" + styles.kv_value + "bar" + styles.reset ) == rv def test_init_accepts_overriding_levels(self, styles, padded): """ Stdlib levels are rendered aligned, in brackets, and color coded. """ my_styles = dev.ConsoleRenderer.get_default_level_styles( colors=dev._has_colors ) my_styles["MY_OH_MY"] = my_styles["critical"] cr = dev.ConsoleRenderer( colors=dev._has_colors, level_styles=my_styles ) # this would blow up if the level_styles override failed rv = cr( None, None, {"event": "test", "level": "MY_OH_MY", "foo": "bar"} ) assert ( "[" + dev.RED + styles.bright + dev._pad("MY_OH_MY", cr._longest_level) + styles.reset + "] " + padded + " " + styles.kv_key + "foo" + styles.reset + "=" + styles.kv_value + "bar" + styles.reset ) == rv def test_returns_colorful_styles_when_colors_true(self): """ When colors=True, returns _colorful_styles instance. """ styles = dev.ConsoleRenderer.get_default_column_styles(colors=True) assert styles is dev._colorful_styles def test_returns_plain_styles_when_colors_false(self): """ When colors=False, returns _plain_styles instance. """ styles = dev.ConsoleRenderer.get_default_column_styles(colors=False) assert styles is dev._plain_styles assert styles.reset == "" assert styles.bright == "" assert styles.level_critical == "" assert styles.kv_key == "" assert styles.kv_value == "" @pytest.mark.skipif( not dev._IS_WINDOWS or dev.colorama is not None, reason="Only relevant on Windows without colorama", ) def test_raises_system_error_on_windows_without_colorama(self): """ On Windows without colorama, raises SystemError when colors=True. """ with pytest.raises(SystemError, match="requires the colorama package"): dev.ConsoleRenderer.get_default_column_styles(colors=True) @pytest.mark.skipif( not dev._IS_WINDOWS or dev.colorama is None, reason="Only relevant on Windows with colorama", ) def test_initializes_colorama_on_windows_with_force_colors(self): """ On Windows with colorama, force_colors=True reinitializes colorama. """ with mock.patch.object( dev.colorama, "init" ) as mock_init, mock.patch.object( dev.colorama, "deinit" ) as mock_deinit: styles = dev.ConsoleRenderer.get_default_column_styles( colors=True, force_colors=True ) assert styles is dev._colorful_styles mock_deinit.assert_called_once() mock_init.assert_called_once_with(strip=False) def test_logger_name(self, cr, styles, padded): """ Logger names are appended after the event. """ rv = cr(None, None, {"event": "test", "logger": "some_module"}) assert ( padded + " [" + styles.reset + styles.bright + dev.BLUE + "some_module" + styles.reset + "]" + styles.reset ) == rv def test_logger_name_name(self, cr, padded, styles): """ It's possible to set the logger name using a "logger_name" key. """ assert ( padded + " [" + styles.reset + styles.bright + dev.BLUE + "yolo" + styles.reset + "]" + styles.reset ) == cr(None, None, {"event": "test", "logger_name": "yolo"}) def test_key_values(self, cr, styles, padded): """ Key-value pairs go sorted alphabetically to the end. """ rv = cr(None, None, {"event": "test", "key": "value", "foo": "bar"}) assert ( padded + " " + styles.kv_key + "foo" + styles.reset + "=" + styles.kv_value + "bar" + styles.reset + " " + styles.kv_key + "key" + styles.reset + "=" + styles.kv_value + "value" + styles.reset ) == rv def test_key_values_unsorted(self, styles, padded): """ Key-value pairs go in original order to the end. """ cr = dev.ConsoleRenderer(sort_keys=False) rv = cr( None, None, {"event": "test", "key": "value", "foo": "bar"}, ) assert ( padded + " " + styles.kv_key + "key" + styles.reset + "=" + styles.kv_value + "value" + styles.reset + " " + styles.kv_key + "foo" + styles.reset + "=" + styles.kv_value + "bar" + styles.reset ) == rv @pytest.mark.parametrize("wrap", [True, False]) def test_exception_rendered( self, cr, recwarn, wrap, styles, padded, monkeypatch ): """ Exceptions are rendered after a new line if they are already rendered in the event dict. A warning is emitted if exception printing is "customized". """ exc = "Traceback:\nFake traceback...\nFakeError: yolo" # Wrap the formatter to provoke the warning. if wrap: monkeypatch.setattr( cr, "_exception_formatter", lambda s, ei: dev.plain_traceback(s, ei), ) rv = cr(None, None, {"event": "test", "exception": exc}) assert (f"{padded}\n" + exc) == rv if wrap: (w,) = recwarn.list assert ( "Remove `format_exc_info` from your processor chain " "if you want pretty exceptions.", ) == w.message.args def test_stack_info(self, cr, styles, padded): """ Stack traces are rendered after a new line. """ stack = "fake stack" rv = cr(None, None, {"event": "test", "stack": stack}) assert (f"{padded}\n" + stack) == rv def test_exc_info_tuple(self, cr, styles, padded): """ If exc_info is a tuple, it is used. """ try: 0 / 0 except ZeroDivisionError: ei = sys.exc_info() rv = cr(None, None, {"event": "test", "exc_info": ei}) exc = dev._format_exception(ei) assert (f"{padded}\n" + exc) == rv def test_exc_info_bool(self, cr, styles, padded): """ If exc_info is True, it is obtained using sys.exc_info(). """ try: 0 / 0 except ZeroDivisionError: ei = sys.exc_info() rv = cr(None, None, {"event": "test", "exc_info": True}) exc = dev._format_exception(ei) assert (f"{padded}\n" + exc) == rv def test_exc_info_exception(self, cr, styles, padded): """ If exc_info is an exception, it is used by converting to a tuple. """ try: 0 / 0 except ZeroDivisionError as e: ei = e rv = cr(None, None, {"event": "test", "exc_info": ei}) exc = dev._format_exception((ei.__class__, ei, ei.__traceback__)) assert (f"{padded}\n" + exc) == rv def test_pad_event_to_param(self, styles): """ `pad_event_to` parameter works. """ rv = dev.ConsoleRenderer(42, dev._has_colors)( None, None, {"event": "test", "foo": "bar"} ) assert ( styles.bright + dev._pad("test", 42) + styles.reset + " " + styles.kv_key + "foo" + styles.reset + "=" + styles.kv_value + "bar" + styles.reset ) == rv @pytest.mark.parametrize("explicit_ei", ["tuple", "exception", False]) def test_everything(self, cr, styles, padded, explicit_ei): """ Put all cases together. """ if explicit_ei: try: 0 / 0 except ZeroDivisionError as e: if explicit_ei == "tuple": ei = sys.exc_info() elif explicit_ei == "exception": ei = e else: raise ValueError from None else: ei = True stack = "fake stack trace" ed = { "event": "test", "exc_info": ei, "key": "value", "foo": "bar", "timestamp": "13:13", "logger": "some_module", "level": "error", "stack": stack, } if explicit_ei: rv = cr(None, None, ed) else: try: 0 / 0 except ZeroDivisionError: rv = cr(None, None, ed) ei = sys.exc_info() if isinstance(ei, BaseException): ei = (ei.__class__, ei, ei.__traceback__) exc = dev._format_exception(ei) assert ( styles.timestamp + "13:13" + styles.reset + " [" + styles.level_error + styles.bright + dev._pad("error", cr._longest_level) + styles.reset + "] " + padded + " [" + styles.reset + styles.bright + dev.BLUE + "some_module" + styles.reset + "]" + styles.reset + " " + styles.kv_key + "foo" + styles.reset + "=" + styles.kv_value + "bar" + styles.reset + " " + styles.kv_key + "key" + styles.reset + "=" + styles.kv_value + "value" + styles.reset + "\n" + stack + "\n\n" + "=" * 79 + "\n" + "\n" + exc ) == rv def test_colorama_colors_false(self): """ If colors is False, don't use colors or styles ever. """ plain_cr = dev.ConsoleRenderer(colors=False) rv = plain_cr( None, None, {"event": "event", "level": "info", "foo": "bar"} ) assert dev._plain_styles is plain_cr._styles assert "[info ] event foo=bar" == rv def test_colorama_force_colors(self, styles, padded): """ If force_colors is True, use colors even if the destination is non-tty. """ cr = dev.ConsoleRenderer( colors=dev._has_colors, force_colors=dev._has_colors ) rv = cr( None, None, {"event": "test", "level": "critical", "foo": "bar"} ) assert ( "[" + dev.RED + styles.bright + dev._pad("critical", cr._longest_level) + styles.reset + "] " + padded + " " + styles.kv_key + "foo" + styles.reset + "=" + styles.kv_value + "bar" + styles.reset ) == rv assert not dev._has_colors or dev._colorful_styles is cr._styles @pytest.mark.parametrize("rns", [True, False]) def test_repr_native_str(self, rns): """ repr_native_str=False doesn't repr on native strings. "event" is never repr'ed. """ rv = dev.ConsoleRenderer(colors=False, repr_native_str=rns)( None, None, {"event": "哈", "key": 42, "key2": "哈"} ) cnt = rv.count("哈") assert 2 == cnt @pytest.mark.parametrize("repr_native_str", [True, False]) @pytest.mark.parametrize("force_colors", [True, False]) @pytest.mark.parametrize("proto", range(pickle.HIGHEST_PROTOCOL + 1)) def test_pickle(self, repr_native_str, force_colors, proto): """ ConsoleRenderer can be pickled and unpickled. """ r = dev.ConsoleRenderer( repr_native_str=repr_native_str, force_colors=force_colors ) assert r(None, None, {"event": "foo"}) == pickle.loads( pickle.dumps(r, proto) )(None, None, {"event": "foo"}) def test_no_exception(self): """ If there is no exception, don't blow up. """ r = dev.ConsoleRenderer(colors=False) assert ( "hi" == r(None, None, {"event": "hi", "exc_info": None}).rstrip() ) def test_columns_warns_about_meaningless_arguments(self, recwarn): """ If columns is set, a warning is emitted for all ignored arguments. """ dev.ConsoleRenderer( columns=[dev.Column("", lambda k, v: "")], pad_event_to=42, colors=not dev._has_colors, force_colors=True, repr_native_str=True, level_styles=dev._plain_styles, event_key="not event", timestamp_key="not timestamp", ) assert { f"The `{arg}` argument is ignored when passing `columns`." for arg in ( "pad_event_to", "colors", "force_colors", "repr_native_str", "level_styles", "event_key", "timestamp_key", ) } == {str(w.message) for w in recwarn.list} def test_detects_default_column(self): """ The default renderer is detected and removed from the columns list. """ fake_formatter = object() llcf = dev.Column("log_level", dev.LogLevelColumnFormatter(None, "")) cr = dev.ConsoleRenderer( columns=[dev.Column("", fake_formatter), llcf] ) assert fake_formatter is cr._default_column_formatter assert [llcf] == cr._columns def test_enforces_presence_of_exactly_one_default_formatter(self): """ If there is no, or more than one, default formatter, raise ValueError. """ with pytest.raises( ValueError, match="Must pass a default column formatter", ): dev.ConsoleRenderer(columns=[]) with pytest.raises( ValueError, match="Only one default column formatter allowed", ): dev.ConsoleRenderer( columns=[ dev.Column("", lambda k, v: ""), dev.Column("", lambda k, v: ""), ] ) def test_does_not_modify_styles(self): """ Instantiating ConsoleRenderer should not modify the styles passed in. Ref #643 """ styles = {"info": "something"} copy = styles.copy() dev.ConsoleRenderer(level_styles=styles) assert copy == styles def test_exception_formatter_property(self, cr): """ The exception formatter can be set and retrieved without re-instantiating ConsoleRenderer. """ sentinel = object() cr.exception_formatter = sentinel assert sentinel is cr.exception_formatter assert sentinel is cr._exception_formatter def test_sort_keys_property(self, cr): """ The sort_keys setting can be set and retrieved without re-instantiating ConsoleRenderer. """ assert cr.sort_keys is True assert cr._sort_keys is True cr.sort_keys = False assert cr.sort_keys is False assert cr._sort_keys is False cr.sort_keys = True assert cr.sort_keys is True assert cr._sort_keys is True def test_columns_property(self, cr): """ The columns property can be set and retrieved without re-instantiating ConsoleRenderer. The property also fakes the default column formatter. """ cols = [dev.Column("", lambda k, v: "")] cr.columns = cols assert cols == cr.columns assert [] == cr._columns assert cols[0].formatter == cr._default_column_formatter class TestSetExcInfo: def test_wrong_name(self): """ Do nothing if name is not exception. """ assert {} == dev.set_exc_info(None, "foo", {}) @pytest.mark.parametrize("ei", [False, None, ()]) def test_already_set(self, ei): """ Do nothing if exc_info is already set. """ assert {"exc_info": ei} == dev.set_exc_info( None, "foo", {"exc_info": ei} ) def test_set_it(self): """ Set exc_info to True if its not set and if the method name is exception. """ assert {"exc_info": True} == dev.set_exc_info(None, "exception", {}) @pytest.mark.skipif(dev.rich is not None, reason="Needs missing Rich.") def test_rich_traceback_formatter_no_rich(): """ Trying to use RichTracebackFormatter without Rich should raise an helpful error. """ with pytest.raises( ModuleNotFoundError, match="RichTracebackFormatter requires Rich to be installed", ): dev.rich_traceback(StringIO(), sys.exc_info()) @pytest.mark.skipif(dev.rich is None, reason="Needs Rich.") class TestRichTracebackFormatter: def test_default(self): """ If Rich is present, it's the default. """ assert dev.default_exception_formatter is dev.rich_traceback def test_does_not_blow_up(self, sio): """ We trust Rich to do the right thing, so we just exercise the function and check the first new line that we add manually is present. """ try: 0 / 0 except ZeroDivisionError: dev.rich_traceback(sio, sys.exc_info()) assert sio.getvalue().startswith("\n") def test_width_minus_one(self, sio): """ If width is -1, it raises a DeprecationWarning and is replaced by None to let `rich` handle it. """ rtf = dev.RichTracebackFormatter(width=-1) with pytest.deprecated_call(): try: 0 / 0 except ZeroDivisionError: rtf(sio, sys.exc_info()) assert rtf.width is None @pytest.mark.parametrize("code_width_support", [True, False]) def test_code_width_support(self, sio, code_width_support): """ If rich does not support code_width, it should not fail """ from rich.traceback import Trace tb = mock.Mock( spec=[ attr for attr in dir(dev.Traceback(Trace([]))) if (code_width_support or attr != "code_width") ] ) tb.__rich_console__.return_value = "for Python 3.8 compatibility" with mock.patch.object( dev.Traceback, "from_exception", return_value=tb ) as factory: try: 0 / 0 except ZeroDivisionError: dev.rich_traceback(sio, sys.exc_info()) assert "code_width" not in factory.call_args.kwargs if code_width_support: assert tb.code_width == 88 @pytest.mark.skipif( dev.better_exceptions is None, reason="Needs better-exceptions." ) class TestBetterTraceback: def test_default(self): """ If better-exceptions is present and Rich is NOT present, it's the default. """ assert ( dev.rich is not None or dev.default_exception_formatter is dev.better_traceback ) def test_does_not_blow_up(self): """ We trust better-exceptions to do the right thing, so we just exercise the function. """ sio = StringIO() try: 0 / 0 except ZeroDivisionError: dev.better_traceback(sio, sys.exc_info()) assert sio.getvalue().startswith("\n") class TestLogLevelColumnFormatter: def test_no_style(self): """ No level_styles means no control characters and no padding. """ assert "[critical]" == dev.LogLevelColumnFormatter(None, "foo")( "", "critical" ) class TestGetActiveConsoleRenderer: def test_ok(self): """ If there's an active ConsoleRenderer, it's returned. """ assert ( structlog.get_config()["processors"][-1] is dev.ConsoleRenderer.get_active() ) def test_no_console_renderer(self): """ If no ConsoleRenderer is configured, raise NoConsoleRendererConfiguredError. """ structlog.configure(processors=[]) with pytest.raises( structlog.exceptions.NoConsoleRendererConfiguredError ): dev.ConsoleRenderer.get_active() def test_multiple_console_renderers(self): """ If multiple ConsoleRenderers are configured, raise MultipleConsoleRenderersConfiguredError because it's most likely a bug. """ structlog.configure( processors=[dev.ConsoleRenderer(), dev.ConsoleRenderer()] ) with pytest.raises( structlog.exceptions.MultipleConsoleRenderersConfiguredError ): dev.ConsoleRenderer.get_active() class TestConsoleRendererProperties: def test_level_styles_roundtrip(self): """ The level_styles property can be set and retrieved. """ cr = dev.ConsoleRenderer(colors=True) custom = {"info": "X", "error": "Y"} cr.level_styles = custom assert cr.level_styles is custom assert cr._level_styles is custom @pytest.mark.parametrize("colors", [True, False]) def test_set_level_styles_none_resets_to_defaults(self, colors): """ Setting level_styles to None resets to defaults. """ cr = dev.ConsoleRenderer(colors=colors) cr.level_styles = {"info": "X"} cr.level_styles = None assert ( dev.ConsoleRenderer.get_default_level_styles(colors=colors) == cr._level_styles ) def test_roundtrip_pad_level(self): """ The pad_level property can be set and retrieved. """ cr = dev.ConsoleRenderer(pad_level=True) assert cr.pad_level is True assert cr._pad_level is True cr.pad_level = False assert cr.pad_level is False assert cr._pad_level is False cr.pad_level = True assert cr.pad_level is True assert cr._pad_level is True def test_roundtrip_pad_event_to(self): """ The pad_event_to property can be set and retrieved. """ cr = dev.ConsoleRenderer() assert cr.pad_event_to == dev._EVENT_WIDTH assert cr._pad_event_to == dev._EVENT_WIDTH cr.pad_event_to = 50 assert cr.pad_event_to == 50 assert cr._pad_event_to == 50 cr.pad_event_to = 20 assert cr.pad_event_to == 20 assert cr._pad_event_to == 20 def test_repr_native_str_property(self, cr): """ The repr_native_str property can be set and retrieved, and affects formatting. """ cr = dev.ConsoleRenderer(colors=False, repr_native_str=False) assert False is cr.repr_native_str assert "event key=plain" == cr( None, None, {"event": "event", "key": "plain"} ) cr.repr_native_str = True assert "event key='plain'" == cr( None, None, {"event": "event", "key": "plain"} ) def test_pad_event_deprecation_warning(self, recwarn): """ Using pad_event argument raises a deprecation warning. """ dev.ConsoleRenderer(pad_event=42) (w,) = recwarn.list assert ( "The `pad_event` argument is deprecated. Use `pad_event_to` instead." ) == w.message.args[0] assert w.category is DeprecationWarning def test_pad_event_to_param_raises_value_error(self): """ Using pad_event_to and pad_event raises a ValueError. """ with pytest.raises(ValueError): # noqa: PT011 dev.ConsoleRenderer(pad_event_to=42, pad_event=42) def test_same_value_resets_level_styles(self, cr): """ Setting colors to the same value resets the level styles to the defaults. """ val = cr.colors cr._level_styles = {"info": "X", "error": "Y"} cr.colors = cr.colors assert val is cr.colors assert ( cr._level_styles == dev.ConsoleRenderer.get_default_level_styles(colors=val) ) @pytest.mark.skipif( dev._IS_WINDOWS and dev.colorama is None, reason="Toggling colors=True requires colorama on Windows", ) def test_toggle_colors_updates_styles_and_levels(self): """ Toggling colors updates the styles and level styles to colorful styles. """ cr = dev.ConsoleRenderer(colors=False) assert cr.colors is False assert cr._colors is False assert cr._styles is dev._plain_styles assert ( cr._level_styles == dev.ConsoleRenderer.get_default_level_styles(colors=False) ) cr.colors = True assert cr.colors is True assert cr._colors is True assert cr._styles is dev._colorful_styles assert ( cr._level_styles == dev.ConsoleRenderer.get_default_level_styles(colors=True) ) @pytest.mark.skipif( dev._IS_WINDOWS and dev.colorama is None, reason="Toggling colors=True requires colorama on Windows", ) def test_toggle_colors_resets_custom_level_styles(self): """ Toggling colors resets the level styles to the defaults for the new color setting. """ custom = {"info": "X", "error": "Y"} cr = dev.ConsoleRenderer(colors=False, level_styles=custom) assert custom == cr._level_styles cr.colors = True assert ( dev.ConsoleRenderer.get_default_level_styles(colors=True) == cr._level_styles ) # And switching back follows defaults for the new setting again cr.colors = False assert ( dev.ConsoleRenderer.get_default_level_styles(colors=False) == cr._level_styles ) def test_same_force_colors_value_resets_level_styles(self, cr): """ Setting force_colors to the same value resets the level styles to the defaults. """ val = cr.force_colors cr._level_styles = {"info": "X", "error": "Y"} cr.force_colors = cr.force_colors assert val is cr.force_colors assert ( cr._level_styles == dev.ConsoleRenderer.get_default_level_styles(colors=cr.colors) ) def test_toggle_force_colors_updates_styles_and_levels(self): """ Setting force_colors to the same value resets the level styles to the defaults. """ cr = dev.ConsoleRenderer(colors=True, force_colors=False) assert cr.force_colors is False assert cr._force_colors is False assert cr._styles is dev.ConsoleRenderer.get_default_column_styles( colors=True, force_colors=False ) assert ( cr._level_styles == dev.ConsoleRenderer.get_default_level_styles(colors=True) ) cr.force_colors = True assert cr.force_colors is True assert cr._force_colors is True assert cr._styles is dev.ConsoleRenderer.get_default_column_styles( colors=True, force_colors=True ) assert ( cr._level_styles == dev.ConsoleRenderer.get_default_level_styles(colors=True) ) def test_toggle_force_colors_resets_custom_level_styles(self): """ Toggling force_colors resets the level styles to the defaults for the new force_colors setting. """ custom = {"info": "X", "error": "Y"} cr = dev.ConsoleRenderer(colors=True, level_styles=custom) assert custom == cr._level_styles cr.force_colors = True assert ( dev.ConsoleRenderer.get_default_level_styles(colors=True) == cr._level_styles ) cr.force_colors = False assert ( dev.ConsoleRenderer.get_default_level_styles(colors=True) == cr._level_styles ) structlog-25.5.0/tests/test_frames.py0000644000000000000000000001276315077624341014631 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import sys import pytest from structlog._frames import ( _find_first_app_frame_and_name, _format_exception, _format_stack, ) from .helpers import stub class TestFindFirstAppFrameAndName: def test_ignores_structlog_by_default(self): """ No matter what you pass in, structlog frames get always ignored. """ f1 = stub(f_globals={"__name__": "test"}, f_back=None) f2 = stub(f_globals={"__name__": "structlog.blubb"}, f_back=f1) f, n = _find_first_app_frame_and_name(_getframe=lambda: f2) assert (f1, "test") == (f, n) def test_ignoring_of_additional_frame_names_works(self): """ Additional names are properly ignored too. """ f1 = stub(f_globals={"__name__": "test"}, f_back=None) f2 = stub(f_globals={"__name__": "ignored.bar"}, f_back=f1) f3 = stub(f_globals={"__name__": "structlog.blubb"}, f_back=f2) f, n = _find_first_app_frame_and_name( additional_ignores=["ignored"], _getframe=lambda: f3 ) assert (f1, "test") == (f, n) def test_stacklevel(self): """ stacklevel is respected. """ f0 = stub( f_globals={"__name__": "test"}, f_back=stub(f_globals={"__name__": "too far"}, f_back=None), ) f1 = stub(f_globals={"__name__": "skipped"}, f_back=f0) f2 = stub(f_globals={"__name__": "ignored.bar"}, f_back=f1) f3 = stub(f_globals={"__name__": "structlog.blubb"}, f_back=f2) f, n = _find_first_app_frame_and_name( additional_ignores=["ignored"], stacklevel=1, _getframe=lambda: f3 ) assert (f0, "test") == (f, n) def test_stacklevel_capped(self): """ stacklevel is capped at the number of frames. """ f0 = stub(f_globals={"__name__": "test"}, f_back=None) f1 = stub(f_globals={"__name__": "skipped"}, f_back=f0) f2 = stub(f_globals={"__name__": "ignored.bar"}, f_back=f1) f3 = stub(f_globals={"__name__": "structlog.blubb"}, f_back=f2) f, n = _find_first_app_frame_and_name( additional_ignores=["ignored"], stacklevel=100, _getframe=lambda: f3, ) assert (f0, "test") == (f, n) def test_tolerates_missing_name(self): """ Use ``?`` if `f_globals` lacks a `__name__` key """ f1 = stub(f_globals={}, f_back=None) f, n = _find_first_app_frame_and_name(_getframe=lambda: f1) assert (f1, "?") == (f, n) def test_tolerates_name_explicitly_None_oneframe(self): """ Use ``?`` if `f_globals` has a `None` valued `__name__` key """ f1 = stub(f_globals={"__name__": None}, f_back=None) f, n = _find_first_app_frame_and_name(_getframe=lambda: f1) assert (f1, "?") == (f, n) def test_tolerates_name_explicitly_None_manyframe(self): """ Use ``?`` if `f_globals` has a `None` valued `__name__` key, multiple frames up. """ f1 = stub(f_globals={"__name__": None}, f_back=None) f2 = stub(f_globals={"__name__": "structlog.blubb"}, f_back=f1) f, n = _find_first_app_frame_and_name(_getframe=lambda: f2) assert (f1, "?") == (f, n) def test_tolerates_f_back_is_None(self): """ Use ``?`` if all frames are in ignored frames. """ f1 = stub(f_globals={"__name__": "structlog"}, f_back=None) f, n = _find_first_app_frame_and_name(_getframe=lambda: f1) assert (f1, "?") == (f, n) @pytest.fixture def exc_info(): """ Fake a valid exc_info. """ try: raise ValueError except ValueError: return sys.exc_info() class TestFormatException: def test_returns_str(self, exc_info): """ Always returns a native string. """ assert isinstance(_format_exception(exc_info), str) def test_formats(self, exc_info): """ The passed exc_info is formatted. """ assert _format_exception(exc_info).startswith( "Traceback (most recent call last):\n" ) def test_no_trailing_nl(self, exc_info, monkeypatch): """ Trailing newlines are snipped off but if the string does not contain one nothing is removed. """ from structlog._frames import traceback monkeypatch.setattr( traceback, "print_exception", lambda *a: a[-1].write("foo") ) assert "foo" == _format_exception(exc_info) class TestFormatStack: def test_returns_str(self): """ Always returns a native string. """ assert isinstance(_format_stack(sys._getframe()), str) def test_formats(self): """ The passed stack is formatted. """ assert _format_stack(sys._getframe()).startswith( "Stack (most recent call last):\n" ) def test_no_trailing_nl(self, monkeypatch): """ Trailing newlines are snipped off but if the string does not contain one nothing is removed. """ from structlog._frames import traceback monkeypatch.setattr( traceback, "print_stack", lambda frame, file: file.write("foo") ) assert _format_stack(sys._getframe()).endswith("foo") structlog-25.5.0/tests/test_generic.py0000644000000000000000000000321415077624341014757 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import pickle import pytest import time_machine from structlog._config import _CONFIG from structlog._generic import BoundLogger from structlog.testing import ReturnLogger class TestLogger: def log(self, msg): return "log", msg def gol(self, msg): return "gol", msg class TestGenericBoundLogger: def test_caches(self): """ __getattr__() gets called only once per logger method. """ b = BoundLogger( ReturnLogger(), _CONFIG.default_processors, _CONFIG.default_context_class(), ) assert "msg" not in b.__dict__ b.msg("foo") assert "msg" in b.__dict__ @pytest.mark.parametrize("proto", range(3, pickle.HIGHEST_PROTOCOL + 1)) @time_machine.travel("2023-05-22 17:00", tick=False) def test_pickle(self, proto): """ Can be pickled and unpickled. """ b = BoundLogger( ReturnLogger(), _CONFIG.default_processors, _CONFIG.default_context_class(), ).bind(x=1) assert b.info("hi") == pickle.loads(pickle.dumps(b, proto)).info("hi") def test_deepcopy(self): """ __getattr__ returns None for '__deepcopy__' """ b = BoundLogger( ReturnLogger(), _CONFIG.default_processors, _CONFIG.default_context_class(), ) assert b.__deepcopy__ is None structlog-25.5.0/tests/test_native.py0000644000000000000000000002544315077624341014641 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import logging import pickle import pytest from structlog import make_filtering_bound_logger from structlog._log_levels import LEVEL_TO_NAME from structlog._native import _nop from structlog.contextvars import ( bind_contextvars, clear_contextvars, merge_contextvars, ) @pytest.fixture(name="bl") def _bl(cl): return make_filtering_bound_logger(logging.INFO)(cl, [], {}) class TestNativeFilteringLogger: def test_is_enabled_for(self, bl): """ is_enabled_for returns True if the log level is enabled. """ assert bl.is_enabled_for(20) assert bl.is_enabled_for(logging.INFO) assert not bl.is_enabled_for(19) assert not bl.is_enabled_for(logging.DEBUG) def test_get_effective_level(self, bl): """ get_effective_level returns the log level. """ assert 20 == logging.INFO == bl.get_effective_level() def test_exact_level(self, bl, cl): """ if log level is exactly the min_level, log. """ bl.info("yep") assert [("info", (), {"event": "yep"})] == cl.calls async def test_async_exact_level(self, bl, cl): """ if log level is exactly the min_level, log. """ await bl.ainfo("yep") assert [("info", (), {"event": "yep"})] == cl.calls def test_one_below(self, bl, cl): """ if log level is below the min_level, don't log. """ bl.debug("nope") assert [] == cl.calls async def test_async_one_below(self, bl, cl): """ if log level is below the min_level, don't log. """ await bl.adebug("nope") assert [] == cl.calls def test_filtered_interp(self, bl, cl): """ Passing interpolation args works if the log entry is filtered out. """ bl.debug("hello %s!", "world") assert [] == cl.calls async def test_async_filtered_interp(self, bl, cl): """ Passing interpolation args works if the log entry is filtered out. """ await bl.adebug("hello %s!", "world") assert [] == cl.calls def test_no_args(self, bl, cl): """ If no args are passed, don't attempt interpolation. See also #473 """ bl.info(42) assert 42 == cl.calls[0][2]["event"] async def test_async_no_args(self, bl, cl): """ If no args are passed, don't attempt interpolation. See also #473 """ await bl.ainfo(42) assert 42 == cl.calls[0][2]["event"] def test_log_exact_level(self, bl, cl): """ if log level is exactly the min_level, log. """ bl.log(logging.INFO, "yep") assert [("info", (), {"event": "yep"})] == cl.calls async def test_alog_exact_level(self, bl, cl): """ if log level is exactly the min_level, log. """ await bl.alog(logging.INFO, "yep") assert [("info", (), {"event": "yep"})] == cl.calls def test_log_one_below(self, bl, cl): """ if log level is below the min_level, don't log. """ bl.log(logging.DEBUG, "nope") assert [] == cl.calls async def test_alog_one_below(self, bl, cl): """ if log level is below the min_level, don't log. """ await bl.alog(logging.DEBUG, "nope") assert [] == cl.calls async def test_alog_no_args(self, bl, cl): """ If no args are passed, interpolation is not attempted. See also #473 """ await bl.alog(logging.INFO, 42) assert 42 == cl.calls[0][2]["event"] def test_log_interp(self, bl, cl): """ Interpolation happens if args are passed. """ bl.log(logging.INFO, "answer is %d.", 42) assert "answer is 42." == cl.calls[0][2]["event"] def test_log_interp_dict(self, bl, cl): """ Dict-based interpolation happens if a mapping is passed. """ bl.log(logging.INFO, "answer is %(answer)d.", {"answer": 42}) assert "answer is 42." == cl.calls[0][2]["event"] async def test_alog_interp(self, bl, cl): """ Interpolation happens if args are passed. """ await bl.alog(logging.INFO, "answer is %d.", 42) assert "answer is 42." == cl.calls[0][2]["event"] async def test_alog_interp_dict(self, bl, cl): """ Dict-based interpolation happens if a mapping is passed. """ await bl.alog(logging.INFO, "answer is %(answer)d.", {"answer": 42}) assert "answer is 42." == cl.calls[0][2]["event"] def test_filter_bound_below_missing_event_string(self, bl): """ Missing event arg causes exception below min_level. """ with pytest.raises(TypeError) as exc_info: bl.debug(missing="event string!") assert exc_info.type is TypeError message = "missing 1 required positional argument: 'event'" assert message in exc_info.value.args[0] def test_filter_bound_exact_missing_event_string(self, bl): """ Missing event arg causes exception even at min_level. """ with pytest.raises(TypeError) as exc_info: bl.info(missing="event string!") assert exc_info.type is TypeError message = "missing 1 required positional argument: 'event'" assert message in exc_info.value.args[0] def test_exception(self, bl, cl): """ exception ensures that exc_info is set to True, unless it's already set. """ bl.exception("boom") assert [("error", (), {"event": "boom", "exc_info": True})] == cl.calls async def test_async_exception(self, bl, cl): """ aexception sets exc_info to current exception info, if it's not already set. """ try: raise Exception("boom") except Exception as e: await bl.aexception("foo") exc = e assert 1 == len(cl.calls) assert isinstance(cl.calls[0][2]["exc_info"], tuple) assert exc == cl.calls[0][2]["exc_info"][1] def test_exception_positional_args(self, bl, cl): """ exception allows for positional args """ bl.exception("%s %s", "boom", "bastic") assert [ ("error", (), {"event": "boom bastic", "exc_info": True}) ] == cl.calls def test_exception_dict_args(self, bl, cl): """ exception allows for dict-based args """ bl.exception( "%(action)s %(what)s", {"action": "boom", "what": "bastic"} ) assert [ ("error", (), {"event": "boom bastic", "exc_info": True}) ] == cl.calls async def test_aexception_positional_args(self, bl, cl): """ aexception allows for positional args """ await bl.aexception("%s %s", "boom", "bastic") assert 1 == len(cl.calls) assert "boom bastic" == cl.calls[0][2]["event"] async def test_aexception_dict_args(self, bl, cl): """ aexception allows for dict-based args """ await bl.aexception( "%(action)s %(what)s", {"action": "boom", "what": "bastic"} ) assert 1 == len(cl.calls) assert "boom bastic" == cl.calls[0][2]["event"] async def test_async_exception_true(self, bl, cl): """ aexception replaces exc_info with current exception info, if exc_info is True. """ try: raise Exception("boom") except Exception as e: await bl.aexception("foo", exc_info=True) exc = e assert 1 == len(cl.calls) assert isinstance(cl.calls[0][2]["exc_info"], tuple) assert exc is cl.calls[0][2]["exc_info"][1] def test_exception_passed(self, bl, cl): """ exception if exc_info has a value, exception doesn't tamper with it. """ bl.exception("boom", exc_info=42) assert [("error", (), {"event": "boom", "exc_info": 42})] == cl.calls async def test_async_exception_passed(self, bl, cl): """ exception if exc_info has a value (other than True), exception doesn't tamper with it. """ await bl.aexception("boom", exc_info=42) assert [("error", (), {"event": "boom", "exc_info": 42})] == cl.calls def test_exception_pass_exception(self, bl, cl): """ If an Exception is passed for the event, don't explode. Not a documented feature, but a regression for some people. See #473. """ try: raise Exception("foo") except Exception as e: bl.exception(e) exc = e assert exc is cl.calls[0][2]["event"] @pytest.mark.parametrize("level", tuple(LEVEL_TO_NAME.keys())) def test_pickle(self, level): """ FilteringBoundLogger are pickleable. """ bl = make_filtering_bound_logger(level) assert bl == pickle.loads(pickle.dumps(bl)) def test_pos_args(self, bl, cl): """ Positional arguments are used for string interpolation. """ bl.info("hello %s -- %d!", "world", 42) assert [("info", (), {"event": "hello world -- 42!"})] == cl.calls async def test_async_pos_args(self, bl, cl): """ Positional arguments are used for string interpolation. """ await bl.ainfo("hello %s -- %d!", "world", 42) assert [("info", (), {"event": "hello world -- 42!"})] == cl.calls @pytest.mark.parametrize( ("meth", "args"), [ ("aexception", ("ev",)), ("ainfo", ("ev",)), ("alog", (logging.INFO, "ev")), ], ) async def test_async_contextvars_merged(self, meth, args, cl): """ Contextvars are merged into the event dict. """ clear_contextvars() bl = make_filtering_bound_logger(logging.INFO)( cl, [merge_contextvars], {} ) bind_contextvars(context_included="yep") await getattr(bl, meth)(*args) assert len(cl.calls) == 1 assert "context_included" in cl.calls[0].kwargs def test_log_percent(self, bl, cl): """ As long as there's no positional args passed, logging % is possible. """ bl.info("hey %! %%!") assert [("info", (), {"event": "hey %! %%!"})] == cl.calls def test_log_level_str(self): """ *min_level* can be a string and the case doesn't matter. """ bl = make_filtering_bound_logger("wArNiNg") assert bl.warning is not _nop assert bl.info is _nop structlog-25.5.0/tests/test_output.py0000644000000000000000000002216015077624341014704 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import copy import pickle from io import BytesIO, StringIO import pytest from structlog import ( BytesLogger, BytesLoggerFactory, PrintLogger, PrintLoggerFactory, WriteLogger, WriteLoggerFactory, ) from structlog._output import WRITE_LOCKS, stderr, stdout from .helpers import stdlib_log_methods class TestLoggers: """ Tests common to the Print and WriteLoggers. """ @pytest.fixture(name="logger_cls", params=(WriteLogger, PrintLogger)) @staticmethod def _logger_cls(request): return request.param def test_prints_to_stdout_by_default(self, logger_cls, capsys): """ Instantiating without arguments gives conveniently a logger to standard out. """ logger_cls().msg("hello") out, err = capsys.readouterr() assert "hello\n" == out assert "" == err def test_prints_to_correct_file(self, logger_cls, tmp_path, capsys): """ Supplied files are respected. """ p = tmp_path / "test.log" with p.open("w") as f: logger_cls(f).msg("hello") out, err = capsys.readouterr() assert "" == out == err assert "hello\n" == p.read_text() def test_lock(self, logger_cls, sio): """ Creating a logger adds a lock to WRITE_LOCKS. """ assert sio not in WRITE_LOCKS logger_cls(sio) assert sio in WRITE_LOCKS @pytest.mark.parametrize("method", stdlib_log_methods) def test_stdlib_methods_support(self, logger_cls, method, sio): """ Print/WriteLogger implements methods of stdlib loggers. """ getattr(logger_cls(sio), method)("hello") assert "hello" in sio.getvalue() @pytest.mark.parametrize("file", [None, stdout, stderr]) @pytest.mark.parametrize("proto", range(pickle.HIGHEST_PROTOCOL + 1)) def test_pickle(self, logger_cls, file, proto): """ Can be pickled and unpickled for stdout and stderr. Can't compare output because capsys et all would confuse the logic. """ pl = logger_cls(file=file) rv = pickle.loads(pickle.dumps(pl, proto)) assert pl._file is rv._file assert pl._lock is rv._lock @pytest.mark.parametrize("proto", range(pickle.HIGHEST_PROTOCOL + 1)) def test_pickle_not_stdout_stderr(self, logger_cls, tmpdir, proto): """ Loggers with different files than stdout/stderr raise a PickingError. """ f = tmpdir.join("file.log") f.write("") pl = logger_cls(file=f.open()) with pytest.raises(pickle.PicklingError, match=r"Only (.+)Loggers to"): pickle.dumps(pl, proto) def test_deepcopy(self, logger_cls, capsys): """ Deepcopied logger works. """ copied_logger = copy.deepcopy(logger_cls()) copied_logger.msg("hello") out, err = capsys.readouterr() assert "hello\n" == out assert "" == err def test_deepcopy_no_stdout(self, logger_cls, tmp_path): """ Only loggers that log to stdout or stderr can be deepcopy-ed. """ p = tmp_path / "log.txt" with p.open(mode="w") as f: logger = logger_cls(f) logger.msg("hello") with pytest.raises(copy.error): copy.deepcopy(logger) assert "hello\n" == p.read_text() def test_repr(self, logger_cls): """ __repr__ makes sense. """ assert repr(logger_cls()).startswith(f"<{logger_cls.__name__}(file=") def test_stdout_monkeypatch(self, monkeypatch, capsys): """ If stdout gets monkeypatched, the new instance receives the output. """ import sys p = PrintLogger() new_stdout = StringIO() monkeypatch.setattr(sys, "stdout", new_stdout) p.msg("hello") out, err = capsys.readouterr() assert "hello\n" == new_stdout.getvalue() assert "" == out assert "" == err class TestPrintLoggerFactory: def test_does_not_cache(self): """ Due to doctest weirdness, we must not reuse PrintLoggers. """ f = PrintLoggerFactory() assert f() is not f() def test_passes_file(self): """ If a file is passed to the factory, it get passed on to the logger. """ pl = PrintLoggerFactory(stderr)() assert stderr is pl._file def test_ignores_args(self): """ PrintLogger doesn't take positional arguments. If any are passed to the factory, they are not passed to the logger. """ PrintLoggerFactory()(1, 2, 3) class TestWriteLoggerFactory: def test_does_not_cache(self): """ Due to doctest weirdness, we must not reuse WriteLoggers. """ f = WriteLoggerFactory() assert f() is not f() def test_passes_file(self): """ If a file is passed to the factory, it get passed on to the logger. """ pl = WriteLoggerFactory(stderr)() assert stderr is pl._file def test_ignores_args(self): """ WriteLogger doesn't take positional arguments. If any are passed to the factory, they are not passed to the logger. """ WriteLoggerFactory()(1, 2, 3) class TestBytesLogger: def test_prints_to_stdout_by_default(self, capsys): """ Instantiating without arguments gives conveniently a logger to standard out. """ BytesLogger().msg(b"hell\xc3\xb6") out, err = capsys.readouterr() assert "hellö\n" == out assert "" == err def test_prints_to_correct_file(self, tmp_path, capsys): """ Supplied files are respected. """ p = tmp_path / "test.log" with p.open("wb") as f: BytesLogger(f).msg(b"hello") out, err = capsys.readouterr() assert "" == out == err assert "hello\n" == p.read_text() def test_repr(self): """ __repr__ makes sense. """ assert repr(BytesLogger()).startswith(" dict[str, Any]: """ A dict to be passed in the `extra` parameter of the `logging` module's log methods. """ return { "this": "is", "some": "extra values", "x_int": 4, "x_bool": True, } @pytest.fixture(name="extra_dict") def extra_dict_fixture(): return extra_dict() class TestExtraAdder: @pytest.mark.parametrize( ("allow", "misses"), [ (None, None), ({}, None), *[({key}, None) for key in extra_dict()], ({"missing"}, {"missing"}), ({"missing", "keys"}, {"missing"}), ({"this", "x_int"}, None), ], ) def test_add_extra( self, make_log_record: Callable[[], logging.LogRecord], extra_dict: dict[str, Any], allow: Collection[str] | None, misses: set[str] | None, ): """ Extra attributes of a LogRecord object are added to the event dict. """ record: logging.LogRecord = make_log_record() record.__dict__.update(extra_dict) event_dict = {"_record": record, "ed_key": "ed_value"} expected = self._copy_allowed(event_dict, extra_dict, allow) if allow is None: actual = ExtraAdder()(None, None, event_dict) assert expected == actual actual = ExtraAdder(allow)(None, None, event_dict) assert expected == actual if misses: assert misses.isdisjoint(expected.keys()) def test_no_record(self): """ If the event_dict has no LogRecord, do nothing. """ actual = ExtraAdder()(None, None, {}) assert {} == actual @pytest.mark.parametrize( ("allow", "misses"), [ (None, None), ({}, None), *[({key}, None) for key in extra_dict()], ({"missing"}, {"missing"}), ({"missing", "keys"}, {"missing"}), ({"this", "x_int"}, None), ], ) def test_add_extra_e2e( self, extra_dict: dict[str, Any], allow: Collection[str] | None, misses: set[str] | None, ): """ Values passed in the `extra` parameter of the `logging` module's log methods pass through to log output. """ logger = logging.Logger(sys._getframe().f_code.co_name) string_io = StringIO() handler = logging.StreamHandler(string_io) formatter = ProcessorFormatter( foreign_pre_chain=[ExtraAdder(allow)], processors=[JSONRenderer()], ) handler.setFormatter(formatter) handler.setLevel(0) logger.addHandler(handler) logger.setLevel(0) logging.warning("allow = %s", allow) # noqa: LOG015 event_dict = {"event": "Some text"} expected = self._copy_allowed(event_dict, extra_dict, allow) logger.info("Some %s", "text", extra=extra_dict) actual = { key: value for key, value in json.loads(string_io.getvalue()).items() if not key.startswith("_") } assert expected == actual if misses: assert misses.isdisjoint(expected.keys()) @classmethod def _copy_allowed( cls, event_dict: EventDict, extra_dict: dict[str, Any], allow: Collection[str] | None, ) -> EventDict: if allow is None: return {**event_dict, **extra_dict} return { **event_dict, **{ key: value for key, value in extra_dict.items() if key in allow }, } @pytest.fixture(name="stdlib_logger") def _stdlib_logger(): logger = logging.getLogger("test_logger") logger.setLevel(logging.DEBUG) yield logger logging.basicConfig() class TestRenderToLogArgsAndKwargs: def test_default(self, stdlib_logger: logging.Logger): """ Passes `event` key from `event_dict` in the first positional argument and handles otherwise empty `event_dict`. """ method_name = "debug" event = "message" args, kwargs = render_to_log_args_and_kwargs( stdlib_logger, method_name, {"event": event} ) assert (event,) == args assert {} == kwargs with patch.object(stdlib_logger, "_log") as mock_log: getattr(stdlib_logger, method_name)(*args, **kwargs) mock_log.assert_called_once_with(logging.DEBUG, event, ()) def test_pass_remaining_event_dict_as_extra( self, stdlib_logger: logging.Logger, event_dict: dict[str, Any] ): """ Passes remaining `event_dict` as `extra`. """ expected_extra = event_dict.copy() method_name = "info" event = "message" event_dict["event"] = event args, kwargs = render_to_log_args_and_kwargs( stdlib_logger, method_name, event_dict ) assert (event,) == args assert {"extra": expected_extra} == kwargs with patch.object(stdlib_logger, "_log") as mock_log: getattr(stdlib_logger, method_name)(*args, **kwargs) mock_log.assert_called_once_with( logging.INFO, event, (), extra=expected_extra ) def test_pass_positional_args_from_event_dict_as_args( self, stdlib_logger: logging.Logger, event_dict: dict[str, Any] ): """ Passes items from "positional_args" key from `event_dict` as positional arguments. """ expected_extra = event_dict.copy() method_name = "warning" event = "message: a = %s, b = %d" positional_args = ("foo", 123) event_dict["event"] = event event_dict["positional_args"] = positional_args args, kwargs = render_to_log_args_and_kwargs( stdlib_logger, method_name, event_dict ) assert (event, *(positional_args)) == args assert {"extra": expected_extra} == kwargs with patch.object(stdlib_logger, "_log") as mock_log: getattr(stdlib_logger, method_name)(*args, **kwargs) mock_log.assert_called_once_with( logging.WARNING, event, positional_args, extra=expected_extra ) def test_pass_kwargs_from_event_dict_as_kwargs( self, stdlib_logger: logging.Logger, event_dict: dict[str, Any] ): """ Passes "exc_info", "stack_info", and "stacklevel" keys from `event_dict` as keyword arguments. """ expected_extra = event_dict.copy() method_name = "info" event = "message" exc_info = True stack_info = False stacklevel = 2 event_dict["event"] = event event_dict["exc_info"] = exc_info event_dict["stack_info"] = stack_info event_dict["stacklevel"] = stacklevel args, kwargs = render_to_log_args_and_kwargs( stdlib_logger, method_name, event_dict ) assert (event,) == args assert { "exc_info": exc_info, "stack_info": stack_info, "stacklevel": stacklevel, "extra": expected_extra, } == kwargs with patch.object(stdlib_logger, "_log") as mock_log: getattr(stdlib_logger, method_name)(*args, **kwargs) mock_log.assert_called_once_with( logging.INFO, event, (), exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=expected_extra, ) def test_integration( self, stdlib_logger: logging.Logger, event_dict: dict[str, Any] ): """ `render_to_log_args_and_kwargs` with a wrapped logger calls the stdlib logger correctly. Reserved stdlib keyword arguments are in `logging.Logger._log`. https://github.com/python/cpython/blob/60403a5409ff2c3f3b07dd2ca91a7a3e096839c7/Lib/logging/__init__.py#L1640 """ event = "message: a = %s, b = %d" arg_1 = "foo" arg_2 = 123 exc_info = False stack_info = True stacklevel = 3 struct_logger = wrap_logger( stdlib_logger, processors=[render_to_log_args_and_kwargs], wrapper_class=BoundLogger, ) with patch.object(stdlib_logger, "_log") as mock_log: struct_logger.info( event, arg_1, arg_2, exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, **event_dict, ) mock_log.assert_called_once_with( logging.INFO, event, (arg_1, arg_2), exc_info=exc_info, stack_info=stack_info, stacklevel=stacklevel, extra=event_dict, ) class TestRenderToLogKwargs: def test_default(self, stdlib_logger): """ Translates `event` to `msg` and handles otherwise empty `event_dict`s. """ d = render_to_log_kwargs(None, None, {"event": "message"}) assert {"msg": "message", "extra": {}} == d # now check stdlib logger likes those kwargs with patch.object(stdlib_logger, "_log") as mock_log: stdlib_logger.info(**d) mock_log.assert_called_once_with(logging.INFO, "message", (), extra={}) def test_add_extra_event_dict(self, event_dict, stdlib_logger): """ Adds all remaining data from `event_dict` into `extra`. """ event_dict["event"] = "message" d = render_to_log_kwargs(None, None, event_dict) assert {"msg": "message", "extra": event_dict} == d # now check stdlib logger likes those kwargs with patch.object(stdlib_logger, "_log") as mock_log: stdlib_logger.info(**d) mock_log.assert_called_once_with( logging.INFO, "message", (), extra=event_dict ) def test_handles_special_kw(self, event_dict, stdlib_logger): """ "exc_info", "stack_info", and "stacklevel" aren't passed as extras. Cf. https://github.com/hynek/structlog/issues/424 """ del event_dict["a"] # needs a repr event_dict["event"] = "message" event_dict["exc_info"] = True event_dict["stack_info"] = False event_dict["stacklevel"] = 1 event_dict["stackLevel"] = 1 # not a reserved kw d = render_to_log_kwargs(None, None, event_dict) expected = { "msg": "message", "exc_info": True, "stack_info": False, "stacklevel": 1, "extra": { "b": [3, 4], "x": 7, "y": "test", "z": (1, 2), "stackLevel": 1, }, } assert expected == d # now check stdlib logger likes those kwargs with patch.object(stdlib_logger, "_log") as mock_log: stdlib_logger.info(**d) expected.pop("msg") mock_log.assert_called_once_with( logging.INFO, "message", (), **expected ) def test_integration_special_kw(self, event_dict, stdlib_logger): """ render_to_log_kwargs with a wrapped logger calls the stdlib logger correctly reserved stdlib keywords are in logging.Logger._log https://github.com/python/cpython/blob/ae7b17673f29efe17b416cbcfbf43b5b3ff5977c/Lib/logging/__init__.py#L1632 """ expected = { "msg": "message", "exc_info": True, "stack_info": False, "stacklevel": 1, "extra": {**event_dict}, } event_dict["exc_info"] = True event_dict["stack_info"] = False event_dict["stacklevel"] = 1 struct_logger = wrap_logger( stdlib_logger, processors=[render_to_log_kwargs], ) # now check struct logger passes those kwargs to stdlib with patch.object(stdlib_logger, "_log") as mock_log: struct_logger.info("message", **event_dict) expected.pop("msg") mock_log.assert_called_once_with( logging.INFO, "message", (), **expected ) @pytest.fixture(name="configure_for_processor_formatter") def _configure_for_processor_formatter(): """ Configure structlog to use ProcessorFormatter. Reset logging setting after the test (structlog is reset automatically before all tests). """ configure( processors=[add_log_level, ProcessorFormatter.wrap_for_formatter], logger_factory=LoggerFactory(), wrapper_class=BoundLogger, ) yield logging.basicConfig() def configure_logging( pre_chain, logger=None, pass_foreign_args=False, renderer=ConsoleRenderer(colors=False), # noqa: B008 ): """ Configure logging to use ProcessorFormatter. Return a list that is filled with event dicts form calls. """ event_dicts = [] def capture(_, __, ed): event_dicts.append(ed.copy()) return ed logging.config.dictConfig( { "version": 1, "disable_existing_loggers": False, "formatters": { "plain": { "()": ProcessorFormatter, "processors": [ capture, ProcessorFormatter.remove_processors_meta, renderer, ], "foreign_pre_chain": pre_chain, "format": "%(message)s [in %(funcName)s]", "logger": logger, "pass_foreign_args": pass_foreign_args, } }, "handlers": { "default": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "plain", } }, "loggers": { "": { "handlers": ["default"], "level": "DEBUG", "propagate": True, } }, } ) return event_dicts @pytest.mark.usefixtures("configure_for_processor_formatter") class TestProcessorFormatter: """ These are all integration tests because they're all about integration. """ def test_foreign_delegate(self, capsys): """ If foreign_pre_chain is None, non-structlog log entries are delegated to logging. The processor chain's event dict is invoked with `_from_structlog=False` """ calls = configure_logging(None) logging.getLogger().warning("foo") assert ("", "foo [in test_foreign_delegate]\n") == capsys.readouterr() assert calls[0]["_from_structlog"] is False assert isinstance(calls[0]["_record"], logging.LogRecord) def test_clears_args(self, capsys): """ We render our log records before sending it back to logging. Therefore we must clear `LogRecord.args` otherwise the user gets an `TypeError: not all arguments converted during string formatting.` if they use positional formatting in stdlib logging. """ configure_logging(None) logging.getLogger().warning("hello %s.", "world") assert ( "", "hello world. [in test_clears_args]\n", ) == capsys.readouterr() def test_pass_foreign_args_true_sets_positional_args_key(self): """ If `pass_foreign_args` is `True` we set the `positional_args` key in the `event_dict` before clearing args. """ test_processor = call_recorder(lambda _, __, event_dict: event_dict) configure_logging((test_processor,), pass_foreign_args=True) positional_args = {"foo": "bar"} logging.getLogger().info("okay %(foo)s", positional_args) event_dict = test_processor.call_args_list[0].args[2] assert "positional_args" in event_dict assert positional_args == event_dict["positional_args"] def test_log_dict(self, capsys): """ dicts can be logged with std library loggers. """ configure_logging(None) logging.getLogger().warning({"foo": "bar"}) assert ( "", "{'foo': 'bar'} [in test_log_dict]\n", ) == capsys.readouterr() def test_foreign_pre_chain(self, capsys): """ If foreign_pre_chain is an iterable, it's used to pre-process non-structlog log entries. """ configure_logging([add_log_level]) logging.getLogger().warning("foo") assert ( "", "[warning ] foo [in test_foreign_pre_chain]\n", ) == capsys.readouterr() def test_foreign_pre_chain_add_logger_name(self, capsys): """ foreign_pre_chain works with add_logger_name processor. """ configure_logging((add_logger_name,)) logging.getLogger("sample-name").warning("foo") assert ( "", "foo [sample-name] [in test_foreign_pr" "e_chain_add_logger_name]\n", ) == capsys.readouterr() def test_foreign_chain_can_pass_dictionaries_without_excepting( self, capsys ): """ If a foreign logger passes a dictionary to a logging function, check we correctly identify that it did not come from structlog. """ configure_logging(None) configure( processors=[ProcessorFormatter.wrap_for_formatter], logger_factory=LoggerFactory(), wrapper_class=BoundLogger, ) logging.getLogger().warning({"foo": "bar"}) assert ( "", "{'foo': 'bar'} [in " "test_foreign_chain_can_pass_dictionaries_without_excepting]\n", ) == capsys.readouterr() def test_foreign_pre_chain_gets_exc_info(self): """ If non-structlog record contains exc_info, foreign_pre_chain functions have access to it. """ test_processor = call_recorder(lambda _, __, event_dict: event_dict) configure_logging((test_processor,), renderer=KeyValueRenderer()) try: raise RuntimeError("oh no") except Exception: logging.getLogger().exception("okay") event_dict = test_processor.call_args_list[0].args[2] assert "exc_info" in event_dict assert isinstance(event_dict["exc_info"], tuple) def test_foreign_pre_chain_sys_exc_info(self): """ If a foreign_pre_chain function accesses sys.exc_info(), ProcessorFormatter should not have changed it. """ class MyError(Exception): pass def add_excinfo(logger, log_method, event_dict): event_dict["exc_info"] = sys.exc_info() return event_dict test_processor = call_recorder(lambda _, __, event_dict: event_dict) configure_logging( (add_excinfo, test_processor), renderer=KeyValueRenderer() ) try: raise MyError("oh no") except Exception: logging.getLogger().error("okay") event_dict = test_processor.call_args_list[0].args[2] assert MyError is event_dict["exc_info"][0] def test_other_handlers_get_original_record(self): """ Logging handlers that come after the handler with ProcessorFormatter should receive original, unmodified record. """ configure_logging(None) handler1 = logging.StreamHandler() handler1.setFormatter(ProcessorFormatter(JSONRenderer())) handler2 = stub( handle=call_recorder(lambda record: None), level=logging.INFO, ) logger = logging.getLogger() logger.addHandler(handler1) logger.addHandler(handler2) logger.info("meh") assert 1 == len(handler2.handle.call_args_list) handler2_record = handler2.handle.call_args_list[0].args[0] assert "meh" == handler2_record.msg @pytest.mark.parametrize("keep", [True, False]) def test_formatter_unsets_exc_info(self, capsys, keep): """ Stack traces doesn't get printed outside of the json document when keep_exc_info are set to False but preserved if set to True. """ configure_logging(None) logger = logging.getLogger() def format_exc_info_fake(logger, name, event_dict): del event_dict["exc_info"] event_dict["exception"] = "Exception!" return event_dict formatter = ProcessorFormatter( processor=JSONRenderer(), keep_stack_info=keep, keep_exc_info=keep, foreign_pre_chain=[format_exc_info_fake], ) logger.handlers[0].setFormatter(formatter) try: raise RuntimeError("oh no") except Exception: logging.getLogger().exception("seen worse") out, err = capsys.readouterr() assert "" == out if keep is False: assert ( '{"event": "seen worse", "exception": "Exception!"}\n' ) == err else: assert "Traceback (most recent call last):" in err @pytest.mark.parametrize("keep", [True, False]) def test_formatter_unsets_stack_info(self, capsys, keep): """ Stack traces doesn't get printed outside of the json document when keep_stack_info are set to False but preserved if set to True. """ configure_logging(None) logger = logging.getLogger() formatter = ProcessorFormatter( processor=JSONRenderer(), keep_stack_info=keep, keep_exc_info=keep, foreign_pre_chain=[], ) logger.handlers[0].setFormatter(formatter) logging.getLogger().warning("have a stack trace", stack_info=True) out, err = capsys.readouterr() assert "" == out if keep is False: assert 1 == err.count("Stack (most recent call last):") else: assert 2 == err.count("Stack (most recent call last):") def test_native(self, capsys): """ If the log entry comes from structlog, it's unpackaged and processed. """ eds = configure_logging(None) get_logger().warning("foo") assert ( "", "[warning ] foo [in test_native]\n", ) == capsys.readouterr() assert eds[0]["_from_structlog"] is True assert isinstance(eds[0]["_record"], logging.LogRecord) def test_native_logger(self, capsys): """ If the log entry comes from structlog, it's unpackaged and processed. """ logger = logging.getLogger() eds = configure_logging(None, logger=logger) get_logger().warning("foo") assert ( "", "[warning ] foo [in test_native_logger]\n", ) == capsys.readouterr() assert eds[0]["_from_structlog"] is True assert isinstance(eds[0]["_record"], logging.LogRecord) def test_foreign_pre_chain_filter_by_level(self, capsys): """ foreign_pre_chain works with filter_by_level processor. """ logger = logging.getLogger() configure_logging([filter_by_level], logger=logger) configure( processors=[ProcessorFormatter.wrap_for_formatter], logger_factory=LoggerFactory(), wrapper_class=BoundLogger, ) logger.warning("foo") assert ( "", "foo [in test_foreign_pre_chain_filter_by_level]\n", ) == capsys.readouterr() def test_processor_and_processors(self): """ Passing both processor and processors raises a TypeError. """ with pytest.raises(TypeError, match="mutually exclusive"): ProcessorFormatter(processor=1, processors=[1]) def test_no_renderer(self): """ Passing neither processor nor processors raises a TypeError. """ with pytest.raises(TypeError, match="must be passed"): ProcessorFormatter() def test_remove_processors_meta(self): """ remove_processors_meta removes _record and _from_structlog. And only them. """ assert {"foo": "bar"} == ProcessorFormatter.remove_processors_meta( None, None, {"foo": "bar", "_record": "foo", "_from_structlog": True}, ) def test_non_string_message_warning(self): """ A warning is raised if the last processor in ProcessorFormatter.processors doesn't return a string. """ configure_logging(None) logger = logging.getLogger() formatter = ProcessorFormatter( processors=[lambda *args, **kwargs: {"foo": "bar"}], ) logger.handlers[0].setFormatter(formatter) with pytest.warns( RuntimeWarning, match="The last processor in ProcessorFormatter.processors must return a string", ): logger.info("baz") def test_logrecord_exc_info(self): """ LogRecord.exc_info is set consistently for structlog and non-structlog log records. """ configure_logging(None) # This doesn't test ProcessorFormatter itself directly, but it's # relevant to setups where ProcessorFormatter is used, i.e. where # handlers will receive LogRecord objects that come from both structlog # and non-structlog loggers. records: Dict[ # noqa: UP006 - dict isn't generic until Python 3.9 str, logging.LogRecord ] = {} class DummyHandler(logging.Handler): def emit(self, record): # Don't do anything; just store the record in the records dict # by its message, so we can assert things about it. if isinstance(record.msg, dict): records[record.msg["event"]] = record else: records[record.msg] = record stdlib_logger = logging.getLogger() structlog_logger = get_logger() # It doesn't matter which logger we add the handler to here. stdlib_logger.addHandler(DummyHandler()) try: raise Exception("foo") except Exception: stdlib_logger.exception("bar") structlog_logger.exception("baz") stdlib_record = records.pop("bar") assert "bar" == stdlib_record.msg assert stdlib_record.exc_info assert Exception is stdlib_record.exc_info[0] assert ("foo",) == stdlib_record.exc_info[1].args structlog_record = records.pop("baz") assert "baz" == structlog_record.msg["event"] assert True is structlog_record.msg["exc_info"] assert structlog_record.exc_info assert Exception is structlog_record.exc_info[0] assert ("foo",) == structlog_record.exc_info[1].args assert not records def test_use_get_message_false(self): """ If use_get_message_is False, the event is obtained using str(record.msg) instead of calling record.getMessage. That means positional formatting is not performed. """ event_dicts = [] def capture(_, __, ed): event_dicts.append(ed.copy()) return str(ed) proc = ProcessorFormatter(processors=[capture], use_get_message=False) record = logging.LogRecord( "foo", logging.INFO, "path.py", 42, "le msg: %s", ("keep separate",), None, ) assert proc.format(record) assert "le msg: %s" == event_dicts[0]["event"] @pytest_asyncio.fixture(name="abl") async def _abl(cl): return AsyncBoundLogger(cl, context={}, processors=[]) class TestAsyncBoundLogger: def test_sync_bl(self, abl, cl): """ AsyncBoungLogger.sync_bl works outside of loops. """ abl.sync_bl.info("test") assert [ CapturedCall(method_name="info", args=(), kwargs={"event": "test"}) ] == cl.calls @pytest.mark.asyncio async def test_protocol(self, abl): """ AsyncBoundLogger is a proper BindableLogger. """ assert isinstance(abl, BindableLogger) @pytest.mark.asyncio async def test_correct_levels(self, abl, cl, stdlib_log_method): """ The proxy methods call the correct upstream methods. """ await getattr(abl.bind(foo="bar"), stdlib_log_method)("42") aliases = {"warn": "warning"} expect = aliases.get(stdlib_log_method, stdlib_log_method) assert expect == cl.calls[0].method_name @pytest.mark.asyncio async def test_correct_level_fatal(self, abl, cl): """ fatal, that I have no idea why we support, maps to critical. """ await abl.bind(foo="bar").fatal("42") assert "critical" == cl.calls[0].method_name @pytest.mark.asyncio async def test_log_method(self, abl, cl): """ The `log` method is proxied too. """ await abl.bind(foo="bar").log(logging.ERROR, "42") assert "error" == cl.calls[0].method_name @pytest.mark.asyncio async def test_exception(self, abl, cl): """ `exception` makes sure 'exc_info" is set, if it's not set already. """ try: raise ValueError("omg") except ValueError: await abl.exception("oops") ei = cl.calls[0].kwargs["exc_info"] assert ValueError is ei[0] assert ("omg",) == ei[1].args @pytest.mark.asyncio async def test_exception_do_not_overwrite(self, abl, cl): """ `exception` leaves exc_info be, if it's set. """ o1 = object() o2 = object() o3 = object() try: raise ValueError("omg") except ValueError: await abl.exception("oops", exc_info=(o1, o2, o3)) ei = cl.calls[0].kwargs["exc_info"] assert (o1, o2, o3) == ei @pytest.mark.asyncio async def test_bind_unbind(self, cl): """ new/bind/unbind/try_unbind are correctly propagated. """ l1 = AsyncBoundLogger(cl, context={}, processors=[]) l2 = l1.bind(x=42) assert l1 is not l2 assert l1.sync_bl is not l2.sync_bl assert {} == l1._context assert {"x": 42} == l2._context l3 = l2.new(y=23) assert l2 is not l3 assert l2.sync_bl is not l3.sync_bl assert {"y": 23} == l3._context l4 = l3.unbind("y") assert {} == l4._context assert l3 is not l4 # N.B. x isn't bound anymore. l5 = l4.try_unbind("x") assert {} == l5._context assert l4 is not l5 @pytest.mark.asyncio async def test_integration(self, capsys): """ Configure and log an actual entry. """ configure( processors=[add_log_level, JSONRenderer()], logger_factory=PrintLogger, wrapper_class=AsyncBoundLogger, cache_logger_on_first_use=True, ) logger = get_logger() await logger.bind(foo="bar").info("baz", x="42") assert { "foo": "bar", "x": "42", "event": "baz", "level": "info", } == json.loads(capsys.readouterr().out) @pytest.mark.parametrize("log_level", [None, 45]) def test_recreate_defaults(log_level): """ Recreate defaults configures structlog and -- if asked -- logging. """ logging.basicConfig( stream=sys.stderr, level=1, force=True, ) recreate_defaults(log_level=log_level) assert BoundLogger is _CONFIG.default_wrapper_class assert dict is _CONFIG.default_context_class assert isinstance(_CONFIG.logger_factory, LoggerFactory) log = get_logger().bind() if log_level is not None: assert log_level == log.getEffectiveLevel() else: assert 1 == log.getEffectiveLevel() structlog-25.5.0/tests/test_testing.py0000644000000000000000000001467015077624341015030 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import pytest import structlog from structlog import contextvars, get_config, get_logger, testing from structlog.testing import ( CapturedCall, CapturingLogger, CapturingLoggerFactory, LogCapture, ReturnLogger, ReturnLoggerFactory, ) class TestCaptureLogs: def test_captures_logs(self): """ Log entries are captured and retain their structure. """ with testing.capture_logs() as logs: get_logger().bind(x="y").info("hello", answer=42) get_logger().bind(a="b").info("goodbye", foo={"bar": "baz"}) assert [ {"event": "hello", "log_level": "info", "x": "y", "answer": 42}, { "a": "b", "event": "goodbye", "log_level": "info", "foo": {"bar": "baz"}, }, ] == logs def get_active_procs(self): return get_config()["processors"] def test_restores_processors_on_success(self): """ Processors are patched within the contextmanager and restored on exit. """ orig_procs = self.get_active_procs() assert len(orig_procs) > 1 with testing.capture_logs(): modified_procs = self.get_active_procs() assert len(modified_procs) == 1 assert isinstance(modified_procs[0], LogCapture) restored_procs = self.get_active_procs() assert orig_procs is restored_procs assert len(restored_procs) > 1 def test_uses_processors_arg_and_restores_on_success(self): """ Processors passed with `processors` arg are active only until context exits. """ orig_procs = self.get_active_procs() assert len(orig_procs) > 1 with testing.capture_logs(processors=[contextvars.merge_contextvars]): modified_procs = self.get_active_procs() assert len(modified_procs) == 2 assert contextvars.merge_contextvars == modified_procs[0] assert isinstance(modified_procs[1], LogCapture) assert len(modified_procs) == 2 assert contextvars.merge_contextvars == modified_procs[0] assert isinstance(modified_procs[1], LogCapture) restored_procs = self.get_active_procs() assert orig_procs is restored_procs assert len(restored_procs) > 1 def test_restores_processors_on_error(self): """ Processors are restored even on errors. """ orig_procs = self.get_active_procs() with pytest.raises(NotImplementedError), testing.capture_logs(): raise NotImplementedError("from test") assert orig_procs is self.get_active_procs() def test_captures_bound_logers(self): """ Even logs from already bound loggers are captured and their processors restored on exit. """ logger = get_logger("bound").bind(foo="bar") logger.info("ensure logger is bound") with testing.capture_logs() as logs: logger.info("hello", answer=42) assert logs == [ { "event": "hello", "answer": 42, "foo": "bar", "log_level": "info", } ] def test_captures_log_level_mapping(self): """ exceptions and warn log levels are mapped like in regular loggers. """ structlog.configure( processors=[ structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, ) with testing.capture_logs() as logs: get_logger().exception("hello", answer=42) get_logger().warn("again", answer=23) assert [ { "event": "hello", "answer": 42, "exc_info": True, "log_level": "error", }, { "answer": 23, "event": "again", "log_level": "warning", }, ] == logs class TestReturnLogger: # @pytest.mark.parametrize("method", stdlib_log_methods) def test_stdlib_methods_support(self, stdlib_log_method): """ ReturnLogger implements methods of stdlib loggers. """ v = getattr(ReturnLogger(), stdlib_log_method)("hello") assert "hello" == v def test_return_logger(self): """ Return logger returns exactly what's sent in. """ obj = ["hello"] assert obj is ReturnLogger().msg(obj) class TestReturnLoggerFactory: def test_builds_returnloggers(self): """ Factory returns ReturnLoggers. """ f = ReturnLoggerFactory() assert isinstance(f(), ReturnLogger) def test_caches(self): """ There's no need to have several loggers so we return the same one on each call. """ f = ReturnLoggerFactory() assert f() is f() def test_ignores_args(self): """ ReturnLogger doesn't take positional arguments. If any are passed to the factory, they are not passed to the logger. """ ReturnLoggerFactory()(1, 2, 3) class TestCapturingLogger: def test_factory_caches(self): """ CapturingLoggerFactory returns one CapturingLogger over and over again. """ clf = CapturingLoggerFactory() cl1 = clf() cl2 = clf() assert cl1 is cl2 def test_repr(self): """ repr says how many calls there were. """ cl = CapturingLogger() cl.info("hi") cl.error("yolo") assert "" == repr(cl) def test_captures(self): """ All calls to all names are captured. """ cl = CapturingLogger() cl.info("hi", val=42) cl.trololo("yolo", foo={"bar": "baz"}) assert [ CapturedCall(method_name="info", args=("hi",), kwargs={"val": 42}), CapturedCall( method_name="trololo", args=("yolo",), kwargs={"foo": {"bar": "baz"}}, ), ] == cl.calls structlog-25.5.0/tests/test_threadlocal.py0000644000000000000000000003274215077624341015635 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import inspect import threading import pytest import structlog from structlog import get_logger, wrap_logger from structlog._base import BoundLoggerBase from structlog._config import BoundLoggerLazyProxy from structlog.testing import ReturnLogger from structlog.threadlocal import ( _CONTEXT, as_immutable, bind_threadlocal, bound_threadlocal, clear_threadlocal, get_merged_threadlocal, get_threadlocal, merge_threadlocal, merge_threadlocal_context, tmp_bind, unbind_threadlocal, wrap_dict, ) from .helpers import CustomError try: import greenlet except ImportError: greenlet = None @pytest.fixture(autouse=True) def _clear_threadlocal(): """ Make sure all tests start with a clean slate. """ with pytest.deprecated_call(): clear_threadlocal() @pytest.fixture(name="D") def _D(): """ Returns a dict wrapped in _ThreadLocalDictWrapper. """ with pytest.deprecated_call(): return wrap_dict(dict) @pytest.fixture(name="log") def _log(logger): """ Returns a ReturnLogger with a freshly wrapped dict. """ with pytest.deprecated_call(): return wrap_logger(logger, context_class=wrap_dict(dict)) @pytest.fixture(name="logger") def _logger(): """ Returns a simple logger stub with a *msg* method that takes one argument which gets returned. """ return ReturnLogger() class TestTmpBind: def test_bind(self, log): """ tmp_bind does not modify the thread-local state. """ log = log.bind(y=23) with pytest.deprecated_call(), tmp_bind(log, x=42, y="foo") as tmp_log: assert ( {"y": "foo", "x": 42} == tmp_log._context._dict == log._context._dict ) assert {"y": 23} == log._context._dict def test_bind_exc(self, log): """ tmp_bind cleans up properly on exceptions. """ log = log.bind(y=23) with pytest.raises( # noqa: PT012 CustomError ), pytest.deprecated_call(), tmp_bind(log, x=42, y="foo") as tmp_log: assert ( {"y": "foo", "x": 42} == tmp_log._context._dict == log._context._dict ) raise CustomError assert {"y": 23} == log._context._dict def test_tmp_bind_lazy(self): """ tmp_bind works with a BoundLoggerLazyProxy -- i.e. before the first bind. """ with pytest.deprecated_call(): structlog.configure(context_class=wrap_dict(dict)) log = get_logger() assert isinstance(log, BoundLoggerLazyProxy) with pytest.deprecated_call(), tmp_bind(log, x=42) as tmp_log: assert {"x": 42} == tmp_log._context._dict assert {} == log._context class TestAsImmutable: def test_does_not_affect_global(self, log): """ A logger from as_mutable is independent from thread local state. """ log = log.new(x=42) with pytest.deprecated_call(): il = as_immutable(log) assert isinstance(il._context, dict) il = il.bind(y=23) assert {"x": 42, "y": 23} == il._context assert {"x": 42} == log._context._dict def test_converts_proxy(self, log): """ as_immutable converts a BoundLoggerLazyProxy into a concrete bound logger. """ with pytest.deprecated_call(): il = as_immutable(log) assert isinstance(il._context, dict) assert isinstance(il, BoundLoggerBase) def test_idempotency(self, log): """ as_immutable on an as_immutable logger works. """ with pytest.deprecated_call(): il = as_immutable(log) with pytest.deprecated_call(): assert isinstance(as_immutable(il), BoundLoggerBase) class TestThreadLocalDict: def test_wrap_returns_distinct_classes(self): """ Each call to wrap_dict returns a distinct new class whose context is independent from others. """ with pytest.deprecated_call(): D1 = wrap_dict(dict) D2 = wrap_dict(dict) assert D1 != D2 assert D1 is not D2 D1.x = 42 D2.x = 23 assert D1.x != D2.x @pytest.mark.skipif( greenlet is not None, reason="Don't mix threads and greenlets." ) def test_is_thread_local(self, D): """ The context is *not* shared between threads. """ class TestThread(threading.Thread): def __init__(self, d): self._d = d threading.Thread.__init__(self) def run(self): assert "tl" not in self._d._dict self._d["tl"] = 23 with pytest.deprecated_call(): d = wrap_dict(dict)() d["tl"] = 42 t = TestThread(d) t.start() t.join() assert 42 == d._dict["tl"] def test_context_is_global_to_thread(self, D): """ The context is shared between all instances of a wrapped class. """ d1 = D({"a": 42}) d2 = D({"b": 23}) d3 = D() assert {"a": 42, "b": 23} == d1._dict == d2._dict == d3._dict assert d1 == d2 == d3 with pytest.deprecated_call(): D_ = wrap_dict(dict) d_ = D_({"a": 42, "b": 23}) assert d1 != d_ def test_init_with_itself_works(self, D): """ Initializing with an instance of the wrapped class will use its values. """ d = D({"a": 42}) assert {"a": 42, "b": 23} == D(d, b=23)._dict def test_iter_works(self, D): """ ___iter__ is proxied to the wrapped class. """ d = D({"a": 42}) assert ["a"] == list(iter(d)) def test_non_dunder_proxy_works(self, D): """ Calls to a non-dunder method get proxied to the wrapped class. """ d = D({"a": 42}) d.clear() assert 0 == len(d) def test_repr(self, D): """ ___repr__ takes the repr of the wrapped class into account. """ r = repr(D({"a": 42})) assert r.startswith("") @pytest.mark.skipif(greenlet is None, reason="Needs greenlet.") def test_is_greenlet_local(self, D): """ Context is shared between greenlets. """ with pytest.deprecated_call(): d = wrap_dict(dict)() d["switch"] = 42 def run(): assert "x" not in d._dict d["switch"] = 23 greenlet.greenlet(run).switch() assert 42 == d._dict["switch"] def test_delattr(self, D): """ ___delattr__ is proxied to the wrapped class. """ d = D() d["delattr"] = 42 assert 42 == d._dict["delattr"] del d.__class__._tl.dict_ def test_delattr_missing(self, D): """ __delattr__ on an inexisting attribute raises AttributeError. """ d = D() with pytest.raises(AttributeError) as e: d._tl.__delattr__("does_not_exist") assert e.value.args[0] in ( "does_not_exist", "'_thread._local' object has no attribute 'does_not_exist'", ) def test_del(self, D): """ ___del__ is proxied to the wrapped class. """ d = D() d["del"] = 13 assert 13 == d._dict["del"] del d["del"] assert "del" not in d._dict def test_new_class(self, D): """ The context of a new wrapped class is empty. """ assert 0 == len(D()) class TestNewThreadLocal: def test_alias(self): """ We're keeping the old alias around. """ assert merge_threadlocal_context is merge_threadlocal def test_bind_and_merge(self): """ Binding a variable causes it to be included in the result of merge_threadlocal. """ with pytest.deprecated_call(): bind_threadlocal(a=1) with pytest.deprecated_call(): assert {"a": 1, "b": 2} == merge_threadlocal(None, None, {"b": 2}) def test_clear(self): """ The thread-local context can be cleared, causing any previously bound variables to not be included in merge_threadlocal's result. """ with pytest.deprecated_call(): bind_threadlocal(a=1) with pytest.deprecated_call(): clear_threadlocal() with pytest.deprecated_call(): assert {"b": 2} == merge_threadlocal(None, None, {"b": 2}) def test_merge_works_without_bind(self): """ merge_threadlocal returns values as normal even when there has been no previous calls to bind_threadlocal. """ with pytest.deprecated_call(): assert {"b": 2} == merge_threadlocal(None, None, {"b": 2}) def test_multiple_binds(self): """ Multiple calls to bind_threadlocal accumulate values instead of replacing them. """ with pytest.deprecated_call(): bind_threadlocal(a=1, b=2) bind_threadlocal(c=3) with pytest.deprecated_call(): assert {"a": 1, "b": 2, "c": 3} == merge_threadlocal( None, None, {"b": 2} ) def test_unbind_threadlocal(self): """ Test that unbinding from threadlocal works for keys that exist and does not raise error when they do not exist. """ with pytest.deprecated_call(): bind_threadlocal(a=234, b=34) with pytest.deprecated_call(): assert {"a": 234, "b": 34} == get_threadlocal() with pytest.deprecated_call(): unbind_threadlocal("a") with pytest.deprecated_call(): assert {"b": 34} == get_threadlocal() with pytest.deprecated_call(): unbind_threadlocal("non-existing-key") with pytest.deprecated_call(): assert {"b": 34} == get_threadlocal() def test_get_context_no_context(self): """ If there is no context yet, _get_context will add it. """ # Don't rely on test order. if hasattr(_CONTEXT, "context"): del _CONTEXT.context with pytest.raises(AttributeError): _CONTEXT.context with pytest.deprecated_call(): assert {} == get_threadlocal() def test_get_merged(self): """ Returns a copy of the threadlocal context merged with the logger's context. """ with pytest.deprecated_call(): bind_threadlocal(x=1) log = structlog.get_logger().bind(y=2) with pytest.deprecated_call(): assert {"x": 1, "y": 2} == get_merged_threadlocal(log) class TestBoundThreadlocal: def test_cleanup(self): """ Bindings are cleaned up """ with pytest.deprecated_call(), bound_threadlocal(x=42, y="foo"): assert {"x": 42, "y": "foo"} == get_threadlocal() with pytest.deprecated_call(): assert {} == get_threadlocal() def test_cleanup_conflict(self): """ Overwritten keys are restored after the clean up """ with pytest.deprecated_call(): bind_threadlocal(x="original", z="unrelated") with bound_threadlocal(x=42, y="foo"): assert { "x": 42, "y": "foo", "z": "unrelated", } == get_threadlocal() with pytest.deprecated_call(): assert {"x": "original", "z": "unrelated"} == get_threadlocal() def test_preserve_independent_bind(self): """ New bindings inside bound_threadlocal are preserved after the clean up """ with pytest.deprecated_call(), bound_threadlocal(x=42): bind_threadlocal(y="foo") assert {"x": 42, "y": "foo"} == get_threadlocal() with pytest.deprecated_call(): assert {"y": "foo"} == get_threadlocal() def test_nesting_works(self): """ bound_threadlocal binds and unbinds even when nested """ with pytest.deprecated_call(): with bound_threadlocal(l1=1): assert {"l1": 1} == get_threadlocal() with bound_threadlocal(l2=2): assert {"l1": 1, "l2": 2} == get_threadlocal() assert {"l1": 1} == get_threadlocal() assert {} == get_threadlocal() def test_as_decorator(self): """ bound_threadlocal can be used as a decorator and it preserves the name, signature and documentation of the wrapped function. """ @bound_threadlocal(x=42) def wrapped(arg1): """Wrapped documentation""" with pytest.deprecated_call(): bind_threadlocal(y=arg1) with pytest.deprecated_call(): assert {"x": 42, "y": arg1} == get_threadlocal() # I can't find a way for the warnings to be raised from the decorator. with pytest.deprecated_call(): wrapped(23) assert "wrapped" == wrapped.__name__ assert "(arg1)" == str(inspect.signature(wrapped)) assert "Wrapped documentation" == wrapped.__doc__ structlog-25.5.0/tests/test_tracebacks.py0000644000000000000000000010065415077624341015453 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. from __future__ import annotations import asyncio import inspect import json import sys from pathlib import Path from types import ModuleType from typing import Any import pytest import structlog from structlog import tracebacks class SecretStr(str): # noqa: SLOT000 """ Secrets representation as used in Typed Settings or Pydantic. """ def __repr__(self) -> str: return "*******" @pytest.fixture(autouse=True) def _unimport_rich(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(tracebacks, "rich", None) def get_next_lineno() -> int: return inspect.currentframe().f_back.f_lineno + 1 @pytest.mark.parametrize(("data", "expected"), [(3, "3"), ("spam", "spam")]) def test_save_str(data: Any, expected: str): """ "safe_str()" returns the str repr of an object. """ assert expected == tracebacks.safe_str(data) def test_safe_str_error(): """ "safe_str()" does not fail if __str__() raises an exception. """ class Baam: def __str__(self) -> str: raise ValueError("BAAM!") with pytest.raises(ValueError, match="BAAM!"): str(Baam()) assert "" == tracebacks.safe_str(Baam()) @pytest.mark.parametrize( ("data", "max_len", "expected"), [ (3, None, "3"), ("spam", None, "'spam'"), (b"spam", None, "b'spam'"), ("bacon", 3, "'bac'+2"), ("bacon", 4, "'baco'+1"), ("bacon", 5, "'bacon'"), (SecretStr("password"), None, "*******"), (["spam", "eggs", "bacon"], 10, "\"['spam', '\"+15"), ], ) def test_to_repr(data: Any, max_len: int | None, expected: str) -> None: """ "to_repr()" returns the repr of an object, trimmed to max_len. """ assert expected == tracebacks.to_repr(data, max_string=max_len) @pytest.mark.parametrize( ("use_rich", "data", "max_len", "expected"), [ (True, 3, None, "3"), (True, "spam", None, "'spam'"), (True, b"spam", None, "b'spam'"), (True, "bacon", 3, "'bac'+2"), (True, "bacon", 5, "'bacon'"), (True, SecretStr("password"), None, "*******"), (True, ["spam", "eggs", "bacon"], 4, "['spam', 'eggs', 'baco'+1]"), (False, "bacon", 3, "'bac'+2"), (False, ["spam", "eggs", "bacon"], 4, '"[\'sp"+21'), ], ) def test_to_repr_rich( use_rich: bool, data: Any, max_len: int | None, expected: str, monkeypatch: pytest.MonkeyPatch, ) -> None: """ "to_repr()" uses Rich to get a nice repr if it is installed and if "use_rich" is True. """ try: import rich except ImportError: pytest.skip(reason="rich not installed") monkeypatch.setattr(tracebacks, "rich", rich) assert expected == tracebacks.to_repr( data, max_string=max_len, use_rich=use_rich ) def test_to_repr_error() -> None: """ "to_repr()" does not fail if __repr__() raises an exception. """ class Baam: def __repr__(self) -> str: raise ValueError("BAAM!") with pytest.raises(ValueError, match="BAAM!"): repr(Baam()) assert "" == tracebacks.to_repr(Baam()) def test_simple_exception(): """ Tracebacks are parsed for simple, single exceptions. """ try: lineno = get_next_lineno() 1 / 0 except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ZeroDivisionError", exc_value="division by zero", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_simple_exception", locals=None, ), ], ), ] == trace.stacks @pytest.mark.skipif( sys.version_info < (3, 11), reason="Requires Python 3.11 or higher" ) def test_simple_exception_with_notes(): """ Notes are included in the traceback. """ try: lineno = get_next_lineno() 1 / 0 except Exception as e: e.add_note("This is a note.") e.add_note("This is another note.") trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ZeroDivisionError", exc_value="division by zero", exc_notes=["This is a note.", "This is another note."], syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_simple_exception_with_notes", locals=None, ), ], ), ] == trace.stacks def test_raise_hide_cause(): """ If "raise ... from None" is used, the trace looks like from a simple exception. """ try: try: 1 / 0 except ArithmeticError: lineno = get_next_lineno() raise ValueError("onoes") from None except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ValueError", exc_value="onoes", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_raise_hide_cause", locals=None, ), ], ), ] == trace.stacks def test_raise_with_cause(): """ If "raise ... from orig" is used, the orig trace is included and marked as cause. """ try: try: lineno_1 = get_next_lineno() 1 / 0 except ArithmeticError as orig_exc: lineno_2 = get_next_lineno() raise ValueError("onoes") from orig_exc except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ValueError", exc_value="onoes", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno_2, name="test_raise_with_cause", locals=None, ), ], ), tracebacks.Stack( exc_type="ZeroDivisionError", exc_value="division by zero", syntax_error=None, is_cause=True, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno_1, name="test_raise_with_cause", locals=None, ), ], ), ] == trace.stacks def test_raise_with_cause_no_tb(): """ If an exception's cause has no traceback, that cause is ignored. """ try: lineno = get_next_lineno() raise ValueError("onoes") from RuntimeError("I am fake") except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ValueError", exc_value="onoes", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_raise_with_cause_no_tb", locals=None, ), ], ), ] == trace.stacks def test_raise_nested(): """ If an exc is raised during handling another one, the orig trace is included. """ try: try: lineno_1 = get_next_lineno() 1 / 0 except ArithmeticError: lineno_2 = get_next_lineno() raise ValueError("onoes") # noqa: B904 except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ValueError", exc_value="onoes", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno_2, name="test_raise_nested", locals=None, ), ], ), tracebacks.Stack( exc_type="ZeroDivisionError", exc_value="division by zero", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno_1, name="test_raise_nested", locals=None, ), ], ), ] == trace.stacks def test_raise_no_msg(): """ If exception classes (not instances) are raised, "exc_value" is an empty string. """ try: lineno = get_next_lineno() raise RuntimeError except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="RuntimeError", exc_value="", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_raise_no_msg", locals=None, ), ], ), ] == trace.stacks def test_syntax_error(): """ For SyntaxError, extra info about that error is added to the trace. """ try: # raises SyntaxError: invalid syntax lineno = get_next_lineno() eval("2 +* 2") except SyntaxError as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="SyntaxError", exc_value="invalid syntax (, line 1)", syntax_error=tracebacks.SyntaxError_( offset=4, filename="", line="2 +* 2", lineno=1, msg="invalid syntax", ), is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_syntax_error", ), ], ), ] == trace.stacks def test_filename_with_bracket(): """ Filenames with brackets (e.g., "") are handled properly. """ try: lineno = get_next_lineno() exec(compile("1/0", filename="", mode="exec")) except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ZeroDivisionError", exc_value="division by zero", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_filename_with_bracket", locals=None, ), tracebacks.Frame( filename="", lineno=1, name="", locals=None, ), ], ), ] == trace.stacks def test_filename_not_a_file(): """ "Invalid" filenames are appended to CWD as if they were actual files. """ try: lineno = get_next_lineno() exec(compile("1/0", filename="string", mode="exec")) except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert [ tracebacks.Stack( exc_type="ZeroDivisionError", exc_value="division by zero", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_filename_not_a_file", locals=None, ), tracebacks.Frame( filename=str(Path.cwd().joinpath("string")), lineno=1, name="", locals=None, ), ], ), ] == trace.stacks def test_show_locals(): """ Local variables in each frame can optionally be captured. """ def bar(a): print(1 / a) def foo(a): bar(a) try: foo(0) except Exception as e: trace = tracebacks.extract( type(e), e, e.__traceback__, show_locals=True ) stack_locals = [f.locals for f in trace.stacks[0].frames] # The first frames contain functions with "random" memory addresses, # so we only check the variable names: assert stack_locals[0].keys() == {"foo", "e", "bar"} assert stack_locals[1].keys() == {"a", "bar"} assert stack_locals[2] == {"a": "0"} def test_recursive(): """ Recursion errors give a lot of frames but don't break stuff. """ def foo(n): return bar(n) def bar(n): return foo(n) try: lineno = get_next_lineno() foo(1) except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) frames = trace.stacks[0].frames trace.stacks[0].frames = [] assert [ tracebacks.Stack( exc_type="RecursionError", exc_value="maximum recursion depth exceeded", syntax_error=None, is_cause=False, frames=[], ), ] == trace.stacks assert ( len(frames) > sys.getrecursionlimit() - 50 ) # Buffer for frames from pytest assert ( tracebacks.Frame( filename=__file__, lineno=lineno, name="test_recursive", ) == frames[0] ) # If we run the tests under Python 3.12 with sysmon enabled, it inserts # frames at the end. if sys.version_info >= (3, 12): frames = [f for f in frames if "coverage" not in f.filename] # Depending on whether we invoke pytest directly or run tox, either "foo()" # or "bar()" is at the end of the stack. assert frames[-1] in [ tracebacks.Frame( filename=__file__, lineno=lineno - 7, name="foo", ), tracebacks.Frame( filename=__file__, lineno=lineno - 4, name="bar", ), ] @pytest.mark.skipif( sys.version_info < (3, 11), reason="Requires Python 3.11 or higher" ) def test_exception_groups() -> None: """ Exception groups are detected and a list of Trace instances is added to the exception group's Trace. """ lineno = get_next_lineno() async def t1() -> None: 1 / 0 async def t2() -> None: raise ValueError("Blam!") async def main(): async with asyncio.TaskGroup() as tg: tg.create_task(t1()) tg.create_task(t2()) try: asyncio.run(main()) except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert "ExceptionGroup" == trace.stacks[0].exc_type assert ( "unhandled errors in a TaskGroup (2 sub-exceptions)" == trace.stacks[0].exc_value ) exceptions = trace.stacks[0].exceptions assert [ tracebacks.Trace( stacks=[ tracebacks.Stack( exc_type="ZeroDivisionError", exc_value="division by zero", exc_notes=[], syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno + 2, name="t1", locals=None, ) ], is_group=False, exceptions=[], ) ] ), tracebacks.Trace( stacks=[ tracebacks.Stack( exc_type="ValueError", exc_value="Blam!", exc_notes=[], syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno + 5, name="t2", locals=None, ) ], is_group=False, exceptions=[], ) ] ), ] == exceptions @pytest.mark.parametrize( ("kwargs", "local_variable"), [ ( {"locals_max_string": None}, "x" * (tracebacks.LOCALS_MAX_STRING + 10), ), ( {"locals_max_length": None}, list(range(tracebacks.LOCALS_MAX_LENGTH + 10)), ), ], ) def test_exception_dict_transformer_locals_max_accept_none_argument( kwargs, local_variable ): """ ExceptionDictTransformer works when either locals_max_string or locals_max_length is set to None. """ try: my_local = local_variable 1 / 0 except Exception as e: format_json = tracebacks.ExceptionDictTransformer( show_locals=True, **kwargs ) result = format_json((type(e), e, e.__traceback__)) _ = my_local assert len(result) == 1 assert len(result[0]["frames"]) == 1 frame = result[0]["frames"][0] assert frame["locals"]["my_local"].strip("'") == str(local_variable) def test_json_traceback(): """ Tracebacks are formatted to JSON with all information. """ try: lineno = get_next_lineno() 1 / 0 except Exception as e: format_json = tracebacks.ExceptionDictTransformer(show_locals=False) result = format_json((type(e), e, e.__traceback__)) assert [ { "exc_type": "ZeroDivisionError", "exc_value": "division by zero", "exc_notes": [], "exceptions": [], "frames": [ { "filename": __file__, "lineno": lineno, "name": "test_json_traceback", } ], "is_cause": False, "is_group": False, "syntax_error": None, }, ] == result @pytest.mark.skipif( sys.version_info < (3, 11), reason="Requires Python 3.11 or higher" ) def test_json_traceback_with_notes(): """ Tracebacks are formatted to JSON with all information. """ try: lineno = get_next_lineno() 1 / 0 except Exception as e: e.add_note("This is a note.") e.add_note("This is another note.") format_json = tracebacks.ExceptionDictTransformer(show_locals=False) result = format_json((type(e), e, e.__traceback__)) assert [ { "exc_type": "ZeroDivisionError", "exc_value": "division by zero", "exc_notes": ["This is a note.", "This is another note."], "exceptions": [], "frames": [ { "filename": __file__, "lineno": lineno, "name": "test_json_traceback_with_notes", } ], "is_cause": False, "is_group": False, "syntax_error": None, }, ] == result def test_json_traceback_locals_max_string(): """ Local variables in each frame are trimmed to locals_max_string. """ try: _var = "spamspamspam" lineno = get_next_lineno() 1 / 0 except Exception as e: result = tracebacks.ExceptionDictTransformer(locals_max_string=4)( (type(e), e, e.__traceback__) ) assert [ { "exc_type": "ZeroDivisionError", "exc_value": "division by zero", "exc_notes": [], "exceptions": [], "frames": [ { "filename": __file__, "lineno": lineno, "locals": { "_var": "'spam'+8", "e": "'Zero'+33", "lineno": str(lineno), }, "name": "test_json_traceback_locals_max_string", } ], "is_cause": False, "is_group": False, "syntax_error": None, }, ] == result @pytest.mark.parametrize( ("max_frames", "expected_frames", "skipped_idx", "skipped_count"), [ (2, 3, 1, 2), (3, 3, 1, 2), (4, 4, -1, 0), (5, 4, -1, 0), ], ) def test_json_traceback_max_frames( max_frames: int, expected_frames: int, skipped_idx: int, skipped_count: int ): """ Only max_frames frames are included in the traceback and the skipped frames are reported. """ def spam(): return 1 / 0 def eggs(): spam() def bacon(): eggs() try: bacon() except Exception as e: format_json = tracebacks.ExceptionDictTransformer( show_locals=False, max_frames=max_frames ) result = format_json((type(e), e, e.__traceback__)) trace = result[0] assert len(trace["frames"]) == expected_frames, trace["frames"] if skipped_count: assert trace["frames"][skipped_idx] == { "filename": "", "lineno": -1, "name": f"Skipped frames: {skipped_count}", } else: assert not any(f["lineno"] == -1 for f in trace["frames"]) @pytest.mark.parametrize( ("suppress", "file_no_locals"), [ pytest.param((__file__,), __file__, id="file"), pytest.param((json,), json.__file__, id="json"), ], ) def test_json_tracebacks_suppress( suppress: tuple[str | ModuleType, ...], file_no_locals: str, capsys: pytest.CaptureFixture, ) -> None: """ Console and JSON output look as expected This means also warnings are logged correctly. """ try: try: json.loads(42) # type: ignore[arg-type] except Exception as e: raise ValueError("error shown to users") from e except ValueError as e: format_json = tracebacks.ExceptionDictTransformer( show_locals=True, suppress=suppress ) result = format_json((type(e), e, e.__traceback__)) for stack in result: for frame in stack["frames"]: no_locals = frame["filename"] == file_no_locals if no_locals: assert "locals" not in frame else: assert "locals" in frame @pytest.mark.parametrize( ("hide_sunder", "hide_dunder", "expected"), [ (False, False, {"_spam", "__eggs"}), (True, False, set()), # Also hides "__eggs", b/c it starts with "_"! (False, True, {"_spam"}), (True, True, set()), ], ) def test_json_tracebacks_skip_sunder_dunder( hide_sunder: bool, hide_dunder: bool, expected: set[str] ) -> None: """ Local variables starting with "_" or "__" can be hidden from the locals dict. """ def func() -> None: _spam = True __eggs = 3 1 / 0 try: func() except ZeroDivisionError as e: format_json = tracebacks.ExceptionDictTransformer( show_locals=True, locals_hide_sunder=hide_sunder, locals_hide_dunder=hide_dunder, ) result = format_json((type(e), e, e.__traceback__)) assert expected == set(result[0]["frames"][1]["locals"]) @pytest.mark.parametrize( "kwargs", [ {"locals_max_length": -1}, {"locals_max_string": -1}, {"max_frames": -1}, {"max_frames": 0}, {"max_frames": 1}, {"suppress": (json,)}, ], ) def test_json_traceback_value_error( kwargs: dict[str, Any], monkeypatch: pytest.MonkeyPatch ) -> None: """ Wrong arguments to ExceptionDictTransformer raise a ValueError that contains the name of the argument.. """ if "suppress" in kwargs: monkeypatch.setattr(kwargs["suppress"][0], "__file__", None) with pytest.raises(ValueError, match=next(iter(kwargs.keys()))): tracebacks.ExceptionDictTransformer(**kwargs) @pytest.mark.skipif( sys.version_info < (3, 11), reason="Requires Python 3.11 or higher" ) def test_json_exception_groups() -> None: """ When rendered as JSON, the "Trace.stacks" is stripped, so "exceptions" is a list of lists and not a list of objects (with a single "stacks" attribute. """ lineno = get_next_lineno() async def t1() -> None: 1 / 0 async def t2() -> None: raise ValueError("Blam!") async def main(): async with asyncio.TaskGroup() as tg: tg.create_task(t1()) tg.create_task(t2()) try: asyncio.run(main()) except Exception as e: format_json = tracebacks.ExceptionDictTransformer(show_locals=False) result = format_json((type(e), e, e.__traceback__)) assert "ExceptionGroup" == result[0]["exc_type"] exceptions = result[0]["exceptions"] assert [ [ { "exc_type": "ZeroDivisionError", "exc_value": "division by zero", "exc_notes": [], "syntax_error": None, "is_cause": False, "frames": [ { "filename": __file__, "lineno": lineno + 2, "name": "t1", } ], "is_group": False, "exceptions": [], } ], [ { "exc_type": "ValueError", "exc_value": "Blam!", "exc_notes": [], "syntax_error": None, "is_cause": False, "frames": [ { "filename": __file__, "lineno": lineno + 5, "name": "t2", } ], "is_group": False, "exceptions": [], } ], ] == exceptions class TestLogException: """ Higher level integration tests for `Logger.exception()`. """ @pytest.fixture def cap_logs(self) -> structlog.testing.LogCapture: """ Create a LogCapture to be used as processor and fixture for retrieving logs in tests. """ return structlog.testing.LogCapture() @pytest.fixture def logger( self, cap_logs: structlog.testing.LogCapture ) -> structlog.Logger: """ Create a logger with the dict_tracebacks and a LogCapture processor. """ old_processors = structlog.get_config()["processors"] structlog.configure([structlog.processors.dict_tracebacks, cap_logs]) logger = structlog.get_logger("dict_tracebacks") try: yield logger finally: structlog.configure(processors=old_processors) def test_log_explicit_exception( self, logger: structlog.Logger, cap_logs: structlog.testing.LogCapture ) -> None: """ The log row contains a traceback when `Logger.exception()` is explicitly called with an exception instance. """ try: 1 / 0 except ZeroDivisionError as e: logger.exception("onoes", exception=e) log_row = cap_logs.entries[0] assert log_row["exception"][0]["exc_type"] == "ZeroDivisionError" def test_log_implicit_exception( self, logger: structlog.Logger, cap_logs: structlog.testing.LogCapture ) -> None: """ The log row contains a traceback when `Logger.exception()` is called in an `except` block but without explicitly passing the exception. """ try: 1 / 0 except ZeroDivisionError: logger.exception("onoes") log_row = cap_logs.entries[0] assert log_row["exception"][0]["exc_type"] == "ZeroDivisionError" def test_no_exception( self, logger: structlog.Logger, cap_logs: structlog.testing.LogCapture ) -> None: """ `Logger.exception()` should not be called outside an `except` block but this cases is gracefully handled and does not lead to an exception. See: https://github.com/hynek/structlog/issues/634 """ logger.exception("onoes") assert [{"event": "onoes", "log_level": "error"}] == cap_logs.entries @pytest.mark.skipif( sys.version_info < (3, 11), reason="Requires Python 3.11 or higher" ) def test_reraise_error_from_exception_group(): """ There is no RecursionError when building the traceback for an exception that has been re-raised from an ExceptionGroup. """ inner_lineno = None lineno = None try: try: inner_lineno = get_next_lineno() raise ExceptionGroup( # noqa: F821 "Some error occurred", [ValueError("value error")], ) except ExceptionGroup as e: # noqa: F821 lineno = get_next_lineno() raise e.exceptions[0] # noqa: B904 except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert lineno is not None assert inner_lineno is not None assert 2 == len(trace.stacks) assert lineno == trace.stacks[0].frames[0].lineno assert ( tracebacks.Stack( exc_type="ValueError", exc_value="value error", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_reraise_error_from_exception_group", locals=None, ) ], is_group=False, exceptions=[], ) == trace.stacks[0] ) assert ( tracebacks.Stack( exc_type="ExceptionGroup", exc_value="Some error occurred (1 sub-exception)", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=inner_lineno, name="test_reraise_error_from_exception_group", locals=None, ), ], is_group=True, exceptions=[tracebacks.Trace(stacks=[])], ) == trace.stacks[1] ) def test_exception_cycle(): """ There is no RecursionError when building the traceback for an exception that has itself in its cause chain. """ inner_lineno = None lineno = None try: try: exc = ValueError("onoes") inner_lineno = get_next_lineno() raise exc except Exception as exc: lineno = get_next_lineno() raise exc from exc except Exception as e: trace = tracebacks.extract(type(e), e, e.__traceback__) assert lineno is not None assert inner_lineno is not None assert 1 == len(trace.stacks) assert lineno == trace.stacks[0].frames[0].lineno assert ( tracebacks.Stack( exc_type="ValueError", exc_value="onoes", syntax_error=None, is_cause=False, frames=[ tracebacks.Frame( filename=__file__, lineno=lineno, name="test_exception_cycle", locals=None, ), tracebacks.Frame( filename=__file__, lineno=inner_lineno, name="test_exception_cycle", locals=None, ), ], is_group=False, exceptions=[], ) == trace.stacks[0] ) structlog-25.5.0/tests/test_twisted.py0000644000000000000000000002404515077624341015033 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import json import pytest from structlog import ReturnLogger from structlog._config import _CONFIG from structlog.processors import KeyValueRenderer from .helpers import call_recorder try: from twisted.python.failure import Failure from twisted.python.log import ILogObserver from structlog.twisted import ( BoundLogger, EventAdapter, JSONLogObserverWrapper, JSONRenderer, LoggerFactory, PlainFileLogObserver, ReprWrapper, _extractStuffAndWhy, plainJSONStdOutLogger, ) except ImportError: pytest.skip(allow_module_level=True) def test_LoggerFactory(): """ Logger factory ultimately returns twisted.python.log for output. """ from twisted.python import log assert log is LoggerFactory()() def _render_repr(_, __, event_dict): return repr(event_dict) def build_bl(logger=None, processors=None, context=None): """ Convenience function to build BoundLoggerses with sane defaults. """ return BoundLogger( logger or ReturnLogger(), processors or [KeyValueRenderer()], context if context is not None else _CONFIG.default_context_class(), ) class TestBoundLogger: def test_msg(self): """ log.msg renders correctly. """ bl = build_bl() assert "foo=42 event='event'" == bl.msg("event", foo=42) def test_errVanilla(self): """ log.err renders correctly if no failure is attached. """ bl = build_bl() assert "foo=42 event='event'" == bl.err("event", foo=42) def test_errWithFailure(self): """ Failures are correctly injected into the log entries. """ bl = build_bl( processors=[EventAdapter(dictRenderer=KeyValueRenderer())] ) try: raise ValueError except ValueError: # Use str() for comparison to avoid tricky # deep-compares of Failures. assert str( ( (), { "_stuff": Failure(ValueError()), "_why": "foo=42 event='event'", }, ) ) == str(bl.err("event", foo=42)) class TestExtractStuffAndWhy: def test_extractFailsOnTwoFailures(self): """ Raise ValueError if both _stuff and event contain exceptions. """ with pytest.raises( ValueError, match="Both _stuff and event contain an Exception/Failure", ): _extractStuffAndWhy( { "_stuff": Failure(ValueError()), "event": Failure(TypeError()), } ) def test_failsOnConflictingEventAnd_why(self): """ Raise ValueError if both _why and event are in the event_dict. """ with pytest.raises( ValueError, match="Both `_why` and `event` supplied" ): _extractStuffAndWhy({"_why": "foo", "event": "bar"}) def test_handlesFailures(self): """ Extracts failures and events. """ f = Failure(ValueError()) assert ({"value": f}, "foo", {}) == _extractStuffAndWhy( {"_why": "foo", "_stuff": {"value": f}} ) assert ({"value": f}, None, {}) == _extractStuffAndWhy( {"_stuff": {"value": f}} ) def test_handlesMissingFailure(self): """ Missing failures extract a None. """ assert (None, "foo", {}) == _extractStuffAndWhy({"event": "foo"}) class TestEventAdapter: """ Some tests here are redundant because they predate _extractStuffAndWhy. """ def test_EventAdapterFormatsLog(self): """ EventAdapter formats log entries correctly. """ la = EventAdapter(_render_repr) assert "{'foo': 'bar'}" == la(None, "msg", {"foo": "bar"}) def test_transforms_whyIntoEvent(self): """ log.err(_stuff=exc, _why='foo') makes the output 'event="foo"' """ la = EventAdapter(_render_repr) error = ValueError("test") rv = la(None, "err", {"_stuff": error, "_why": "foo", "event": None}) assert () == rv[0] assert isinstance(rv[1]["_stuff"], Failure) assert error == rv[1]["_stuff"].value assert "{'event': 'foo'}" == rv[1]["_why"] def test_worksUsualCase(self): """ log.err(exc, _why='foo') makes the output 'event="foo"' """ la = EventAdapter(_render_repr) error = ValueError("test") rv = la(None, "err", {"event": error, "_why": "foo"}) assert () == rv[0] assert isinstance(rv[1]["_stuff"], Failure) assert error == rv[1]["_stuff"].value assert "{'event': 'foo'}" == rv[1]["_why"] def test_allKeywords(self): """ log.err(_stuff=exc, _why='event') """ la = EventAdapter(_render_repr) error = ValueError("test") rv = la(None, "err", {"_stuff": error, "_why": "foo"}) assert () == rv[0] assert isinstance(rv[1]["_stuff"], Failure) assert error == rv[1]["_stuff"].value assert "{'event': 'foo'}" == rv[1]["_why"] def test_noFailure(self): """ log.err('event') """ la = EventAdapter(_render_repr) assert ((), {"_stuff": None, "_why": "{'event': 'someEvent'}"}) == la( None, "err", {"event": "someEvent"} ) def test_noFailureWithKeyword(self): """ log.err(_why='event') """ la = EventAdapter(_render_repr) assert ((), {"_stuff": None, "_why": "{'event': 'someEvent'}"}) == la( None, "err", {"_why": "someEvent"} ) def test_catchesConflictingEventAnd_why(self): """ Passing both _why and event raises a ValueError. """ la = EventAdapter(_render_repr) with pytest.raises( ValueError, match="Both `_why` and `event` supplied" ): la(None, "err", {"event": "someEvent", "_why": "someReason"}) @pytest.fixture def jr(): """ A plain Twisted JSONRenderer. """ return JSONRenderer() class TestJSONRenderer: def test_dumpsKWsAreHandedThrough(self, jr): """ JSONRenderer allows for setting arguments that are passed to json.dumps(). Make sure they are passed. """ d = {"x": "foo"} d.update(a="bar") jr_sorted = JSONRenderer(sort_keys=True) assert jr_sorted(None, "err", d) != jr(None, "err", d) def test_handlesMissingFailure(self, jr): """ Calling err without an actual failure works and returns the event as a string wrapped in ReprWrapper. """ assert ( ReprWrapper('{"event": "foo"}') == jr(None, "err", {"event": "foo"})[0][0] ) assert ( ReprWrapper('{"event": "foo"}') == jr(None, "err", {"_why": "foo"})[0][0] ) def test_msgWorksToo(self, jr): """ msg renders the event as a string and wraps it using ReprWrapper. """ assert ( ReprWrapper('{"event": "foo"}') == jr(None, "msg", {"_why": "foo"})[0][0] ) def test_handlesFailure(self, jr): """ JSONRenderer renders failures correctly. """ rv = jr(None, "err", {"event": Failure(ValueError())})[0][0].string assert "Failure: builtins.ValueError" in rv assert '"event": "error"' in rv def test_setsStructLogField(self, jr): """ Formatted entries are marked so they can be identified without guessing for example in JSONLogObserverWrapper. """ assert {"_structlog": True} == jr(None, "msg", {"_why": "foo"})[1] class TestReprWrapper: def test_repr(self): """ The repr of the wrapped string is the vanilla string without quotes. """ assert "foo" == repr(ReprWrapper("foo")) class TestPlainFileLogObserver: def test_isLogObserver(self, sio): """ PlainFileLogObserver is an ILogObserver. """ assert ILogObserver.providedBy(PlainFileLogObserver(sio)) def test_writesOnlyMessageWithLF(self, sio): """ PlainFileLogObserver writes only the message and a line feed. """ PlainFileLogObserver(sio)( {"system": "some system", "message": ("hello",)} ) assert "hello\n" == sio.getvalue() class TestJSONObserverWrapper: def test_IsAnObserver(self): """ JSONLogObserverWrapper is an ILogObserver. """ assert ILogObserver.implementedBy(JSONLogObserverWrapper) def test_callsWrappedObserver(self): """ The wrapper always runs the wrapped observer in the end. """ o = call_recorder(lambda *a, **kw: None) JSONLogObserverWrapper(o)({"message": ("hello",)}) assert 1 == len(o.call_args_list) def test_jsonifiesPlainLogEntries(self): """ Entries that aren't formatted by JSONRenderer are rendered as JSON now. """ o = call_recorder(lambda *a, **kw: None) JSONLogObserverWrapper(o)({"message": ("hello",), "system": "-"}) msg = json.loads(o.call_args_list[0].args[0]["message"][0]) assert msg == {"event": "hello", "system": "-"} def test_leavesStructLogAlone(self): """ Entries that are formatted by JSONRenderer are left alone. """ d = {"message": ("hello",), "_structlog": True} def verify(eventDict): assert d == eventDict JSONLogObserverWrapper(verify)(d) class TestPlainJSONStdOutLogger: def test_isLogObserver(self): """ plainJSONStdOutLogger is an ILogObserver. """ assert ILogObserver.providedBy(plainJSONStdOutLogger()) structlog-25.5.0/tests/test_utils.py0000644000000000000000000000417215077624341014507 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. import multiprocessing import sys import pytest from structlog._utils import get_processname class TestGetProcessname: def test_default(self): """ The returned process name matches the name of the current process from the `multiprocessing` module. """ assert get_processname() == multiprocessing.current_process().name def test_changed(self, monkeypatch: pytest.MonkeyPatch): """ The returned process name matches the name of the current process from the `multiprocessing` module if it is not the default. """ tmp_name = "fakename" monkeypatch.setattr( target=multiprocessing.current_process(), name="name", value=tmp_name, ) assert get_processname() == tmp_name def test_no_multiprocessing(self, monkeypatch: pytest.MonkeyPatch) -> None: """ The returned process name is the default process name if the `multiprocessing` module is not available. """ tmp_name = "fakename" monkeypatch.setattr( target=multiprocessing.current_process(), name="name", value=tmp_name, ) monkeypatch.setattr( target=sys, name="modules", value={}, ) assert get_processname() == "n/a" def test_exception(self, monkeypatch: pytest.MonkeyPatch) -> None: """ The returned process name is the default process name when an exception is thrown when an attempt is made to retrieve the current process name from the `multiprocessing` module. """ def _current_process() -> None: raise RuntimeError("test") monkeypatch.setattr( target=multiprocessing, name="current_process", value=_current_process, ) assert get_processname() == "n/a" structlog-25.5.0/tests/processors/__init__.py0000644000000000000000000000000015077624341016233 0ustar00structlog-25.5.0/tests/processors/test_processors.py0000644000000000000000000005247515077624341017764 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. from __future__ import annotations import functools import inspect import itertools import json import logging import os import pickle import sys import threading from io import StringIO import pytest import structlog from structlog import BoundLogger from structlog._utils import get_processname from structlog.processors import ( CallsiteParameter, CallsiteParameterAdder, EventRenamer, ExceptionPrettyPrinter, JSONRenderer, StackInfoRenderer, UnicodeDecoder, UnicodeEncoder, _figure_out_exc_info, format_exc_info, ) from structlog.stdlib import ProcessorFormatter from structlog.typing import EventDict, ExcInfo from ..additional_frame import additional_frame try: import simplejson except ImportError: simplejson = None class TestUnicodeEncoder: def test_encodes(self): """ Unicode strings get encoded (as UTF-8 by default). """ e = UnicodeEncoder() assert {"foo": b"b\xc3\xa4r"} == e(None, None, {"foo": "b\xe4r"}) def test_passes_arguments(self): """ Encoding options are passed into the encoding call. """ e = UnicodeEncoder("latin1", "xmlcharrefreplace") assert {"foo": b"–"} == e(None, None, {"foo": "\u2013"}) def test_bytes_nop(self): """ If the string is already bytes, don't do anything. """ e = UnicodeEncoder() assert {"foo": b"b\xc3\xa4r"} == e(None, None, {"foo": b"b\xc3\xa4r"}) class TestUnicodeDecoder: def test_decodes(self): """ Byte strings get decoded (as UTF-8 by default). """ ud = UnicodeDecoder() assert {"foo": "b\xe4r"} == ud(None, None, {"foo": b"b\xc3\xa4r"}) def test_passes_arguments(self): """ Encoding options are passed into the encoding call. """ ud = UnicodeDecoder("utf-8", "ignore") assert {"foo": ""} == ud(None, None, {"foo": b"\xa1\xa4"}) def test_bytes_nop(self): """ If the value is already unicode, don't do anything. """ ud = UnicodeDecoder() assert {"foo": "b\u2013r"} == ud(None, None, {"foo": "b\u2013r"}) class TestExceptionPrettyPrinter: def test_stdout_by_default(self): """ If no file is supplied, use stdout. """ epp = ExceptionPrettyPrinter() assert sys.stdout is epp._file def test_prints_exception(self, sio): """ If there's an `exception` key in the event_dict, just print it out. This happens if `format_exc_info` was run before us in the chain. """ epp = ExceptionPrettyPrinter(file=sio) try: raise ValueError except ValueError: ed = format_exc_info(None, None, {"exc_info": True}) epp(None, None, ed) out = sio.getvalue() assert "test_prints_exception" in out assert "raise ValueError" in out def test_uses_exception_formatter(self, sio): """ If an `exception_formatter` is passed, use that to render the exception rather than the default. """ def formatter(exc_info: ExcInfo) -> str: return f"error: {exc_info}" epp = ExceptionPrettyPrinter(file=sio, exception_formatter=formatter) try: raise ValueError except ValueError as e: epp(None, None, {"exc_info": True}) formatted = formatter(e) out = sio.getvalue() assert formatted in out def test_removes_exception_after_printing(self, sio): """ After pretty printing `exception` is removed from the event_dict. """ epp = ExceptionPrettyPrinter(sio) try: raise ValueError except ValueError: ed = format_exc_info(None, None, {"exc_info": True}) assert "exception" in ed new_ed = epp(None, None, ed) assert "exception" not in new_ed def test_handles_exc_info(self, sio): """ If `exc_info` is passed in, it behaves like `format_exc_info`. """ epp = ExceptionPrettyPrinter(sio) try: raise ValueError except ValueError: epp(None, None, {"exc_info": True}) out = sio.getvalue() assert "test_handles_exc_info" in out assert "raise ValueError" in out def test_removes_exc_info_after_printing(self, sio): """ After pretty printing `exception` is removed from the event_dict. """ epp = ExceptionPrettyPrinter(sio) try: raise ValueError except ValueError: ed = epp(None, None, {"exc_info": True}) assert "exc_info" not in ed def test_nop_if_no_exception(self, sio): """ If there is no exception, don't print anything. """ epp = ExceptionPrettyPrinter(sio) epp(None, None, {}) assert "" == sio.getvalue() def test_own_exc_info(self, sio): """ If exc_info is a tuple, use it. """ epp = ExceptionPrettyPrinter(sio) try: raise ValueError("XXX") except ValueError: ei = sys.exc_info() epp(None, None, {"exc_info": ei}) assert "XXX" in sio.getvalue() def test_exception_on_py3(self, sio): """ On Python 3, it's also legal to pass an Exception. """ epp = ExceptionPrettyPrinter(sio) try: raise ValueError("XXX") except ValueError as e: epp(None, None, {"exc_info": e}) assert "XXX" in sio.getvalue() @pytest.fixture def sir(): return StackInfoRenderer() class TestStackInfoRenderer: def test_removes_stack_info(self, sir): """ The `stack_info` key is removed from `event_dict`. """ ed = sir(None, None, {"stack_info": True}) assert "stack_info" not in ed def test_adds_stack_if_asked(self, sir): """ If `stack_info` is true, `stack` is added. """ ed = sir(None, None, {"stack_info": True}) assert "stack" in ed def test_renders_correct_stack(self, sir): """ The rendered stack is correct. """ ed = sir(None, None, {"stack_info": True}) assert 'ed = sir(None, None, {"stack_info": True})' in ed["stack"] def test_additional_ignores(self): """ Filtering of names works. """ sir = StackInfoRenderer(["tests.additional_frame"]) ed = additional_frame( functools.partial(sir, None, None, {"stack_info": True}) ) assert "additional_frame.py" not in ed["stack"] class TestFigureOutExcInfo: @pytest.mark.parametrize("true_value", [True, 1, 1.1]) def test_obtains_exc_info_on_True(self, true_value): """ If the passed argument evaluates to True obtain exc_info ourselves. """ try: 0 / 0 except Exception: assert sys.exc_info() == _figure_out_exc_info(true_value) else: pytest.fail("Exception not raised.") def test_py3_exception_no_traceback(self): """ Exceptions without tracebacks are simply returned with None for traceback. """ e = ValueError() assert (e.__class__, e, None) == _figure_out_exc_info(e) class TestCallsiteParameterAdder: parameter_strings = { "pathname", "filename", "module", "func_name", "lineno", "thread", "thread_name", "process", "process_name", } # Exclude QUAL_NAME from the general set to keep parity with stdlib # LogRecord-derived parameters. QUAL_NAME is tested separately. _all_parameters = { p for p in set(CallsiteParameter) if p is not CallsiteParameter.QUAL_NAME } def test_all_parameters(self) -> None: """ All callsite parameters are included in ``self.parameter_strings`` and the dictionary returned by ``self.get_callsite_parameters`` contains keys for all callsite parameters. """ assert self.parameter_strings == { member.value for member in self._all_parameters } assert self.parameter_strings == self.get_callsite_parameters().keys() @pytest.mark.skipif( sys.version_info < (3, 11), reason="QUAL_NAME requires Python 3.11+" ) def test_qual_name_structlog(self) -> None: """ QUAL_NAME is added for structlog-originated events on Python 3.11+. """ processor = CallsiteParameterAdder( parameters={CallsiteParameter.QUAL_NAME} ) event_dict: EventDict = {"event": "msg"} actual = processor(None, None, event_dict) assert actual["qual_name"].endswith( f"{self.__class__.__name__}.test_qual_name_structlog" ) def test_qual_name_logging_origin_absent(self) -> None: """ QUAL_NAME is not sourced from stdlib LogRecord and remains absent (because it doesn't exist). """ processor = CallsiteParameterAdder( parameters={CallsiteParameter.QUAL_NAME} ) record = logging.LogRecord( "name", logging.INFO, __file__, 0, "message", None, None, "func", ) event_dict: EventDict = { "event": "message", "_record": record, "_from_structlog": False, } actual = processor(None, None, event_dict) assert "qual_name" not in actual @pytest.mark.asyncio @pytest.mark.parametrize( ("wrapper_class", "method_name"), [ (structlog.stdlib.BoundLogger, "ainfo"), (structlog.stdlib.AsyncBoundLogger, "info"), ], ) async def test_async(self, wrapper_class, method_name) -> None: """ Callsite information for async invocations are correct. """ string_io = StringIO() class StringIOLogger(structlog.PrintLogger): def __init__(self): super().__init__(file=string_io) processor = self.make_processor(None, ["concurrent", "threading"]) structlog.configure( processors=[processor, JSONRenderer()], logger_factory=StringIOLogger, wrapper_class=wrapper_class, cache_logger_on_first_use=True, ) logger = structlog.stdlib.get_logger() callsite_params = self.get_callsite_parameters() await getattr(logger, method_name)("baz") logger_params = json.loads(string_io.getvalue()) # These are different when running under async for key in ["thread", "thread_name"]: callsite_params.pop(key) logger_params.pop(key) assert {"event": "baz", **callsite_params} == logger_params def test_additional_ignores(self, monkeypatch: pytest.MonkeyPatch) -> None: """ Stack frames from modules with names that start with values in `additional_ignores` are ignored when determining the callsite. """ test_message = "test message" additional_ignores = ["tests.additional_frame"] processor = self.make_processor(None, additional_ignores) event_dict: EventDict = {"event": test_message} # Warning: the next two lines must appear exactly like this to make # line numbers match. callsite_params = self.get_callsite_parameters(1) actual = processor(None, None, event_dict) expected = {"event": test_message, **callsite_params} assert expected == actual @pytest.mark.parametrize( ("origin", "parameter_strings"), itertools.product( ["logging", "structlog"], [ None, *[{parameter} for parameter in parameter_strings], set(), parameter_strings, {"pathname", "filename"}, {"module", "func_name"}, ], ), ) def test_processor( self, origin: str, parameter_strings: set[str] | None, ): """ The correct callsite parameters are added to event dictionaries. """ test_message = "test message" processor = self.make_processor(parameter_strings) if origin == "structlog": event_dict: EventDict = {"event": test_message} callsite_params = self.get_callsite_parameters() actual = processor(None, None, event_dict) elif origin == "logging": callsite_params = self.get_callsite_parameters() record = logging.LogRecord( "name", logging.INFO, callsite_params["pathname"], callsite_params["lineno"], test_message, None, None, callsite_params["func_name"], ) event_dict: EventDict = { "event": test_message, "_record": record, "_from_structlog": False, } actual = processor(None, None, event_dict) else: pytest.fail(f"invalid origin {origin}") actual = { key: value for key, value in actual.items() if not key.startswith("_") } callsite_params = self.filter_parameter_dict( callsite_params, parameter_strings ) expected = {"event": test_message, **callsite_params} assert expected == actual @pytest.mark.parametrize( ("setup", "origin", "parameter_strings"), itertools.product( ["common-without-pre", "common-with-pre", "shared", "everywhere"], ["logging", "structlog"], [ None, *[{parameter} for parameter in parameter_strings], set(), parameter_strings, {"pathname", "filename"}, {"module", "func_name"}, ], ), ) def test_e2e( self, setup: str, origin: str, parameter_strings: set[str] | None, ) -> None: """ Logging output contains the correct callsite parameters. """ logger = logging.Logger(sys._getframe().f_code.co_name) string_io = StringIO() handler = logging.StreamHandler(string_io) processors = [self.make_processor(parameter_strings)] if setup == "common-without-pre": common_processors = processors formatter = ProcessorFormatter( processors=[*processors, JSONRenderer()] ) elif setup == "common-with-pre": common_processors = processors formatter = ProcessorFormatter( foreign_pre_chain=processors, processors=[JSONRenderer()], ) elif setup == "shared": common_processors = [] formatter = ProcessorFormatter( processors=[*processors, JSONRenderer()], ) elif setup == "everywhere": common_processors = processors formatter = ProcessorFormatter( foreign_pre_chain=processors, processors=[*processors, JSONRenderer()], ) else: pytest.fail(f"invalid setup {setup}") handler.setFormatter(formatter) handler.setLevel(0) logger.addHandler(handler) logger.setLevel(0) test_message = "test message" if origin == "logging": callsite_params = self.get_callsite_parameters() logger.info(test_message) elif origin == "structlog": ctx = {} bound_logger = BoundLogger( logger, [*common_processors, ProcessorFormatter.wrap_for_formatter], ctx, ) callsite_params = self.get_callsite_parameters() bound_logger.info(test_message) else: pytest.fail(f"invalid origin {origin}") callsite_params = self.filter_parameter_dict( callsite_params, parameter_strings ) actual = { key: value for key, value in json.loads(string_io.getvalue()).items() if not key.startswith("_") } expected = {"event": test_message, **callsite_params} assert expected == actual def test_pickeable_callsite_parameter_adder(self) -> None: """ An instance of ``CallsiteParameterAdder`` can be pickled. This functionality may be used to propagate structlog configurations to subprocesses. """ pickle.dumps(CallsiteParameterAdder()) @classmethod def make_processor( cls, parameter_strings: set[str] | None, additional_ignores: list[str] | None = None, ) -> CallsiteParameterAdder: """ Creates a ``CallsiteParameterAdder`` with parameters matching the supplied *parameter_strings* values and with the supplied *additional_ignores* values. Args: parameter_strings: Strings for which corresponding ``CallsiteParameters`` should be included in the resulting ``CallsiteParameterAdded``. additional_ignores: Used as *additional_ignores* for the resulting ``CallsiteParameterAdded``. """ if parameter_strings is None: return CallsiteParameterAdder( parameters=cls._all_parameters, additional_ignores=additional_ignores, ) parameters = cls.filter_parameters(parameter_strings) return CallsiteParameterAdder( parameters=parameters, additional_ignores=additional_ignores, ) @classmethod def filter_parameters( cls, parameter_strings: set[str] | None ) -> set[CallsiteParameter]: """ Returns a set containing all ``CallsiteParameter`` members with values that are in ``parameter_strings``. Args: parameter_strings: The parameters strings for which corresponding ``CallsiteParameter`` members should be returned. If this value is `None` then all ``CallsiteParameter`` will be returned. """ if parameter_strings is None: return cls._all_parameters return { parameter for parameter in cls._all_parameters if parameter.value in parameter_strings } @classmethod def filter_parameter_dict( cls, input: dict[str, object], parameter_strings: set[str] | None ) -> dict[str, object]: """ Returns a dictionary that is equivalent to *input* but with all keys not in *parameter_strings* removed. Args: parameter_strings: The keys to keep in the dictionary, if this value is ``None`` then all keys matching ``cls.parameter_strings`` are kept. """ if parameter_strings is None: parameter_strings = cls.parameter_strings return { key: value for key, value in input.items() if key in parameter_strings } @classmethod def get_callsite_parameters(cls, offset: int = 1) -> dict[str, object]: """ This function creates dictionary of callsite parameters for the line that is ``offset`` lines after the invocation of this function. Args: offset: The amount of lines after the invocation of this function that callsite parameters should be generated for. """ frame_info = inspect.stack()[1] frame_traceback = inspect.getframeinfo(frame_info[0]) return { "pathname": frame_traceback.filename, "filename": os.path.basename(frame_traceback.filename), "module": os.path.splitext( os.path.basename(frame_traceback.filename) )[0], "func_name": frame_info.function, "lineno": frame_info.lineno + offset, "thread": threading.get_ident(), "thread_name": threading.current_thread().name, "process": os.getpid(), "process_name": get_processname(), } class TestRenameKey: def test_rename_once(self): """ Renaming event to something else works. """ assert {"msg": "hi", "foo": "bar"} == EventRenamer("msg")( None, None, {"event": "hi", "foo": "bar"} ) def test_rename_twice(self): """ Renaming both from and to `event` works. """ assert { "msg": "hi", "event": "fabulous", "foo": "bar", } == EventRenamer("msg", "_event")( None, None, {"event": "hi", "foo": "bar", "_event": "fabulous"} ) def test_replace_by_key_is_optional(self): """ The key that is renamed to `event` doesn't have to exist. """ assert {"msg": "hi", "foo": "bar"} == EventRenamer("msg", "missing")( None, None, {"event": "hi", "foo": "bar"} ) structlog-25.5.0/tests/processors/test_renderers.py0000644000000000000000000004370615077624341017550 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. from __future__ import annotations import datetime import json import pickle import pytest import time_machine from structlog.processors import ( ExceptionRenderer, JSONRenderer, KeyValueRenderer, LogfmtRenderer, MaybeTimeStamper, TimeStamper, _json_fallback_handler, format_exc_info, ) from structlog.threadlocal import wrap_dict from ..helpers import CustomError try: import simplejson except ImportError: simplejson = None class TestKeyValueRenderer: def test_sort_keys(self, event_dict): """ Keys are sorted if sort_keys is set. """ rv = KeyValueRenderer(sort_keys=True)(None, None, event_dict) assert r"a= b=[3, 4] x=7 y='test' z=(1, 2)" == rv def test_order_complete(self, event_dict): """ Orders keys according to key_order. """ rv = KeyValueRenderer(key_order=["y", "b", "a", "z", "x"])( None, None, event_dict ) assert r"y='test' b=[3, 4] a= z=(1, 2) x=7" == rv def test_order_missing(self, event_dict): """ Missing keys get rendered as None. """ rv = KeyValueRenderer(key_order=["c", "y", "b", "a", "z", "x"])( None, None, event_dict ) assert r"c=None y='test' b=[3, 4] a= z=(1, 2) x=7" == rv def test_order_missing_dropped(self, event_dict): """ Missing keys get dropped """ rv = KeyValueRenderer( key_order=["c", "y", "b", "a", "z", "x"], drop_missing=True )(None, None, event_dict) assert r"y='test' b=[3, 4] a= z=(1, 2) x=7" == rv def test_order_extra(self, event_dict): """ Extra keys get sorted if sort_keys=True. """ event_dict["B"] = "B" event_dict["A"] = "A" rv = KeyValueRenderer( key_order=["c", "y", "b", "a", "z", "x"], sort_keys=True )(None, None, event_dict) assert ( r"c=None y='test' b=[3, 4] a= z=(1, 2) x=7 A='A' B='B'" ) == rv def test_order_sorted_missing_dropped(self, event_dict): """ Keys get sorted if sort_keys=True and extras get dropped. """ event_dict["B"] = "B" event_dict["A"] = "A" rv = KeyValueRenderer( key_order=["c", "y", "b", "a", "z", "x"], sort_keys=True, drop_missing=True, )(None, None, event_dict) assert r"y='test' b=[3, 4] a= z=(1, 2) x=7 A='A' B='B'" == rv def test_random_order(self, event_dict): """ No special ordering doesn't blow up. """ rv = KeyValueRenderer()(None, None, event_dict) assert isinstance(rv, str) @pytest.mark.parametrize("rns", [True, False]) def test_repr_native_str(self, rns): """ repr_native_str=False doesn't repr on native strings. """ rv = KeyValueRenderer(repr_native_str=rns)( None, None, {"event": "哈", "key": 42, "key2": "哈"} ) cnt = rv.count("哈") assert 2 == cnt class TestLogfmtRenderer: def test_sort_keys(self, event_dict): """ Keys are sorted if sort_keys is set. """ rv = LogfmtRenderer(sort_keys=True)(None, None, event_dict) assert r'a= b="[3, 4]" x=7 y=test z="(1, 2)"' == rv def test_order_complete(self, event_dict): """ Orders keys according to key_order. """ rv = LogfmtRenderer(key_order=["y", "b", "a", "z", "x"])( None, None, event_dict ) assert r'y=test b="[3, 4]" a= z="(1, 2)" x=7' == rv def test_order_missing(self, event_dict): """ Missing keys get rendered as None. """ rv = LogfmtRenderer(key_order=["c", "y", "b", "a", "z", "x"])( None, None, event_dict ) assert r'c= y=test b="[3, 4]" a= z="(1, 2)" x=7' == rv def test_order_missing_dropped(self, event_dict): """ Missing keys get dropped """ rv = LogfmtRenderer( key_order=["c", "y", "b", "a", "z", "x"], drop_missing=True )(None, None, event_dict) assert r'y=test b="[3, 4]" a= z="(1, 2)" x=7' == rv def test_order_extra(self, event_dict): """ Extra keys get sorted if sort_keys=True. """ event_dict["B"] = "B" event_dict["A"] = "A" rv = LogfmtRenderer( key_order=["c", "y", "b", "a", "z", "x"], sort_keys=True )(None, None, event_dict) assert ( r'c= y=test b="[3, 4]" a= z="(1, 2)" x=7 A=A B=B' ) == rv def test_order_sorted_missing_dropped(self, event_dict): """ Keys get sorted if sort_keys=True and extras get dropped. """ event_dict["B"] = "B" event_dict["A"] = "A" rv = LogfmtRenderer( key_order=["c", "y", "b", "a", "z", "x"], sort_keys=True, drop_missing=True, )(None, None, event_dict) assert r'y=test b="[3, 4]" a= z="(1, 2)" x=7 A=A B=B' == rv def test_random_order(self, event_dict): """ No special ordering doesn't blow up. """ rv = LogfmtRenderer()(None, None, event_dict) assert isinstance(rv, str) def test_empty_event_dict(self): """ Empty event dict renders as empty string. """ rv = LogfmtRenderer()(None, None, {}) assert "" == rv def test_bool_as_flag(self): """ If activated, render ``{"a": True}`` as ``a`` instead of ``a=true``. """ event_dict = {"a": True, "b": False} rv_abbrev = LogfmtRenderer(bool_as_flag=True)(None, None, event_dict) assert r"a b=false" == rv_abbrev rv_no_abbrev = LogfmtRenderer(bool_as_flag=False)( None, None, event_dict ) assert r"a=true b=false" == rv_no_abbrev def test_reference_format(self): """ Test rendering according to example at https://pkg.go.dev/github.com/kr/logfmt """ event_dict = { "foo": "bar", "a": 14, "baz": "hello kitty", "cool%story": "bro", "f": True, "%^asdf": True, } rv = LogfmtRenderer()(None, None, event_dict) assert 'foo=bar a=14 baz="hello kitty" cool%story=bro f %^asdf' == rv def test_equal_sign_or_space_in_value(self): """ Values with equal signs are always quoted. """ event_dict = { "without": "somevalue", "withequal": "some=value", "withspace": "some value", } rv = LogfmtRenderer()(None, None, event_dict) assert ( r'without=somevalue withequal="some=value" withspace="some value"' == rv ) def test_invalid_key(self): """ Keys cannot contain space characters. """ event_dict = {"invalid key": "somevalue"} with pytest.raises(ValueError, match='Invalid key: "invalid key"'): LogfmtRenderer()(None, None, event_dict) def test_newline_in_value(self): """ Newlines in values are escaped. """ event_dict = {"with_newline": "some\nvalue"} rv = LogfmtRenderer()(None, None, event_dict) assert r"with_newline=some\nvalue" == rv @pytest.mark.parametrize( ("raw", "escaped"), [ # Slash by itself does not need to be escaped. (r"a\slash", r"a\slash"), # A quote requires quoting, and escaping the quote. ('a"quote', r'"a\"quote"'), # If anything triggers quoting of the string, then the slash must # be escaped. ( r'a\slash with space or a"quote', r'"a\\slash with space or a\"quote"', ), ( r"I want to render this \"string\" with logfmtrenderer", r'"I want to render this \\\"string\\\" with logfmtrenderer"', ), ], ) def test_escaping(self, raw, escaped): """ Backslashes and quotes are escaped. """ rv = LogfmtRenderer()(None, None, {"key": raw}) assert f"key={escaped}" == rv class TestJSONRenderer: def test_renders_json(self, event_dict): """ Renders a predictable JSON string. """ rv = JSONRenderer(sort_keys=True)(None, None, event_dict) assert ( r'{"a": "", "b": [3, 4], "x": 7, ' r'"y": "test", "z": ' r"[1, 2]}" ) == rv def test_FallbackEncoder_handles_ThreadLocalDictWrapped_dicts(self): """ Our fallback handling handles properly ThreadLocalDictWrapper values. """ with pytest.deprecated_call(): d = wrap_dict(dict) s = json.dumps(d({"a": 42}), default=_json_fallback_handler) assert '{"a": 42}' == s def test_FallbackEncoder_falls_back(self): """ The fallback handler uses repr if it doesn't know the type. """ s = json.dumps( {"date": datetime.date(1980, 3, 25)}, default=_json_fallback_handler, ) assert '{"date": "datetime.date(1980, 3, 25)"}' == s def test_serializer(self): """ A custom serializer is used if specified. """ jr = JSONRenderer(serializer=lambda obj, **kw: {"a": 42}) obj = object() assert {"a": 42} == jr(None, None, obj) def test_custom_fallback(self): """ A custom fallback handler can be used. """ jr = JSONRenderer(default=lambda x: repr(x)[::-1]) d = {"date": datetime.date(1980, 3, 25)} assert '{"date": ")52 ,3 ,0891(etad.emitetad"}' == jr(None, None, d) @pytest.mark.skipif(simplejson is None, reason="simplejson is missing.") def test_simplejson(self, event_dict): """ Integration test with simplejson. """ jr = JSONRenderer(serializer=simplejson.dumps) assert { "a": "", "b": [3, 4], "x": 7, "y": "test", "z": [1, 2], } == json.loads(jr(None, None, event_dict)) class TestTimeStamper: def test_disallows_non_utc_unix_timestamps(self): """ A asking for a UNIX timestamp with a timezone that's not UTC raises a ValueError. """ with pytest.raises(ValueError, match="UNIX timestamps are always UTC"): TimeStamper(utc=False) def test_inserts_utc_unix_timestamp_by_default(self): """ Per default a float UNIX timestamp is used. """ ts = TimeStamper() d = ts(None, None, {}) assert isinstance(d["timestamp"], float) @time_machine.travel("1980-03-25 16:00:00") def test_local(self): """ Timestamp in local timezone work. Due to historic reasons, the default format does not include a timezone. """ ts = TimeStamper(fmt="iso", utc=False) d = ts(None, None, {}) assert "1980-03-25T16:00:00" == d["timestamp"] @time_machine.travel("1980-03-25 16:00:00") def test_formats(self): """ The fmt string is respected. """ ts = TimeStamper(fmt="%Y") d = ts(None, None, {}) assert "1980" == d["timestamp"] @time_machine.travel( datetime.datetime(1980, 3, 25, 16, 0, 0, tzinfo=datetime.timezone.utc) ) def test_inserts_formatted_utc(self): """ The fmt string in UTC timezone works. """ ts = TimeStamper(fmt="%Y-%m-%d %H:%M:%S %Z") d = ts(None, None, {}) assert "1980-03-25 16:00:00 UTC" == d["timestamp"] @time_machine.travel("1980-03-25 16:00:00") def test_inserts_formatted_local(self): """ The fmt string in local timezone works. """ local_tz = datetime.datetime.now().astimezone().tzname() ts = TimeStamper(fmt="%Y-%m-%d %H:%M:%S %Z", utc=False) d = ts(None, None, {}) assert f"1980-03-25 16:00:00 {local_tz}" == d["timestamp"] @time_machine.travel("1980-03-25 16:00:00") def test_tz_aware(self): """ The timestamp that is used for formatting is timezone-aware. """ ts = TimeStamper(fmt="%z") d = ts(None, None, {}) assert "" == datetime.datetime.now().strftime("%z") # noqa: DTZ005 assert "" != d["timestamp"] @time_machine.travel( datetime.datetime(1980, 3, 25, 16, 0, 0, tzinfo=datetime.timezone.utc) ) def test_adds_Z_to_iso(self): """ stdlib's isoformat is buggy, so we fix it. """ ts = TimeStamper(fmt="iso", utc=True) d = ts(None, None, {}) assert "1980-03-25T16:00:00Z" == d["timestamp"] @time_machine.travel("1980-03-25 16:00:00") def test_key_can_be_specified(self): """ Timestamp is stored with the specified key. """ ts = TimeStamper(fmt="%m", key="month") d = ts(None, None, {}) assert "03" == d["month"] @time_machine.travel("1980-03-25 16:00:00", tick=False) @pytest.mark.parametrize("fmt", [None, "%Y"]) @pytest.mark.parametrize("utc", [True, False]) @pytest.mark.parametrize("key", [None, "other-key"]) @pytest.mark.parametrize("proto", range(pickle.HIGHEST_PROTOCOL + 1)) def test_pickle(self, fmt, utc, key, proto): """ TimeStamper is serializable. """ # UNIX timestamps must be UTC. if fmt is None and not utc: pytest.skip() ts = TimeStamper() assert ts(None, None, {}) == pickle.loads(pickle.dumps(ts, proto))( None, None, {} ) def test_apply_time_machine_after_instantiation(self): """ Freezing time after instantiation of TimeStamper works. """ ts = TimeStamper(fmt="iso", utc=False) # Simulate a different local time by traveling to a different timestamp # after the stamper was created. with time_machine.travel("1980-03-25 17:00:00"): d = ts(None, None, {}) assert "1980-03-25T17:00:00" == d["timestamp"] class TestMaybeTimeStamper: def test_overwrite(self): """ If there is a timestamp, leave it. """ mts = MaybeTimeStamper() assert {"timestamp": 42} == mts(None, None, {"timestamp": 42}) def test_overwrite_custom_key(self): """ If there is a timestamp with a custom key, leave it. """ mts = MaybeTimeStamper(key="timestamp2") assert {"timestamp2": 42} == mts(None, None, {"timestamp2": 42}) def test_none(self): """ If there is no timestamp, add one. """ mts = MaybeTimeStamper() assert "timestamp" in mts(None, None, {}) class TestFormatExcInfo: def test_custom_formatter(self): """ The exception formatter can be changed. """ formatter = ExceptionRenderer(lambda _: "There is no exception!") try: raise CustomError("test") except CustomError as e: exc = e assert formatter(None, None, {"exc_info": exc}) == { "exception": "There is no exception!" } @pytest.mark.parametrize("ei", [False, None, ""]) def test_nop(self, ei): """ If exc_info is falsey, only remove the key. """ assert {} == ExceptionRenderer()(None, None, {"exc_info": ei}) def test_nop_missing(self): """ If event dict doesn't contain exc_info, do nothing. """ assert {} == ExceptionRenderer()(None, None, {}) def test_formats_tuple(self): """ If exc_info is an arbitrary 3-tuple, it is not used. """ formatter = ExceptionRenderer(lambda exc_info: exc_info) d = formatter(None, None, {"exc_info": (None, None, 42)}) assert {} == d def test_gets_exc_info_on_bool(self): """ If exc_info is True, it is obtained using sys.exc_info(). """ # monkeypatching sys.exc_info makes currently pytest return 1 on # success. try: raise ValueError("test") except ValueError: d = ExceptionRenderer()(None, None, {"exc_info": True}) assert "exc_info" not in d assert 'raise ValueError("test")' in d["exception"] assert "ValueError: test" in d["exception"] def test_exception(self): """ Passing exceptions as exc_info is valid. """ formatter = ExceptionRenderer(lambda exc_info: exc_info) try: raise ValueError("test") except ValueError as e: exc = e else: pytest.fail("Exception not raised.") assert { "exception": (ValueError, exc, exc.__traceback__) } == formatter(None, None, {"exc_info": exc}) def test_exception_without_traceback(self): """ If an Exception is missing a traceback, render it anyway. """ rv = ExceptionRenderer()( None, None, {"exc_info": Exception("no traceback!")} ) assert {"exception": "Exception: no traceback!"} == rv def test_format_exception(self): """ "format_exception" is the "ExceptionRenderer" with default settings. """ try: raise ValueError("test") except ValueError as e: a = format_exc_info(None, None, {"exc_info": e}) b = ExceptionRenderer()(None, None, {"exc_info": e}) assert a == b @pytest.mark.parametrize("ei", [True, (None, None, None)]) def test_no_exception(self, ei): """ A missing exception does not blow up. """ assert {} == format_exc_info(None, None, {"exc_info": ei}) structlog-25.5.0/tests/typing/api.py0000644000000000000000000003011515077624341014367 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the MIT License. See the LICENSE file in the root of this # repository for complete details. """ Make sure our configuration examples actually pass the type checker. """ from __future__ import annotations import logging import logging.config from typing import Any, Callable import structlog from structlog.processors import CallsiteParameter from structlog.typing import FilteringBoundLogger bl = structlog.get_logger() bl.msg("hello", whom="world", x=42, y={}) bls: structlog.stdlib.BoundLogger = structlog.get_logger() bls.info("hello", whom="world", x=42, y={}) def bytes_dumps( __obj: Any, /, default: Callable[[Any], Any] | None = None, option: int | None = None, ) -> bytes: """ Test with orjson's signature taken from . """ return b"{}" structlog.configure( processors=[structlog.processors.JSONRenderer(serializer=bytes_dumps)] ) structlog.configure( processors=[ structlog.stdlib.render_to_log_args_and_kwargs, structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.UnicodeDecoder(), structlog.stdlib.render_to_log_kwargs, ], context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) structlog.configure( processors=[ structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), ) formatter = structlog.stdlib.ProcessorFormatter( processor=structlog.dev.ConsoleRenderer(), ) formatter = structlog.stdlib.ProcessorFormatter( processors=[ structlog.processors.CallsiteParameterAdder(), structlog.processors.CallsiteParameterAdder( {CallsiteParameter.FILENAME}, ["threading"] ), structlog.processors.CallsiteParameterAdder( {CallsiteParameter.LINENO}, additional_ignores=["threading"] ), structlog.processors.CallsiteParameterAdder( parameters={CallsiteParameter.FUNC_NAME}, additional_ignores=["threading"], ), structlog.processors.CallsiteParameterAdder( [ CallsiteParameter.FILENAME, CallsiteParameter.FUNC_NAME, CallsiteParameter.LINENO, ] ), structlog.processors.CallsiteParameterAdder( parameters=[ CallsiteParameter.FILENAME, CallsiteParameter.FUNC_NAME, CallsiteParameter.LINENO, ] ), structlog.processors.CallsiteParameterAdder( parameters=[ CallsiteParameter.FILENAME, CallsiteParameter.FUNC_NAME, CallsiteParameter.LINENO, ] ), ], ) handler = logging.StreamHandler() handler.setFormatter(formatter) root_logger = logging.getLogger() root_logger.addHandler(handler) root_logger.setLevel(logging.INFO) timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S") shared_processors: list[structlog.typing.Processor] = [ structlog.stdlib.add_log_level, timestamper, ] structlog.configure( processors=[ *shared_processors, structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], logger_factory=structlog.stdlib.LoggerFactory(), cache_logger_on_first_use=True, ) formatter = structlog.stdlib.ProcessorFormatter( processor=structlog.dev.ConsoleRenderer(), foreign_pre_chain=[ structlog.stdlib.ExtraAdder(), structlog.stdlib.ExtraAdder(allow=None), structlog.stdlib.ExtraAdder(None), structlog.stdlib.ExtraAdder(allow=["k1", "k2"]), structlog.stdlib.ExtraAdder({"k1", "k2"}), *shared_processors, ], ) timestamper = structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S") pre_chain = [ # Add the log level and a timestamp to the event_dict if the log entry # is not from structlog. structlog.stdlib.add_log_level, timestamper, ] logging.config.dictConfig( { "version": 1, "disable_existing_loggers": False, "formatters": { "plain": { "()": structlog.stdlib.ProcessorFormatter, "processor": structlog.dev.ConsoleRenderer(colors=False), "foreign_pre_chain": pre_chain, }, "colored": { "()": structlog.stdlib.ProcessorFormatter, "processor": structlog.dev.ConsoleRenderer(colors=True), "foreign_pre_chain": pre_chain, }, }, "handlers": { "default": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "colored", }, "file": { "level": "DEBUG", "class": "logging.handlers.WatchedFileHandler", "filename": "test.log", "formatter": "plain", }, }, "loggers": { "": { "handlers": ["default", "file"], "level": "DEBUG", "propagate": True, }, }, } ) structlog.configure( processors=[ structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), timestamper, structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.UnicodeDecoder(), structlog.processors.JSONRenderer(), ], context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.UnicodeDecoder(), structlog.processors.LogfmtRenderer(), ], context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.TimeStamper(fmt="iso"), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.UnicodeDecoder(), structlog.processors.JSONRenderer(), ], context_class=dict, logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.AsyncBoundLogger, cache_logger_on_first_use=True, ) # Regression test for # https://github.com/wemake-services/wemake-django-template/ structlog.configure( processors=[ structlog.stdlib.filter_by_level, structlog.processors.TimeStamper(fmt="iso"), structlog.stdlib.add_logger_name, structlog.stdlib.add_log_level, structlog.stdlib.PositionalArgumentsFormatter(), structlog.processors.StackInfoRenderer(), structlog.processors.format_exc_info, structlog.processors.UnicodeDecoder(), structlog.processors.ExceptionPrettyPrinter(), structlog.stdlib.ProcessorFormatter.wrap_for_formatter, ], context_class=structlog.threadlocal.wrap_dict(dict), logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.stdlib.BoundLogger, cache_logger_on_first_use=True, ) with structlog.threadlocal.bound_threadlocal(x=42): pass def typecheck_filtering_return() -> None: fblogger: FilteringBoundLogger = structlog.get_logger(__name__) fblog = fblogger.bind(key1="value1", key2="value2", key3="value3") fblog.info("values bound") fblog = fblog.unbind("key1") fblog.debug("value unbound") fblog = fblog.try_unbind("bad_key") fblog.warn("no value unbound because key not defined") fblog = fblog.new(new="value") fblog.info("this is a whole new logger") fblog.log(logging.CRITICAL, "this is synchronously CRITICAL") async def typecheck_filtering_return_async() -> None: fblogger: FilteringBoundLogger = structlog.get_logger(__name__) await fblogger.adebug("async debug") await fblogger.ainfo("async info") await fblogger.awarning("async warning") await fblogger.awarn("async warn") await fblogger.aerror("async error") await fblogger.afatal("fatal error") await fblogger.aexception("async exception") await fblogger.acritical("async critical") await fblogger.amsg("async msg") await fblogger.alog(logging.CRITICAL, "async log") async def typecheck_stdlib_async() -> None: logger: structlog.stdlib.BoundLogger = structlog.get_logger(__name__) await logger.adebug("async debug") await logger.ainfo("async info") await logger.awarning("async warning") await logger.aerror("async error") await logger.afatal("fatal error") await logger.aexception("async exception") await logger.acritical("async critical") await logger.alog(logging.CRITICAL, "async log") def typecheck_bound_logger_return() -> None: blogger: structlog.BoundLogger = structlog.get_logger(__name__) blog = blogger.bind(key1="value1", key2="value2", key3="value3") blog = blog.unbind("key1") blog = blog.try_unbind("bad_key") blog = blog.new(new="value") # Structured tracebacks and ExceptionRenderer with ExceptionDictTransformer struct_tb: structlog.tracebacks.Trace = structlog.tracebacks.extract( ValueError, ValueError("onoes"), None ) try: raise ValueError("onoes") except ValueError as e: struct_tb = structlog.tracebacks.extract(type(e), e, e.__traceback__) structlog.configure( processors=[ structlog.processors.ExceptionRenderer( structlog.tracebacks.ExceptionDictTransformer() ), structlog.processors.JSONRenderer(), ] ) fbl: FilteringBoundLogger = structlog.get_logger() fbl.info("Hello %s! The answer is %d.", "World", 42, x=1) # Introspection level: int = fbl.get_effective_level() is_active: bool = fbl.is_enabled_for(logging.INFO) is_active = fbl.is_enabled_for(20) # contextvars @structlog.contextvars.bound_contextvars(x=42) def f() -> None: with structlog.contextvars.bound_contextvars(y=23): pass # ConsoleRenderer properties cr = structlog.dev.ConsoleRenderer.get_active() cr.exception_formatter cr.exception_formatter = structlog.dev.plain_traceback cr.exception_formatter = structlog.dev.better_traceback cr.columns cr.columns = [ structlog.dev.Column( "", structlog.dev.KeyValueColumnFormatter("", "", "", repr, 0) ) ] cr.colors cr.colors = False cr.force_colors cr.force_colors = False cr.level_styles cr.level_styles = {"info": "foo"} cr.sort_keys cr.sort_keys = True cr.pad_level cr.pad_level = True cr.pad_event_to cr.pad_event_to = 42 cr.timestamp_key cr.timestamp_key = "ts" cr.event_key cr.event_key = "le event" cr.repr_native_str cr.repr_native_str = True structlog-25.5.0/.gitignore0000644000000000000000000000026115077624341012557 0ustar00*.pyc *.pyo .DS_Store .cache .coverage* .direnv .envrc .mypy_cache .pytest_cache .tox .vscode .idea benchmarks build dist docs/_build htmlcov tmp structlog.docset structlog.tgz structlog-25.5.0/LICENSE-APACHE0000644000000000000000000002367615077624341012532 0ustar00 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS structlog-25.5.0/LICENSE-MIT0000644000000000000000000000213115077624341012221 0ustar00The MIT License (MIT) Copyright (c) 2013 Hynek Schlawack and the structlog contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. structlog-25.5.0/NOTICE0000644000000000000000000000011015077624341011464 0ustar00structlog Copyright 2013 Hynek Schlawack and the structlog contributors structlog-25.5.0/pyproject.toml0000644000000000000000000001632115077624341013507 0ustar00# SPDX-License-Identifier: MIT OR Apache-2.0 [build-system] requires = ["hatchling", "hatch-vcs", "hatch-fancy-pypi-readme>=22.8.0"] build-backend = "hatchling.build" [project] dynamic = ["readme", "version"] name = "structlog" description = "Structured Logging for Python" authors = [{ name = "Hynek Schlawack", email = "hs@ox.cx" }] requires-python = ">=3.8" license = "MIT OR Apache-2.0" keywords = ["logging", "structured", "structure", "log"] classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: System :: Logging", "Typing :: Typed", ] dependencies = ["typing-extensions; python_version<'3.11'"] [project.urls] Documentation = "https://www.structlog.org/" Changelog = "https://github.com/hynek/structlog/blob/main/CHANGELOG.md" GitHub = "https://github.com/hynek/structlog" Funding = "https://github.com/sponsors/hynek" Tidelift = "https://tidelift.com?utm_source=lifter&utm_medium=referral&utm_campaign=hynek" Mastodon = "https://mastodon.social/@hynek" Bluesky = "https://bsky.app/profile/hynek.me" Twitter = "https://twitter.com/hynek" [dependency-groups] tests = [ "time-machine>=2.14.1", "pytest-asyncio>=0.17", "pytest>=6.0", "simplejson", ] # Need Twisted & Rich for stubs. # Otherwise mypy fails in tox. typing = ["mypy>=1.4", "rich", "twisted"] docs = [ "cogapp", "myst-parser", "sphinx", "sphinx-design", "sphinx-copybutton", "sphinx-book-theme", "sphinx-notfound-page", "sphinxcontrib-mermaid", "sphinxext-opengraph", "twisted", ] dev = [ {include-group = "tests"}, {include-group = "typing"}, ] [tool.hatch.version] source = "vcs" raw-options = { local_scheme = "no-local-version" } [tool.pytest.ini_options] addopts = ["--strict-markers", "--strict-config", "--import-mode=importlib"] testpaths = "tests" xfail_strict = true filterwarnings = [ "once::Warning", # Probably "ignore:unclosed event loop:ResourceWarning", ] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" [tool.coverage.run] parallel = true branch = true source = ["structlog"] [tool.coverage.paths] source = ["src", ".tox/py*/**/site-packages"] [tool.coverage.report] show_missing = true skip_covered = true omit = ["src/structlog/_greenlets.py"] [tool.interrogate] omit-covered-files = true verbose = 2 fail-under = 100 whitelist-regex = ["test_.*"] [tool.ruff] src = ["src", "tests"] line-length = 79 [tool.ruff.lint] select = ["ALL"] ignore = [ "A", # shadowing is fine "ANN", # Mypy is better at this "ARG", # unused arguments are common w/ interfaces "C901", # sometimes you trade complexity for performance "COM", # Formatter takes care of our commas "D", # We prefer our own docstring style. "E501", # leave line-length enforcement to formatter "EM101", # simple strings are fine "FBT", # bools are our friends "FIX", # Yes, we want XXX as a marker. "INP001", # sometimes we want Python files outside of packages "ISC001", # conflicts with formatter "N802", # some names are non-pep8 due to stdlib logging / Twisted "N803", # ditto "N806", # ditto "PLC0415", # sometimes, imports have to live elsewhere "PLR0913", # leave complexity to me "PLR2004", # numbers are sometimes fine "PLW1641", # unhashable is often fine "PLW2901", # overwriting a loop var can be useful "RUF001", # leave my smart characters alone "SLF001", # private members are accessed by friendly functions "T201", # prints are fine "TC", # TYPE_CHECKING blocks break autodocs "TD", # we don't follow other people's todo style "TID252", # Relative imports are fine "TRY003", # simple strings are fine "TRY004", # too many false negatives "TRY300", # else blocks are nice, but code-locality is nicer "PTH", # pathlib can be slow, so no point to rewrite ] [tool.ruff.lint.per-file-ignores] "tests/*" = [ "B018", # "useless" expressions can be useful in tests "BLE", # tests have different rules around exceptions "EM", # tests have different rules around exceptions "LOG001", # need to instantiate log messages in tests "PLC1901", # empty strings are falsey, but are less specific in tests "RUF012", # no type hints in tests "S", # it's test; chill out security "SIM300", # Yoda rocks in tests "TRY", # tests have different rules around exceptions ] [tool.ruff.lint.isort] lines-between-types = 1 lines-after-imports = 2 [tool.mypy] strict = true pretty = true show_error_codes = true enable_error_code = ["ignore-without-code"] ignore_missing_imports = true warn_return_any = false [[tool.mypy.overrides]] module = "tests.*" ignore_errors = true [[tool.mypy.overrides]] module = "tests.typing.*" ignore_errors = false [tool.ty.src] include = ["tests/typing"] [tool.pyrefly] project-includes = ["tests/typing"] [tool.hatch.metadata.hooks.fancy-pypi-readme] content-type = "text/markdown" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] text = '''

structlog mascot

''' [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "README.md" start-after = "\n" end-before = "\n" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "README.md" start-after = "\n" [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] text = """ ## Release Information """ [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "CHANGELOG.md" start-after = "" pattern = "\n(###.+?\n)## " [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] text = """ --- [Full Changelog →](https://www.structlog.org/en/stable/changelog.html) """ [[tool.hatch.metadata.hooks.fancy-pypi-readme.fragments]] path = "README.md" start-at = "## Credits" end-before = "" # Point sponsor image URLs to versions. [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] pattern = 'docs\/_static\/sponsors' replacement = 'https://www.structlog.org/en/$HFPR_VERSION/_static/sponsors' [[tool.sponcon.sponsors]] title = "Variomedia AG" url = "https://www.variomedia.de/" img = "Variomedia.svg" [[tool.sponcon.sponsors]] title = "Tidelift" url = "https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek" img = "Tidelift.svg" [[tool.sponcon.sponsors]] title = "Privacy Solutions" url = "https://privacy-solutions.org/" img = "Privacy-Solutions.svg" [[tool.sponcon.sponsors]] title = "FilePreviews" url = "https://filepreviews.io/" img = "FilePreviews.svg" [[tool.sponcon.sponsors]] title = "Polar" url = "https://polar.sh/" img = "Polar.svg" structlog-25.5.0/PKG-INFO0000644000000000000000000002247415077624341011676 0ustar00Metadata-Version: 2.4 Name: structlog Version: 25.5.0 Summary: Structured Logging for Python Project-URL: Documentation, https://www.structlog.org/ Project-URL: Changelog, https://github.com/hynek/structlog/blob/main/CHANGELOG.md Project-URL: GitHub, https://github.com/hynek/structlog Project-URL: Funding, https://github.com/sponsors/hynek Project-URL: Tidelift, https://tidelift.com?utm_source=lifter&utm_medium=referral&utm_campaign=hynek Project-URL: Mastodon, https://mastodon.social/@hynek Project-URL: Bluesky, https://bsky.app/profile/hynek.me Project-URL: Twitter, https://twitter.com/hynek Author-email: Hynek Schlawack License-Expression: MIT OR Apache-2.0 License-File: LICENSE-APACHE License-File: LICENSE-MIT License-File: NOTICE Keywords: log,logging,structure,structured Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: Apache Software License Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: System :: Logging Classifier: Typing :: Typed Requires-Python: >=3.8 Requires-Dist: typing-extensions; python_version < '3.11' Description-Content-Type: text/markdown

structlog mascot

*structlog* is *the* production-ready logging solution for Python: - **Simple**: Everything is about **functions** that take and return **dictionaries** – all hidden behind **familiar APIs**. - **Powerful**: Functions and dictionaries aren’t just simple but also powerful. *structlog* leaves *you* in control. - **Fast**: *structlog* is not hamstrung by designs of yore. Its flexibility comes not at the price of performance. Thanks to its flexible design, *you* choose whether you want *structlog* to take care of the **output** of your log entries or whether you prefer to **forward** them to an existing logging system like the standard library's `logging` module. The output format is just as flexible and *structlog* comes with support for JSON, [*logfmt*](https://brandur.org/logfmt), as well as pretty console output out-of-the-box: [![Screenshot of colorful structlog output with ConsoleRenderer](https://github.com/hynek/structlog/blob/main/docs/_static/console_renderer.png?raw=true)](https://github.com/hynek/structlog/blob/main/docs/_static/console_renderer.png?raw=true) ## Sponsors *structlog* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). Especially those generously supporting us at the *The Organization* tier and higher:

Please consider joining them to help make structlog’s maintenance more sustainable!

## Introduction *structlog* has been successfully used in production at every scale since **2013**, while embracing cutting-edge technologies like *asyncio*, context variables, or type hints as they emerged. Its paradigms proved influential enough to [help design](https://twitter.com/sirupsen/status/638330548361019392) structured logging [packages across ecosystems](https://github.com/sirupsen/logrus). ## Project Links - [**Get Help**](https://stackoverflow.com/questions/tagged/structlog) (use the *structlog* tag on Stack Overflow) - [**PyPI**](https://pypi.org/project/structlog/) - [**GitHub**](https://github.com/hynek/structlog) - [**Documentation**](https://www.structlog.org/) - [**Changelog**](https://github.com/hynek/structlog/tree/main/CHANGELOG.md) - [**Third-party Extensions**](https://github.com/hynek/structlog/wiki/Third-party-Extensions) ## *structlog* for Enterprise Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek). The maintainers of *structlog* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. ## Release Information ### Deprecated - `structlog.dev.ConsoleRenderer()`'s *pad_event* argument has been renamed to *pad_event_to* to differentiate it from the boolean *pad_level* argument. *pad_event* is now deprecated. ### Added - Added `structlog.dev.ConsoleRenderer.get_active()` that returns the currently active `structlog.dev.ConsoleRenderer()`. [#749](https://github.com/hynek/structlog/pull/749) - `structlog.dev.ConsoleRenderer()` now supports setting the `exception_formatter` attribute. You can now disable the pretty-printing of exceptions by setting it to `structlog.dev.plain_traceback`: ```python cr = structlog.dev.ConsoleRenderer.get_active() cr.exception_formatter = structlog.dev.plain_traceback ``` Same goes for *sort_keys*, *columns*, *colors*, *force_colors*, *level_styles*, *pad_event_to*, *event_key*, *timestamp_key*, and *repr_native_str*. [#749](https://github.com/hynek/structlog/pull/749) [#756](https://github.com/hynek/structlog/pull/756) [#757](https://github.com/hynek/structlog/pull/757) [#759](https://github.com/hynek/structlog/pull/759) - Added `structlog.dev.ConsoleRenderer.get_default_column_styles()` for reuse the default column styles. [#741](https://github.com/hynek/structlog/pull/741) - `structlog.testing.capture_logs()` now optionally accepts *processors* to apply before capture. [#728](https://github.com/hynek/structlog/pull/728) - `structlog.dev.RichTracebackFormatter` now exposes the upstream *code_width* parameter. Default *width* is now `None` for full terminal width. Full terminal width is now handled by Rich itself, bringing support for reflow and `COLUMN` environment variable. Passing `-1` for *width* is now deprecated and automatically replaced by `None`. [#717](https://github.com/hynek/structlog/pull/717) - Native loggers now allow the passing of a dictionary for dictionary-based interpolation `log.info("hello %(name)s!", {"name": "world"})`. [#748](https://github.com/hynek/structlog/pull/748) - On Python 3.11+, `structlog.processors.CallsiteParameterAdder` now supports `CallsiteParameter.QUAL_NAME` that adds the qualified name of the callsite, including scope and class names. This is only available for *structlog*-originated events since the standard library has no equivalent. - `structlog.stdlib.LoggerFactory` now supports the *stacklevel* parameter. [#763](https://github.com/hynek/structlog/pull/763) ### Changed - `structlog.dev.rich_traceback()` now throws a more helpful error when Rich is missing. [#735](https://github.com/hynek/structlog/pull/735) ### Fixed - `structlog.processors.MaybeTimeStamper` now respects the *key* argument when determining whether to overwrite the timestamp field. [#747](https://github.com/hynek/structlog/pull/747) - `structlog.tracebacks.extract()` no longer raises a `RecursionError` when the cause chain of an exception contains itself. [#739](https://github.com/hynek/structlog/pull/739) - Default config now respects `FORCE_COLOR` on Windows. [#758](https://github.com/hynek/structlog/pull/758) --- [Full Changelog →](https://www.structlog.org/en/stable/changelog.html) ## Credits *structlog* is written and maintained by [Hynek Schlawack](https://hynek.me/). The idea of bound loggers is inspired by previous work by [Jean-Paul Calderone](https://github.com/exarkun) and [David Reid](https://github.com/dreid). The development is kindly supported by my employer [Variomedia AG](https://www.variomedia.de/), *structlog*’s [Tidelift subscribers](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek), and all my amazing [GitHub Sponsors](https://github.com/sponsors/hynek). The logs-loving beaver logo has been contributed by [Lynn Root](https://www.roguelynn.com).