slack-logger-python


Nameslack-logger-python JSON
Version 0.7.1 PyPI version JSON
download
home_page
SummarySlack logger utilizing python logging interface.
upload_time2023-10-21 00:43:54
maintainer
docs_urlNone
author
requires_python>=3.10
license
keywords slack python logging logger log python-logging handler formatter logging.handler logging.formatter monitoring alerting slack-api webhook slack-logger messaging health-check notification-service notification
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Slack Logger Python - A Slack Logger for Python Logging

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Linting: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
![coverage](https://raw.githubusercontent.com/GRBurst/slack-logger-python/assets/badges/coverage.svg)

A python logging implementation to send messages to Slack.
Design goals:
1. Use Python logging interface such that it can be integrated with standard logging tools.
2. Fully customizable messages with full control over message layout and design.
3. Simple authentication via webhook.
4. Powerful filtering to filter e.g. for environments or particular values only known at runtime.
5. Easy usage to cover most use cases when slack is used for basic alerting and automatic traceback visualization.

To achieve aforementioned goals, the library provides logging handler, formatter and filter implementations.

## Getting Started

1. Install with `pip install slack-logger-python`.
2. Now, you can use the `slack_logger` module in your python code.


### Basic Example with Plain Messages

This basic examples shows the usage and the implementation of design goal (1) and (3).

```python
from slack_logger import SlackFormatter, SlackHandler

logger = logging.getLogger(__name__)

formatter = SlackFormatter.plain() # plain message, no decorations
handler = SlackHandler.from_webhook(os.environ["SLACK_WEBHOOK"])
handler.setFormatter(formatter)
handler.setLevel(logging.WARN)

logger.addHandler(handler)

logger.info("I won't appear.")
logger.warning("I will show up.")
logger.error("Mee too.")
```

### Visually Appealing Messages

You can use the `SlackFormatter.minimal()` and `SlackFormatter.default()` formatter for more visually appealing log messages.
For now, those require a configuration to show e.g. the header.
Extra fields are shown in blocks at the bottom of the message and can be dynamically added at runtime.
Everything else stays the same:

```python
from slack_logger import FormatConfig, SlackFormatter, SlackHandler

logger = logging.getLogger(__name__)

format_config = FormatConfig(service="testrunner", environment="test", extra_fields={"foo": "bar"})
formatter = SlackFormatter.default(format_config)
handler = SlackHandler.from_webhook(os.environ["SLACK_WEBHOOK"])
handler.setFormatter(formatter)
handler.setLevel(logging.WARN)

logger.addHandler(handler)

logger.info("I won't appear.")
logger.warning("I will show up.")
logger.error("Mee too.")
```

Adding extra fields on single log message is achieved by just putting it in the extra fields of the logging interface:
```python
logger.warning("I will show up.", extra = {"extra_fields": {"foo": "baba"}})
```

## Customization

To do basic customizations, you can provide a configuration to the `SlackFormatter`:

```python
    service: Optional[str] = None
    environment: Optional[str] = None
    context: List[str] = []
    emojis: Dict[int, str] = DEFAULT_EMOJIS
    extra_fields: Dict[str, str] = {}
```

Let's look at an example error log from a division by zero error.
Given the following code snippet with a configuration and the default formatter:

```python
import os
from slack_logger import FormatConfig, SlackFormatter, SlackHandler
format_config = FormatConfig(
    service="testrunner", environment="test", extra_fields={"foo": "bar", "raven": "caw"}
)
slack_handler = SlackHandler.from_webhook(os.environ["SLACK_WEBHOOK"])
formatter = SlackFormatter.default(format_config)
slack_handler.setFormatter(formatter)
log.addHandler(slack_handler)
try:
    1/0
except Exception:
    log.exception("Something terrible happened!")
```

We will get the following error message in slack:
![error_log](https://raw.githubusercontent.com/GRBurst/slack-logger-python/assets/docs/images/error_log.png)

It contains a header, a context, a body and a section containing extra fields.
Let's identify those in the image above.

The header is composed of:
1. Log level emoji: ❌
2. Log level name: ERROR
3. Service name: testrunner

The context contains:
1. Environment: test
2. Service name: testrunner

The body includes:
1. The log error message: "Something terrible happened!"
2. The Traceback error

Extra fields:
1. Field "foo" with value "bar"
2. Field "raven" with value "caw"

### Design Message - Design Principal (3)

Messages are fully customizable using slacks block layout, see [Creating rich message layouts](https://api.slack.com/messaging/composing/layouts) and [Reference: Layout blocks](https://api.slack.com/reference/block-kit/blocks).
By implementing the `MessageDesign` interface, you can fully control in the message design, which requires you to implement a function `format_blocks(record: LogRecord) -> Sequence[Optional[Block]]` to transform a `LogRecord` into a sequence of slack message blocks.
Of course, you can add configurations and helper functions as well.

Let's create our own warning message.
This demonstrates the usage and the implementation of design goal (2).

```python
import attrs # for convenience, but not required
from slack_sdk.models.blocks import Block, DividerBlock, HeaderBlock, SectionBlock
from slack_logger import SlackFormatter

@define
class CustomDesign(MessageDesign):

    def format_blocks(self, record: LogRecord) -> Sequence[Optional[Block]]:
        level = record.levelname
        message = record.getMessage()

        blocks: Sequence[Block] = [
            HeaderBlock(text=PlainTextObject(text=f"{level} {message}")),
            DividerBlock(),
            SectionBlock(text=MarkdownTextObject(text=message)),
        ]
        return blocks

formatter = SlackFormatter(design=CustomDesign())
```


#### Provide your own set of emojis

To the default emoji set is a defined by the following dict:
```python
DEFAULT_EMOJIS = {
    logging.CRITICAL: ":fire:",     # 🔥
    logging.ERROR: ":x:",           # ❌
    logging.FATAL: ":x:",           # ❌
    logging.WARNING: ":warning:",   # ⚠️
    logging.WARN: ":warning:",      # ⚠️
    logging.INFO: ":bell:",         # 🔔
    logging.DEBUG: ":microscope:",  # 🔬 
    logging.NOTSET: ":mega:",       # 📣
}
```

You can import and overwrite it partially - or you can define a complete new set of emoji.
The following example demonstrates how you can add the emoji set to the `SlackFormatter`:

```python
from slack_logger import FormatConfig, SlackFormatter

my_emojis = {
    logging.CRITICAL: ":x:",    # ❌
    logging.ERROR: ":x:",       # ❌
    logging.FATAL: ":x:",       # ❌
    logging.WARNING: ":bell:",  # 🔔
    logging.WARN: ":bell:",     # 🔔
    logging.INFO: ":mega:",     # 📣
    logging.DEBUG: ":mega:",    # 📣
    logging.NOTSET: ":mega:",   # 📣
}

config = FormatConfig(service="testrunner", environment="test", emojis=my_emojis)
formatter = SlackFormatter.default(config)
```


### Filter Message

Filters implement the logging interface of `Filters`.
They are designed to work as a companion to a `LogFormatter`, as it can filter on the formatters config.
A message is logged if a filter is matched successfully.
Design goal (4) and (5) are partially demonstrated.

Here is a quick example:

```python
from slack_logger import FilterConfig, SlackFilter, SlackHandler
import os
import logging 

logger = logging.getLogger("ProdFilter")
# Allow only logs from `prod` environment
filter = SlackFilter(config=FilterConfig(environment="prod"), filterType=FilterType.AnyAllowList)
slack_handler.addFilter(filter)
logger.addHandler(slack_handler)


# When the ENV enviroment variable is set to prod, the message will be send.
# Otherwise, the message is filtered out and not send (e.g. if ENV is `dev`)
logger.warning(f"{log_msg} in some environment and allow listed prod", extra={"filter": {"environment": os.getenv("ENV", "dev")}})

# Will be filtered
logger.warning(f"{log_msg} in dev environment and allow listed prod", extra={"filter": {"environment": "dev"}})

# Will be send
logger.warning(f"{log_msg} in dev environment and allow listed prod", extra={"filter": {"environment": "prod"}})
```

Note that we used the `"filter"` property in the `extra` field option here to inject a config, as we don't use a `SlackFormatter`.
You can think of it as a reserved word.
This `on-the-fly` configurations allow to specify properties on messages to alter the filter behavior for a single log message.

There are 4 different types of filter lists:
- `FilterType.AnyAllowList`: Pass filter if any of the provided conditions are met.
- `FilterType.AllAllowList`: Pass filter only if all provided conditions are met.
- `FilterType.AnyDenyList`: Pass filter if no condition is met and deny if any condition is met.
- `FilterType.AllDenyList`: Pass filter if any condition is met and deny if all conditions are met.

#### Combining multiple filters.

It is important to note that as soon a message does not meet any filter condition, it is filtered out and won't appear in the logs, as it is simply the overlap of all filter conditions.
Therefore it is not possible to allow a denied message afterwards.
Furthermore, the order of filters do not matter.


The composition of configurations, filters and dynamic extra fields allow for a flexible way of specifying your message content and filter unwanted messages.

More examples can be found it the [tests folder](https://github.com/GRBurst/slack-logger-python/tree/main/tests).


            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "slack-logger-python",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": "",
    "keywords": "slack,python,logging,logger,log,python-logging,Handler,Formatter,logging.Handler,logging.Formatter,monitoring,alerting,slack-api,webhook,slack-logger,messaging,health-check,notification-service,notification",
    "author": "",
    "author_email": "GRBurst <GRBurst@protonmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/b3/3a/60f34a51a3d75743d856781bdd0d5bae8737f2c7a9c3fa56ce0792d7e084/slack-logger-python-0.7.1.tar.gz",
    "platform": null,
    "description": "# Slack Logger Python - A Slack Logger for Python Logging\n\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Linting: Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)\n![coverage](https://raw.githubusercontent.com/GRBurst/slack-logger-python/assets/badges/coverage.svg)\n\nA python logging implementation to send messages to Slack.\nDesign goals:\n1. Use Python logging interface such that it can be integrated with standard logging tools.\n2. Fully customizable messages with full control over message layout and design.\n3. Simple authentication via webhook.\n4. Powerful filtering to filter e.g. for environments or particular values only known at runtime.\n5. Easy usage to cover most use cases when slack is used for basic alerting and automatic traceback visualization.\n\nTo achieve aforementioned goals, the library provides logging handler, formatter and filter implementations.\n\n## Getting Started\n\n1. Install with `pip install slack-logger-python`.\n2. Now, you can use the `slack_logger` module in your python code.\n\n\n### Basic Example with Plain Messages\n\nThis basic examples shows the usage and the implementation of design goal (1) and (3).\n\n```python\nfrom slack_logger import SlackFormatter, SlackHandler\n\nlogger = logging.getLogger(__name__)\n\nformatter = SlackFormatter.plain() # plain message, no decorations\nhandler = SlackHandler.from_webhook(os.environ[\"SLACK_WEBHOOK\"])\nhandler.setFormatter(formatter)\nhandler.setLevel(logging.WARN)\n\nlogger.addHandler(handler)\n\nlogger.info(\"I won't appear.\")\nlogger.warning(\"I will show up.\")\nlogger.error(\"Mee too.\")\n```\n\n### Visually Appealing Messages\n\nYou can use the `SlackFormatter.minimal()` and `SlackFormatter.default()` formatter for more visually appealing log messages.\nFor now, those require a configuration to show e.g. the header.\nExtra fields are shown in blocks at the bottom of the message and can be dynamically added at runtime.\nEverything else stays the same:\n\n```python\nfrom slack_logger import FormatConfig, SlackFormatter, SlackHandler\n\nlogger = logging.getLogger(__name__)\n\nformat_config = FormatConfig(service=\"testrunner\", environment=\"test\", extra_fields={\"foo\": \"bar\"})\nformatter = SlackFormatter.default(format_config)\nhandler = SlackHandler.from_webhook(os.environ[\"SLACK_WEBHOOK\"])\nhandler.setFormatter(formatter)\nhandler.setLevel(logging.WARN)\n\nlogger.addHandler(handler)\n\nlogger.info(\"I won't appear.\")\nlogger.warning(\"I will show up.\")\nlogger.error(\"Mee too.\")\n```\n\nAdding extra fields on single log message is achieved by just putting it in the extra fields of the logging interface:\n```python\nlogger.warning(\"I will show up.\", extra = {\"extra_fields\": {\"foo\": \"baba\"}})\n```\n\n## Customization\n\nTo do basic customizations, you can provide a configuration to the `SlackFormatter`:\n\n```python\n    service: Optional[str] = None\n    environment: Optional[str] = None\n    context: List[str] = []\n    emojis: Dict[int, str] = DEFAULT_EMOJIS\n    extra_fields: Dict[str, str] = {}\n```\n\nLet's look at an example error log from a division by zero error.\nGiven the following code snippet with a configuration and the default formatter:\n\n```python\nimport os\nfrom slack_logger import FormatConfig, SlackFormatter, SlackHandler\nformat_config = FormatConfig(\n    service=\"testrunner\", environment=\"test\", extra_fields={\"foo\": \"bar\", \"raven\": \"caw\"}\n)\nslack_handler = SlackHandler.from_webhook(os.environ[\"SLACK_WEBHOOK\"])\nformatter = SlackFormatter.default(format_config)\nslack_handler.setFormatter(formatter)\nlog.addHandler(slack_handler)\ntry:\n    1/0\nexcept Exception:\n    log.exception(\"Something terrible happened!\")\n```\n\nWe will get the following error message in slack:\n![error_log](https://raw.githubusercontent.com/GRBurst/slack-logger-python/assets/docs/images/error_log.png)\n\nIt contains a header, a context, a body and a section containing extra fields.\nLet's identify those in the image above.\n\nThe header is composed of:\n1. Log level emoji: \u274c\n2. Log level name: ERROR\n3. Service name: testrunner\n\nThe context contains:\n1. Environment: test\n2. Service name: testrunner\n\nThe body includes:\n1. The log error message: \"Something terrible happened!\"\n2. The Traceback error\n\nExtra fields:\n1. Field \"foo\" with value \"bar\"\n2. Field \"raven\" with value \"caw\"\n\n### Design Message - Design Principal (3)\n\nMessages are fully customizable using slacks block layout, see [Creating rich message layouts](https://api.slack.com/messaging/composing/layouts) and [Reference: Layout blocks](https://api.slack.com/reference/block-kit/blocks).\nBy implementing the `MessageDesign` interface, you can fully control in the message design, which requires you to implement a function `format_blocks(record: LogRecord) -> Sequence[Optional[Block]]` to transform a `LogRecord` into a sequence of slack message blocks.\nOf course, you can add configurations and helper functions as well.\n\nLet's create our own warning message.\nThis demonstrates the usage and the implementation of design goal (2).\n\n```python\nimport attrs # for convenience, but not required\nfrom slack_sdk.models.blocks import Block, DividerBlock, HeaderBlock, SectionBlock\nfrom slack_logger import SlackFormatter\n\n@define\nclass CustomDesign(MessageDesign):\n\n    def format_blocks(self, record: LogRecord) -> Sequence[Optional[Block]]:\n        level = record.levelname\n        message = record.getMessage()\n\n        blocks: Sequence[Block] = [\n            HeaderBlock(text=PlainTextObject(text=f\"{level} {message}\")),\n            DividerBlock(),\n            SectionBlock(text=MarkdownTextObject(text=message)),\n        ]\n        return blocks\n\nformatter = SlackFormatter(design=CustomDesign())\n```\n\n\n#### Provide your own set of emojis\n\nTo the default emoji set is a defined by the following dict:\n```python\nDEFAULT_EMOJIS = {\n    logging.CRITICAL: \":fire:\",     # \ud83d\udd25\n    logging.ERROR: \":x:\",           # \u274c\n    logging.FATAL: \":x:\",           # \u274c\n    logging.WARNING: \":warning:\",   # \u26a0\ufe0f\n    logging.WARN: \":warning:\",      # \u26a0\ufe0f\n    logging.INFO: \":bell:\",         # \ud83d\udd14\n    logging.DEBUG: \":microscope:\",  # \ud83d\udd2c \n    logging.NOTSET: \":mega:\",       # \ud83d\udce3\n}\n```\n\nYou can import and overwrite it partially - or you can define a complete new set of emoji.\nThe following example demonstrates how you can add the emoji set to the `SlackFormatter`:\n\n```python\nfrom slack_logger import FormatConfig, SlackFormatter\n\nmy_emojis = {\n    logging.CRITICAL: \":x:\",    # \u274c\n    logging.ERROR: \":x:\",       # \u274c\n    logging.FATAL: \":x:\",       # \u274c\n    logging.WARNING: \":bell:\",  # \ud83d\udd14\n    logging.WARN: \":bell:\",     # \ud83d\udd14\n    logging.INFO: \":mega:\",     # \ud83d\udce3\n    logging.DEBUG: \":mega:\",    # \ud83d\udce3\n    logging.NOTSET: \":mega:\",   # \ud83d\udce3\n}\n\nconfig = FormatConfig(service=\"testrunner\", environment=\"test\", emojis=my_emojis)\nformatter = SlackFormatter.default(config)\n```\n\n\n### Filter Message\n\nFilters implement the logging interface of `Filters`.\nThey are designed to work as a companion to a `LogFormatter`, as it can filter on the formatters config.\nA message is logged if a filter is matched successfully.\nDesign goal (4) and (5) are partially demonstrated.\n\nHere is a quick example:\n\n```python\nfrom slack_logger import FilterConfig, SlackFilter, SlackHandler\nimport os\nimport logging \n\nlogger = logging.getLogger(\"ProdFilter\")\n# Allow only logs from `prod` environment\nfilter = SlackFilter(config=FilterConfig(environment=\"prod\"), filterType=FilterType.AnyAllowList)\nslack_handler.addFilter(filter)\nlogger.addHandler(slack_handler)\n\n\n# When the ENV enviroment variable is set to prod, the message will be send.\n# Otherwise, the message is filtered out and not send (e.g. if ENV is `dev`)\nlogger.warning(f\"{log_msg} in some environment and allow listed prod\", extra={\"filter\": {\"environment\": os.getenv(\"ENV\", \"dev\")}})\n\n# Will be filtered\nlogger.warning(f\"{log_msg} in dev environment and allow listed prod\", extra={\"filter\": {\"environment\": \"dev\"}})\n\n# Will be send\nlogger.warning(f\"{log_msg} in dev environment and allow listed prod\", extra={\"filter\": {\"environment\": \"prod\"}})\n```\n\nNote that we used the `\"filter\"` property in the `extra` field option here to inject a config, as we don't use a `SlackFormatter`.\nYou can think of it as a reserved word.\nThis `on-the-fly` configurations allow to specify properties on messages to alter the filter behavior for a single log message.\n\nThere are 4 different types of filter lists:\n- `FilterType.AnyAllowList`: Pass filter if any of the provided conditions are met.\n- `FilterType.AllAllowList`: Pass filter only if all provided conditions are met.\n- `FilterType.AnyDenyList`: Pass filter if no condition is met and deny if any condition is met.\n- `FilterType.AllDenyList`: Pass filter if any condition is met and deny if all conditions are met.\n\n#### Combining multiple filters.\n\nIt is important to note that as soon a message does not meet any filter condition, it is filtered out and won't appear in the logs, as it is simply the overlap of all filter conditions.\nTherefore it is not possible to allow a denied message afterwards.\nFurthermore, the order of filters do not matter.\n\n\nThe composition of configurations, filters and dynamic extra fields allow for a flexible way of specifying your message content and filter unwanted messages.\n\nMore examples can be found it the [tests folder](https://github.com/GRBurst/slack-logger-python/tree/main/tests).\n\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "Slack logger utilizing python logging interface.",
    "version": "0.7.1",
    "project_urls": {
        "Bug Tracker": "https://github.com/GRBurst/slack-python-logging/issues",
        "Homepage": "https://github.com/GRBurst/slack-python-logging"
    },
    "split_keywords": [
        "slack",
        "python",
        "logging",
        "logger",
        "log",
        "python-logging",
        "handler",
        "formatter",
        "logging.handler",
        "logging.formatter",
        "monitoring",
        "alerting",
        "slack-api",
        "webhook",
        "slack-logger",
        "messaging",
        "health-check",
        "notification-service",
        "notification"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bd516f8d42e64b017d2801cf612bdddc66c7daebfa8f593bfaa48fab6fa98b24",
                "md5": "ecec2a9a64100bc5a1afbdd87efac5b6",
                "sha256": "773f11dc9f4f0d88acd46e7c1ceed16af6d7cb026722655cbbf969411cda7d6d"
            },
            "downloads": -1,
            "filename": "slack_logger_python-0.7.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ecec2a9a64100bc5a1afbdd87efac5b6",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 9737,
            "upload_time": "2023-10-21T00:43:53",
            "upload_time_iso_8601": "2023-10-21T00:43:53.043459Z",
            "url": "https://files.pythonhosted.org/packages/bd/51/6f8d42e64b017d2801cf612bdddc66c7daebfa8f593bfaa48fab6fa98b24/slack_logger_python-0.7.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b33a60f34a51a3d75743d856781bdd0d5bae8737f2c7a9c3fa56ce0792d7e084",
                "md5": "f89af1dcce0bc520dd27861c2858a016",
                "sha256": "aa9be187982f612f1bc322f63af3f47e71154a3993586bb413e5ab2462a4afc6"
            },
            "downloads": -1,
            "filename": "slack-logger-python-0.7.1.tar.gz",
            "has_sig": false,
            "md5_digest": "f89af1dcce0bc520dd27861c2858a016",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 48295,
            "upload_time": "2023-10-21T00:43:54",
            "upload_time_iso_8601": "2023-10-21T00:43:54.656855Z",
            "url": "https://files.pythonhosted.org/packages/b3/3a/60f34a51a3d75743d856781bdd0d5bae8737f2c7a9c3fa56ce0792d7e084/slack-logger-python-0.7.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-10-21 00:43:54",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "GRBurst",
    "github_project": "slack-python-logging",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "slack-logger-python"
}
        
Elapsed time: 0.12735s