Logging πο
Importing and creating a logging instance π§ͺο
[ ]:
from __future__ import annotations
import structlog
from peak.tools.logging import LogHandler, LogLevel, PeakLogger, get_logger
Example 1: Get a default loggerο
[ ]:
logger: PeakLogger = get_logger(level=LogLevel.DEBUG)
logger.info("structured logging is easy", config=structlog.get_config())
# gives you: {"config":{"cache_logger_on_first_use":true,"context_class":"<class 'dict'>","logger_factory":"<structlog.stdlib.LoggerFactory object at 0x1035fbbe0>","processors":["<function merge_contextvars at 0x103001ab0>","<function pii_masking_processor at 0x1078db9a0>","<function peak_contexts_processor at 0x107823250>","<function filter_by_level at 0x103414a60>","<function add_logger_name at 0x1034155a0>","<function add_log_level at 0x103002560>","<structlog.stdlib.PositionalArgumentsFormatter object at 0x1035f91e0>","<structlog.processors.TimeStamper object at 0x1078f25c0>","<structlog.processors.StackInfoRenderer object at 0x1035f9150>","<structlog.processors.ExceptionRenderer object at 0x102322da0>","<structlog.processors.UnicodeDecoder object at 0x1035f9a20>","<structlog.processors.EventRenamer object at 0x1035fb400>","<structlog.processors.JSONRenderer object at 0x1035f9270>"],"wrapper_class":"<class 'structlog._native.BoundLoggerFilteringAtDebug'>"},"level":"info","logger":"root","message":"structured logging is easy","source":"peak-sdk","timestamp":"2024-03-15T18:32:24.758860Z"}
logger.debug("This is an info message")
# gives you: {"level":"debug","logger":"root","message":"This is an info message","source":"peak-sdk","timestamp":"2024-03-15T18:32:24.762185Z"}
Example 2: Get a customized loggerο
[ ]:
logger: PeakLogger = get_logger(
name=__name__,
level=LogLevel.ERROR,
handlers=[LogHandler.CONSOLE],
)
logger.error("This is an error message")
# gives you: {"level":"error","logger":"__main__","message":"This is an error message","source":"peak-sdk","timestamp":"2024-03-15T18:33:28.749419Z"}
logger.info("This is an info message and hence will not be printed")
# gives you: nothing
Masking sensitive data π§ͺο
Example 1: Masking is enabled by defaultο
[ ]:
logger: PeakLogger = get_logger(__name__)
logger.error("This is my email user@someorg.com")
# gives you: {"level":"error","logger":"__main__","message":"This is my email use****************com","source":"peak-sdk","timestamp":"2024-03-15T18:33:01.000308Z"}
Example 2: Disabling maskingο
[ ]:
logger: PeakLogger = get_logger(name=__name__, disable_masking=True)
logger.error("This is my email user@someorg.com")
# gives you: {"level":"error","logger":"__main__","message":"This is my email user@someorg.com","source":"peak-sdk","timestamp":"2024-04-10T06:25:47.703135Z"}
Binding and unbinding contexts π§ͺο
Example 1: Bind additional context to loggerο
[ ]:
logger: PeakLogger = get_logger(
name=__name__,
)
# bind the context
logger.bind({"key1": "value1", "key2": "value2"})
logger.info("Info msg with additional context")
# gives you: {"key1":"value1","key2":"value2","level":"info","logger":"__main__","message":"Info msg with additional context","source":"peak-sdk","timestamp":"2024-04-10T06:26:08.271239Z"}
Example 2: Unbind key(s) from the contextο
[ ]:
logger: PeakLogger = get_logger(
name=__name__,
)
# bind the context
logger.bind({"key1": "value1", "key2": "value2"})
logger.info("Info msg with additional context")
# gives you: {"key1":"value1","key2":"value2","level":"info","logger":"__main__","message":"Info msg with additional context","source":"peak-sdk","timestamp":"2024-04-10T06:26:24.113639Z"}
# unbind key1 from the context
logger.unbind(["key1", "key3"])
logger.info("Info msg with partial context")
# gives you: {"key2":"value2","level":"info","logger":"__main__","message":"Info msg with partial context","source":"peak-sdk","timestamp":"2024-04-10T06:26:24.115962Z"}
Changing log level π§ͺο
Example 1: Change or Set the log levelο
[ ]:
logger: PeakLogger = get_logger(
name=__name__,
level=LogLevel.INFO,
)
# log level is INFO
logger.info("This is the first info msg")
# gives you: {"level":"info","logger":"__main__","message":"This is the first info msg","source":"peak-sdk","timestamp":"2024-03-15T18:34:33.646110Z"}
logger.error("This is the first error msg")
# gives you: {"level":"error","logger":"__main__","message":"This is the first error msg","source":"peak-sdk","timestamp":"2024-03-15T18:34:33.648278Z"}
# change the log level to ERROR
logger.set_log_level(LogLevel.ERROR)
logger.info("This is the second info msg hence will not be printed")
# gives you : nothing
logger.error("This is the second error msg")
# gives you : {"level":"error","logger":"__main__","message":"This is the second error msg","source":"peak-sdk","timestamp":"2024-03-15T18:34:33.651846Z"}
Additional examples π§ͺο
Example 1: Using Formatting Placeholdersο
[ ]:
logger: PeakLogger = get_logger()
name = "John"
age = 30
logger.info("User: %s, Age: %d", name, age)
# gives you: {"level":"info","logger":"root","message":"User: John, Age: 30","source":"peak-sdk","timestamp":"2024-04-10T06:27:03.176011Z"}
Example 2: Logging with Structured Dataο
[ ]:
from typing import Any
logger: PeakLogger = get_logger()
data: Any = {"user_id": 123, "action": "Login"}
logger.info("User performed an action", data=data)
# gives you: {"data":{"action":"Login","user_id":123},"level":"info","logger":"root","message":"User performed an action","source":"peak-sdk","timestamp":"2024-04-10T06:27:16.466597Z"}
Example 3: Logging Mixed Data Typesο
[ ]:
logger: PeakLogger = get_logger()
user_id = 123
action = "Login"
result: Any = {"success": True, "message": "Logged in successfully"}
logger.info("Logging mixed data types", user_id=user_id, action=action, result=result)
# gives you: {"action":"Login","level":"info","logger":"root","message":"Logging mixed data types","result":{"message":"Logged in successfully","success":true},"source":"peak-sdk","timestamp":"2024-04-10T06:27:24.513378Z","user_id":123}
Example 4: Logging Exceptionsο
[ ]:
logger: PeakLogger = get_logger()
try:
result: float = 10 / 0
except Exception:
logger.exception("An exception occurred")
# gives you:
# {"exception":"Traceback (most recent call last):\n File \"/var/folders/61/qg8m95nx2bl4zqyccrc27bvw0000gp/T/ipykernel_84610/948709948.py\", line 4, in <module>\n result: float = 10 / 0\nZeroDivisionError: division by zero","level":"error","logger":"root","message":"An exception occurred","source":"peak-sdk","timestamp":"2024-04-10T06:27:36.005146Z"}
# Traceback (most recent call last):
# File "/var/folders/**/ipykernel_84610/**.py", line 4, in <module>
# result: float = 10 / 0
# ZeroDivisionError: division by zero
Example 5: Logging with custom / user-providable processorsο
[ ]:
from typing import List
def simple_no_tty_custom_processors_factory() -> List[structlog.types.Processor | Any]:
"""Simple custom processors factory function that returns a list of processors.
Knowingly, this function does not include the EventRenamer processor to demonstrate how it will be patched in if missing.
Returns:
List[structlog.types.Processor | Any]: List of processors
"""
return [
structlog.contextvars.merge_contextvars,
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.JSONRenderer(),
]
custom_logger: PeakLogger = get_logger(
custom_processors_factory=simple_no_tty_custom_processors_factory,
)
logger.info(
"This custom logger was patched to include EventRenamer processors",
processors_list=structlog.get_config()["processors"],
)
# gives you: {"level":"info","logger":"__main__","message":"This custom logger was patched to include EventRenamer processors","processors_list":["<function merge_contextvars at 0x103001ab0>","<function filter_by_level at 0x103414a60>","<function add_logger_name at 0x1034155a0>","<function add_log_level at 0x103002560>","<structlog.stdlib.PositionalArgumentsFormatter object at 0x1035fbeb0>","<structlog.processors.TimeStamper object at 0x107913940>","<structlog.processors.EventRenamer object at 0x1035fb490>","<structlog.processors.JSONRenderer object at 0x1035f9210>"],"source":"peak-sdk","timestamp":"2024-03-15T18:35:36.971465Z"}
logger.info("This is a custom logger")
# gives you: {"level":"info","logger":"__main__","message":"This is a custom logger","source":"peak-sdk","timestamp":"2024-03-15T18:35:36.973325Z"}
Clone an existing loggerο
[ ]:
logger: PeakLogger = get_logger("my_clone-able_logger")
logger.set_log_level(LogLevel.DEBUG)
logger.info("This is a clone-able logger")
# gives you: {"level":"info","logger":"my_clone-able_logger","message":"This is a clone-able logger","source":"peak-sdk","timestamp":"2024-03-18T11:49:35.696540Z"}
logger.bind({"key1": "value1", "key2": "value2"})
cloned_logger: PeakLogger = logger.clone_with_context({"zoo": "keeper"})
logger.debug("This is the original logger")
# gives you: {"level":"debug","logger":"my_clone-able_logger","message":"This is the original logger","source":"peak-sdk","timestamp":"2024-03-18T11:49:35.699316Z"}
cloned_logger.debug(
"This is a cloned logger that has all the context and log level as the original logger along with the additional context we added",
)
# gives you: {"level":"debug","logger":"my_clone-able_logger","message":"This is a cloned logger that has all the context and log level as the original logger along with the additional context we added","source":"peak-sdk","timestamp":"2024-03-18T11:54:22.816122Z","zoo":"keeper"}