grumble


Namegrumble JSON
Version 1.1.0 PyPI version JSON
download
home_page
SummaryFor print debuggers drowning in print statements.
upload_time2023-05-11 01:43:41
maintainer
docs_urlNone
author
requires_python>=3.6
licenseMIT
keywords debugging console logs
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            Grumble ๐Ÿคจ
==========

Grumble is a Python library for print debuggers who are drowning in `print()`
statements..

It's for developers who need to capture lots of data between runs.

It's for developers banging their head against the wall trying to dive deep
into code that's not quite working right, or not working consistently.


Installation is easy
--------------------

```
$ pip3 install grumble
```

Why would you want to install Grumble? Well...


It's a Friday evening, and everything you wrote is busted
---------------------------------------------------------

You spent all week trying to get your code work. And it should work, but it's
not. You're furiously trying to debug it as you get increasingly exhausted,
your brain beginning to melt into a pile of goo.

The hours are ticking away with nothing to show for it. Your console is full
of print output, and it's beginning to blur together into some kind of blended
soup of green Matrix code nonsense.

Log messages mixed with tracebacks, mixed with variable dumps, all coming from
different classes, threads, processes. Screens full of it.

The details change between runs. You're writing it all down, trying to keep it
straight in your head. But it's begun to fall apart. And you're wondering
why you turned to a life of coding instead of a life of farming.

True story.


This is where Grumble comes in
------------------------------

Grumble gives one simple command to log debug output:

```python
def grumble(msg='', state=None, category=None, log_tag=None):
    ...
```

When you `grumble(...)`, it'll print a simple log message to standard output,
and log details to a log file.

By default, the log message will show:

1. An emoji (to help you visually separate that log statement from other noise).

2. A timestamp of the log message.

3. A log file with more details.

   This identifies "grumble", the process name, the PID, a thread name (if using
   threads), and an extra log tag name (if setting `log_tag`).

4. A searchable hash within that log file.

For example:

```python
>>> from grumble import grumble
>>> grumble()
๐Ÿ˜ถ  ๐Ÿ•ง 2022-10-16 12:46:36  ๐Ÿ’พ grumble-python3-86256.log [b6589fc6ab0dc82cf12099d1c2d40ab994e8410c]
```

Good at a glance, and you can customize that with a log message or category:

```python
>>> grumble('Look, a log message!', category='ui', log_tag='uilogs')
๐Ÿง [ui] Look, a log message!  ๐Ÿ•ง 2022-10-16 12:47:10  ๐Ÿ’พ grumble-python3-86256-uilogs.log [356a192b7913b04c54574d18c28d46e6395428ab]
```

And we can add some state. Anything at all.

```python
>>> grumble('Logging the generated object', state=some_object)
๐Ÿคจ [ui] Look, a log message!  ๐Ÿ•ง2022-10-16 12:48:43  ๐Ÿ’พ grumble-python3-86256.log [da4b9237bacccdf19c0760cab7aec4a8359010b0]
```

State will show up, nicely formatted, in the log file. Which we'll cover right
about... now.


Going deeper with logs
----------------------

That log message is handy, but it doesn't tell us much more than a normal
`print()` statement.

It does point to a log file, though. Let's look into that.

Let's write a little program to print the output of a directory, filtered by
a file pattern:


```python
import os
from fnmatch import fnmatch

from grumble import grumble


def filter_filenames(filenames, file_pattern):
    return [
        filename
        for filename in filenames
        if fnmatch(filename, file_pattern)
    ]


def list_directory(path, file_pattern):
    path = os.path.abspath(path)
    results = filter_filenames(os.listdir(path),
                               file_pattern)

    grumble("Let's look at a directory",
            state={
                'raw os.listdir output': os.listdir(path),
            })

    return results

filenames = list_directory('.', file_pattern='*.txt')

print('Files:')
print('\n'.join(filenames))
```

And let's run it!

```
$ python3 dirtest.py
๐Ÿ˜ถ Let's look at a directory  ๐Ÿ•’ 2022-10-16 13:01:21  ๐Ÿ’พ grumble-dirtest.py-7154.log [b6589fc6ab0dc82cf12099d1c2d40ab994e8410c]
Files:
world.txt
hello.txt
```

We have our Grumble log statement, and our resulting directory listing. Time to
look at what's in the log:

```
๐Ÿ˜ถ
๐Ÿ˜ถ  Grumble ID:  b6589fc6ab0dc82cf12099d1c2d40ab994e8410c
๐Ÿ˜ถ   Timestamp:  ๐Ÿ•’ 2022-10-16 13:01:21
๐Ÿ˜ถ     Message:  Let's look at a directory
๐Ÿ˜ถ

## State:
##   {'raw os.listdir output': ['world.txt',
##                              'hello.txt',
##                              'dirtest.py']}
##

>> Traceback:
>>   File "/tmp/grumble/dirtest.py", line 28, in <module>
>>     filenames = list_directory('.', file_pattern='*.txt')
>>   File "/tmp/grumble/dirtest.py", line 20, in list_directory
>>     grumble("Let's look at a directory",
>>

$$ Locals:
$$  {'file_pattern': '*.txt',
$$   'path': '/tmp/grumble',
$$   'results': ['world.txt', 'hello.txt']}
$$
```

Look at all that debugging information! We have:

1. An easy visual and searchable reference from the log output.
2. Any and all state we passed to that call to `grumble()`.
3. A traceback of where we are.
4. All local variables.

As you `grumble()` your way through your debugging session, your log file
(or files, if using threads or different logging tag names) will grow with
helpful information that you can read through or even diff.

Logs are outputted in the current directory by default. You can specify a
different directory by setting the `GRUMBLE_LOG_DIR=...` environment variable,

You can also output full logs to the console by setting `GRUMBLE_OUT=1`.


Works great as an exception handler
-----------------------------------

This log file can also help with exceptions. Say we had:

```python
try:
    raise Exception('bad things happened')
except Exception as e:
    grumble('Uh oh, we hit an exception: %s' % e)

return results
```

Our log file would also contain:

```
!! Exception:
!!   Type: <class 'type'>
!!   Value: Exception('bad things happened')
!!   String: bad things happened
!!   __dict__:
!!     {}
!!
```

Pretty handy. Especially for exceptions that contain additional state.


Works with threads and multiple processes!
------------------------------------------

Logs are differentiated by thread and process IDs. Lock files ensure that
logs don't get jumbled together. Because that would be annoying to deal with.

If you want to collapse everything into a single log file, set
`GRUMBLE_MERGE_THREADS=1`.


Emojis and hashes are deterministic
-----------------------------------

Grumble will cycle through emojis in the following order, every time:

๐Ÿ˜ถ ๐Ÿง ๐Ÿคจ ๐Ÿ˜ฌ ๐Ÿ™„ ๐Ÿ˜‘ ๐Ÿ˜• โ˜น๏ธ ๐Ÿ˜ฏ ๐Ÿ˜ง ๐Ÿ˜ต ๐Ÿ˜  ๐Ÿ˜ฃ ๐Ÿ˜– ๐Ÿ˜ซ ๐Ÿ˜ค ๐Ÿ˜ก ๐Ÿคฌ ๐Ÿ˜’ ๐Ÿ˜ช

Hashes used to identify the matching part in a log file are also consistent
between runs. They're a SHA1 of a 0-based index into the log.

This makes the log output more consistent between runs.

If you run the same process multiple times with different results or behavior,
you'll want to narrow down what's going on. By keeping the order of emojis and
hashes the same, and tagging each log file with process/thread IDs, you'll be
able to more easily diff two runs and see if anything has changed.


What else can it do?
--------------------

No, that's about it. Nothing hidden in the module. Nothing at all. Nope.

Install Grumble today!

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "grumble",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.6",
    "maintainer_email": "",
    "keywords": "debugging,console,logs",
    "author": "",
    "author_email": "Christian Hammond <christian@beanbaginc.com>",
    "download_url": "https://files.pythonhosted.org/packages/13/ef/4c38fcdccf4eedebbb76ec57ca0f63e13bd66f6a7141a65f53618e7affa2/grumble-1.1.0.tar.gz",
    "platform": null,
    "description": "Grumble \ud83e\udd28\n==========\n\nGrumble is a Python library for print debuggers who are drowning in `print()`\nstatements..\n\nIt's for developers who need to capture lots of data between runs.\n\nIt's for developers banging their head against the wall trying to dive deep\ninto code that's not quite working right, or not working consistently.\n\n\nInstallation is easy\n--------------------\n\n```\n$ pip3 install grumble\n```\n\nWhy would you want to install Grumble? Well...\n\n\nIt's a Friday evening, and everything you wrote is busted\n---------------------------------------------------------\n\nYou spent all week trying to get your code work. And it should work, but it's\nnot. You're furiously trying to debug it as you get increasingly exhausted,\nyour brain beginning to melt into a pile of goo.\n\nThe hours are ticking away with nothing to show for it. Your console is full\nof print output, and it's beginning to blur together into some kind of blended\nsoup of green Matrix code nonsense.\n\nLog messages mixed with tracebacks, mixed with variable dumps, all coming from\ndifferent classes, threads, processes. Screens full of it.\n\nThe details change between runs. You're writing it all down, trying to keep it\nstraight in your head. But it's begun to fall apart. And you're wondering\nwhy you turned to a life of coding instead of a life of farming.\n\nTrue story.\n\n\nThis is where Grumble comes in\n------------------------------\n\nGrumble gives one simple command to log debug output:\n\n```python\ndef grumble(msg='', state=None, category=None, log_tag=None):\n    ...\n```\n\nWhen you `grumble(...)`, it'll print a simple log message to standard output,\nand log details to a log file.\n\nBy default, the log message will show:\n\n1. An emoji (to help you visually separate that log statement from other noise).\n\n2. A timestamp of the log message.\n\n3. A log file with more details.\n\n   This identifies \"grumble\", the process name, the PID, a thread name (if using\n   threads), and an extra log tag name (if setting `log_tag`).\n\n4. A searchable hash within that log file.\n\nFor example:\n\n```python\n>>> from grumble import grumble\n>>> grumble()\n\ud83d\ude36  \ud83d\udd67 2022-10-16 12:46:36  \ud83d\udcbe grumble-python3-86256.log [b6589fc6ab0dc82cf12099d1c2d40ab994e8410c]\n```\n\nGood at a glance, and you can customize that with a log message or category:\n\n```python\n>>> grumble('Look, a log message!', category='ui', log_tag='uilogs')\n\ud83e\uddd0 [ui] Look, a log message!  \ud83d\udd67 2022-10-16 12:47:10  \ud83d\udcbe grumble-python3-86256-uilogs.log [356a192b7913b04c54574d18c28d46e6395428ab]\n```\n\nAnd we can add some state. Anything at all.\n\n```python\n>>> grumble('Logging the generated object', state=some_object)\n\ud83e\udd28 [ui] Look, a log message!  \ud83d\udd672022-10-16 12:48:43  \ud83d\udcbe grumble-python3-86256.log [da4b9237bacccdf19c0760cab7aec4a8359010b0]\n```\n\nState will show up, nicely formatted, in the log file. Which we'll cover right\nabout... now.\n\n\nGoing deeper with logs\n----------------------\n\nThat log message is handy, but it doesn't tell us much more than a normal\n`print()` statement.\n\nIt does point to a log file, though. Let's look into that.\n\nLet's write a little program to print the output of a directory, filtered by\na file pattern:\n\n\n```python\nimport os\nfrom fnmatch import fnmatch\n\nfrom grumble import grumble\n\n\ndef filter_filenames(filenames, file_pattern):\n    return [\n        filename\n        for filename in filenames\n        if fnmatch(filename, file_pattern)\n    ]\n\n\ndef list_directory(path, file_pattern):\n    path = os.path.abspath(path)\n    results = filter_filenames(os.listdir(path),\n                               file_pattern)\n\n    grumble(\"Let's look at a directory\",\n            state={\n                'raw os.listdir output': os.listdir(path),\n            })\n\n    return results\n\nfilenames = list_directory('.', file_pattern='*.txt')\n\nprint('Files:')\nprint('\\n'.join(filenames))\n```\n\nAnd let's run it!\n\n```\n$ python3 dirtest.py\n\ud83d\ude36 Let's look at a directory  \ud83d\udd52 2022-10-16 13:01:21  \ud83d\udcbe grumble-dirtest.py-7154.log [b6589fc6ab0dc82cf12099d1c2d40ab994e8410c]\nFiles:\nworld.txt\nhello.txt\n```\n\nWe have our Grumble log statement, and our resulting directory listing. Time to\nlook at what's in the log:\n\n```\n\ud83d\ude36\n\ud83d\ude36  Grumble ID:  b6589fc6ab0dc82cf12099d1c2d40ab994e8410c\n\ud83d\ude36   Timestamp:  \ud83d\udd52 2022-10-16 13:01:21\n\ud83d\ude36     Message:  Let's look at a directory\n\ud83d\ude36\n\n## State:\n##   {'raw os.listdir output': ['world.txt',\n##                              'hello.txt',\n##                              'dirtest.py']}\n##\n\n>> Traceback:\n>>   File \"/tmp/grumble/dirtest.py\", line 28, in <module>\n>>     filenames = list_directory('.', file_pattern='*.txt')\n>>   File \"/tmp/grumble/dirtest.py\", line 20, in list_directory\n>>     grumble(\"Let's look at a directory\",\n>>\n\n$$ Locals:\n$$  {'file_pattern': '*.txt',\n$$   'path': '/tmp/grumble',\n$$   'results': ['world.txt', 'hello.txt']}\n$$\n```\n\nLook at all that debugging information! We have:\n\n1. An easy visual and searchable reference from the log output.\n2. Any and all state we passed to that call to `grumble()`.\n3. A traceback of where we are.\n4. All local variables.\n\nAs you `grumble()` your way through your debugging session, your log file\n(or files, if using threads or different logging tag names) will grow with\nhelpful information that you can read through or even diff.\n\nLogs are outputted in the current directory by default. You can specify a\ndifferent directory by setting the `GRUMBLE_LOG_DIR=...` environment variable,\n\nYou can also output full logs to the console by setting `GRUMBLE_OUT=1`.\n\n\nWorks great as an exception handler\n-----------------------------------\n\nThis log file can also help with exceptions. Say we had:\n\n```python\ntry:\n    raise Exception('bad things happened')\nexcept Exception as e:\n    grumble('Uh oh, we hit an exception: %s' % e)\n\nreturn results\n```\n\nOur log file would also contain:\n\n```\n!! Exception:\n!!   Type: <class 'type'>\n!!   Value: Exception('bad things happened')\n!!   String: bad things happened\n!!   __dict__:\n!!     {}\n!!\n```\n\nPretty handy. Especially for exceptions that contain additional state.\n\n\nWorks with threads and multiple processes!\n------------------------------------------\n\nLogs are differentiated by thread and process IDs. Lock files ensure that\nlogs don't get jumbled together. Because that would be annoying to deal with.\n\nIf you want to collapse everything into a single log file, set\n`GRUMBLE_MERGE_THREADS=1`.\n\n\nEmojis and hashes are deterministic\n-----------------------------------\n\nGrumble will cycle through emojis in the following order, every time:\n\n\ud83d\ude36 \ud83e\uddd0 \ud83e\udd28 \ud83d\ude2c \ud83d\ude44 \ud83d\ude11 \ud83d\ude15 \u2639\ufe0f \ud83d\ude2f \ud83d\ude27 \ud83d\ude35 \ud83d\ude20 \ud83d\ude23 \ud83d\ude16 \ud83d\ude2b \ud83d\ude24 \ud83d\ude21 \ud83e\udd2c \ud83d\ude12 \ud83d\ude2a\n\nHashes used to identify the matching part in a log file are also consistent\nbetween runs. They're a SHA1 of a 0-based index into the log.\n\nThis makes the log output more consistent between runs.\n\nIf you run the same process multiple times with different results or behavior,\nyou'll want to narrow down what's going on. By keeping the order of emojis and\nhashes the same, and tagging each log file with process/thread IDs, you'll be\nable to more easily diff two runs and see if anything has changed.\n\n\nWhat else can it do?\n--------------------\n\nNo, that's about it. Nothing hidden in the module. Nothing at all. Nope.\n\nInstall Grumble today!\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "For print debuggers drowning in print statements.",
    "version": "1.1.0",
    "project_urls": {
        "documentation": "https://github.com/beanbaginc/grumble",
        "homepage": "https://github.com/beanbaginc/grumble",
        "repository": "https://github.com/beanbaginc/grumble"
    },
    "split_keywords": [
        "debugging",
        "console",
        "logs"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b25ca626b114a905e758f0595602f59194fd8123d390edf2fcf3d211f5cd003d",
                "md5": "31d4efc1a7fbcbbadb051c265f7bcf6b",
                "sha256": "08ba89512a2adcaee4e6d0b1b82420739a7bc76183339f84817f7d2eaec53630"
            },
            "downloads": -1,
            "filename": "grumble-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "31d4efc1a7fbcbbadb051c265f7bcf6b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.6",
            "size": 9355,
            "upload_time": "2023-05-11T01:43:39",
            "upload_time_iso_8601": "2023-05-11T01:43:39.240819Z",
            "url": "https://files.pythonhosted.org/packages/b2/5c/a626b114a905e758f0595602f59194fd8123d390edf2fcf3d211f5cd003d/grumble-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "13ef4c38fcdccf4eedebbb76ec57ca0f63e13bd66f6a7141a65f53618e7affa2",
                "md5": "c0ce8f9b3113ad1fe407b95a8feda50d",
                "sha256": "4a07a180e9b013533749abc4768df723dae3811bd4d136dcbb51db1ac7a836d1"
            },
            "downloads": -1,
            "filename": "grumble-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "c0ce8f9b3113ad1fe407b95a8feda50d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.6",
            "size": 9322,
            "upload_time": "2023-05-11T01:43:41",
            "upload_time_iso_8601": "2023-05-11T01:43:41.283886Z",
            "url": "https://files.pythonhosted.org/packages/13/ef/4c38fcdccf4eedebbb76ec57ca0f63e13bd66f6a7141a65f53618e7affa2/grumble-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-05-11 01:43:41",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "beanbaginc",
    "github_project": "grumble",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "grumble"
}
        
Elapsed time: 0.06827s