Coder Social home page Coder Social logo

microsoft / picologging Goto Github PK

View Code? Open in Web Editor NEW
614.0 8.0 22.0 739 KB

An optimized logging library for Python

Home Page: https://microsoft.github.io/picologging

License: MIT License

CMake 0.59% Python 62.71% C++ 36.00% Dockerfile 0.41% Makefile 0.29%
logging python hacktoberfest

picologging's Introduction

picologging

PyPI - Python Version PyPI Anaconda-Server Badge codecov

Warning This project is in beta. There are some incomplete features (see Limitations).

Picologging is a high-performance logging library for Python. picologging is 4-17x faster than the logging module in the standard library.

Picologging is designed to be used as a drop-in replacement for applications which already use logging, and supports the same API as the logging module.

Check out the Documentation for more.

Installation

Picologging can be installed from PyPi using pip:

pip install picologging

Or from conda forge using conda:

conda install -c conda-forge picologging

Usage

Import picologging as logging to use picologging instead of the standard library logging module.

This patches all the loggers registered to use picologging loggers and formatters.

import picologging as logging
logging.basicConfig()

logger = logging.getLogger()

logger.info("A log message!")

logger.warning("A log message with %s", "arguments")

Benchmarks

Run richbench benchmarks/ --markdown with the richbench CLI to see the benchmarks, here is a sample on macOS 11:

Benchmark Min Max Mean Min (+) Max (+) Mean (+)
Logger(level=DEBUG).debug() 0.569 0.600 0.578 0.031 (18.3x) 0.035 (17.0x) 0.033 (17.7x)
Logger(level=DEBUG).debug() with args 0.591 0.607 0.601 0.047 (12.5x) 0.050 (12.2x) 0.048 (12.4x)
Logger(level=INFO).debug() 0.013 0.014 0.013 0.003 (5.0x) 0.003 (4.4x) 0.003 (4.8x)
Logger(level=INFO).debug() with args 0.013 0.014 0.013 0.003 (4.6x) 0.003 (4.2x) 0.003 (4.4x)

Limitations

See Limitations

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Local development

This project comes bundled with a dev container which sets up an appropriate environment. If you install the Dev Containers extension for VS Code, then opening this project in VS Code should prompt it to open it in the dev container.

Once opened in the dev container, run:

pip install -e ".[dev]"
pre-commit install
python setup.py build_ext --inplace --build-type Debug

Run the build command whenever you make changes to the files.

It's also helpful to create a .vscode/launch.json file like this one:

{
    "version": "0.2.0",
    "configurations": [
    {
        "name": "(gdb) Launch pytest",
        "type": "cppdbg",
        "request": "launch",
        "program": "/usr/local/bin/python",
        "args": ["-m", "pytest", "tests"],
        "stopAtEntry": false,
        "cwd": "${workspaceFolder}",
        "environment": [],
        "externalConsole": false,
        "MIMode": "gdb",
        "setupCommands": [
            {
                "description": "Enable pretty-printing for gdb",
                "text": "-enable-pretty-printing",
                "ignoreFailures": true
            },
            {
                "description":  "Set Disassembly Flavor to Intel",
                "text": "-gdb-set disassembly-flavor intel",
                "ignoreFailures": true
            },
        ]
    }
}

Now you can press the "Run and debug" button to run pytest from the gdb debugger and use breakpoint debugging in the C code.

If you would like to be able to dive into the CPython code while debugging, then:

  1. Do a git checkout of the tagged branch for the devcontainer's Python version into the devcontainer's /workspaces/ directory. You may need to sudo.

  2. Follow the instructions in the CPython README to compile the code.

  3. Add the following key to the the configuration in launch.json:

    "sourceFileMap": { "/usr/src/python": "/workspaces/cpython" },
  4. Add the following command to the setupCommands in launch.json:

    {
        "description": "Find CPython source code",
        "text": "-gdb-set auto-load safe-path /workspaces/cpython"
    },

Trademarks

Some components of this Python package are from CPython 3.11 logging library for compatibility reasons.

CPython 3.11 is licensed under the PSF license. The logging module is Copyright (C) 2001-2019 Vinay Sajip. All Rights Reserved.

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

picologging's People

Contributors

alexprengere avatar aminalaee avatar crazy4pi314 avatar dependabot[bot] avatar gabrielcappelli avatar goldziher avatar kjaymiller avatar microsoftopensource avatar pamelafox avatar sadikkuzu avatar tonybaloney avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

picologging's Issues

AttributeError: module 'picologging' has no attribute 'PlaceHolder'

I'm using picologging on a project with uvicorn and I'm running into the following exception. Should this class exist in the picologging library? I can't find the actual implementation for this.

Here is the line referenced in the error:

if not isinstance(logger, picologging.PlaceHolder):

and here is a chunk of the traceback:

Launching API Server with Uvicorn
[09/08/22 12:21:12] ERROR    Traceback (most recent call last):                                                                                                                                                                                                                                                            on.py:121
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/starlette/routing.py", line 645, in lifespan                                                                                                                                                
                                 async with self.lifespan_context(app):                                                                                                                                                                                                                                                             
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/starlette/routing.py", line 540, in __aenter__                                                                                                                                              
                                 await self._router.startup()                                                                                                                                                                                                                                                                       
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/starlite/asgi.py", line 236, in startup                                                                                                                                                     
                                 await self._call_lifespan_handler(handler)                                                                                                                                                                                                                                                         
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/starlite/asgi.py", line 220, in _call_lifespan_handler                                                                                                                                      
                                 value = handler()  # type:ignore                                                                                                                                                                                                                                                                   
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/starlite/config/logging.py", line 60, in configure                                                                                                                                          
                                 picologging_config.dictConfig(self.dict(exclude_none=True))                                                                                                                                                                                                                                        
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/picologging/config.py", line 332, in dictConfig                                                                                                                                             
                                 dictConfigClass(config).configure()                                                                                                                                                                                                                                                                
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/picologging/config.py", line 156, in configure                                                                                                                                              
                                 _handle_existing_loggers(existing, child_loggers, disable_existing)                                                                                                                                                                                                                                
                               File "/Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packages/picologging/config.py", line 46, in _handle_existing_loggers                                                                                                                                
                                 if not isinstance(logger, picologging.PlaceHolder):                                                                                                                                                                                                                                                
                             AttributeError: module 'picologging' has no attribute 'PlaceHolder'                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                    
[09/08/22 12:21:12] ERROR    Application startup failed. Exiting.  

Here is more details:

│ /Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packag │
│ es/picologging/config.py:156 in configure                                                        │
│                                                                                                  │
│   153 │   │   # and by disabling them, you stop them doing any logging.                          │
│   154 │   │   # However, don't disable children of named loggers, as that's                      │
│   155 │   │   # probably not what was intended by the user.                                      │
│ ❱ 156 │   │   _handle_existing_loggers(existing, child_loggers, disable_existing)                │
│   157 │   │                                                                                      │
│   158 │   │   # And finally, do the root logger                                                  │
│   159 │   │   root = config.get("root", None)                                                    │
│                                                                                                  │
│ ╭─────────────────────────────────────────── locals ───────────────────────────────────────────╮ │
│ │    child_loggers = ['starlite_bedrock.starlite.exceptions']                                  │ │
│ │           config = {                                                                         │ │
│ │                    │   'version': 1,                                                         │ │
│ │                    │   'filters': {                                                          │ │
│ │                    │   │   'health_filter':                                                  │ │
│ │                    <starlite_bedrock.starlite.logging.AccessLogFilter object at 0x10cbe83d0> │ │
│ │                    │   },                                                                    │ │
│ │                    │   'propagate': True,                                                    │ │
│ │                    │   'formatters': {'standard': <Formatter: fmt='%(message)s'>},           │ │
│ │                    │   'handlers': {                                                         │ │
│ │                    │   │   'console':                                                        │ │
│ │                    <starlite_bedrock.starlite.logging.RichPicologgingHandler object at       │ │
│ │                    0x10bec03c0>,                                                             │ │
│ │                    │   │   'queue_listener':                                                 │ │
│ │                    <starlite.logging.picologging.QueueListenerHandler object at 0x10cbb4140> │ │
│ │                    │   },                                                                    │ │
│ │                    │   'loggers': {                                                          │ │
│ │                    │   │   'starlite_bedrock': {'propagate': True},                          │ │
│ │                    │   │   'uvicorn.access': {                                               │ │
│ │                    │   │   │   'propagate': True,                                            │ │
│ │                    │   │   │   'filters': ['health_filter']                                  │ │
│ │                    │   │   },                                                                │ │
│ │                    │   │   'uvicorn.error': {'propagate': True},                             │ │
│ │                    │   │   'gunicorn.access': {                                              │ │
│ │                    │   │   │   'propagate': True,                                            │ │
│ │                    │   │   │   'filters': ['health_filter']                                  │ │
│ │                    │   │   },                                                                │ │
│ │                    │   │   'gunicorn.error': {'propagate': True},                            │ │
│ │                    │   │   'sqlalchemy': {'propagate': True},                                │ │
│ │                    │   │   'starlite': {'level': 'WARNING', 'propagate': True},              │ │
│ │                    │   │   'pydantic_openapi_schema': {                                      │ │
│ │                    │   │   │   'propagate': True,                                            │ │
│ │                    │   │   │   'level': 'WARNING',                                           │ │
│ │                    │   │   │   'handlers': ['queue_listener']                                │ │
│ │                    │   │   }                                                                 │ │
│ │                    │   },                                                                    │ │
│ │                    │   'root': {'handlers': ['queue_listener'], 'level': 'INFO'}             │ │
│ │                    }                                                                         │ │
│ │         deferred = []                                                                        │ │
│ │ disable_existing = False                                                                     │ │
│ │       EMPTY_DICT = {}                                                                        │ │
│ │         existing = [                                                                         │ │
│ │                    │   'opdba',                                                              │ │
│ │                    │   'opdba.domain.collection.routes',                                     │ │
│ │                    │   'starlite_bedrock.starlite.exceptions'                                │ │
│ │                    ]                                                                         │ │
│ │          filters = {                                                                         │ │
│ │                    │   'health_filter': <starlite_bedrock.starlite.logging.AccessLogFilter   │ │
│ │                    object at 0x10cbe83d0>                                                    │ │
│ │                    }                                                                         │ │
│ │       formatters = {'standard': <Formatter: fmt='%(message)s'>}                              │ │
│ │          handler = <starlite.logging.picologging.QueueListenerHandler object at 0x10cbb4140> │ │
│ │         handlers = {                                                                         │ │
│ │                    │   'console': <starlite_bedrock.starlite.logging.RichPicologgingHandler  │ │
│ │                    object at 0x10bec03c0>,                                                   │ │
│ │                    │   'queue_listener': <starlite.logging.picologging.QueueListenerHandler  │ │
│ │                    object at 0x10cbb4140>                                                    │ │
│ │                    }                                                                         │ │
│ │                i = 4                                                                         │ │
│ │      incremental = False                                                                     │ │
│ │          loggers = {                                                                         │ │
│ │                    │   'starlite_bedrock': {'propagate': True},                              │ │
│ │                    │   'uvicorn.access': {'propagate': True, 'filters': ['health_filter']},  │ │
│ │                    │   'uvicorn.error': {'propagate': True},                                 │ │
│ │                    │   'gunicorn.access': {                                                  │ │
│ │                    │   │   'propagate': True,                                                │ │
│ │                    │   │   'filters': ['health_filter']                                      │ │
│ │                    │   },                                                                    │ │
│ │                    │   'gunicorn.error': {'propagate': True},                                │ │
│ │                    │   'sqlalchemy': {'propagate': True},                                    │ │
│ │                    │   'starlite': {'level': 'WARNING', 'propagate': True},                  │ │
│ │                    │   'pydantic_openapi_schema': {                                          │ │
│ │                    │   │   'propagate': True,                                                │ │
│ │                    │   │   'level': 'WARNING',                                               │ │
│ │                    │   │   'handlers': ['queue_listener']                                    │ │
│ │                    │   }                                                                     │ │
│ │                    }                                                                         │ │
│ │             name = 'pydantic_openapi_schema'                                                 │ │
│ │     num_existing = 4                                                                         │ │
│ │            pflen = 24                                                                        │ │
│ │         prefixed = 'pydantic_openapi_schema.'                                                │ │
│ │             root = <Logger 'root' (INFO)>                                                    │ │
│ │             self = <picologging.config.DictConfigurator object at 0x10cbe8340>               │ │
│ ╰──────────────────────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                                  │
│ /Users/codyfincher/Code/OPDBA/oracle-database-assessment/master/.venv/lib/python3.10/site-packag │
│ es/picologging/config.py:46 in _handle_existing_loggers                                          │
│                                                                                                  │
│    43 │   for log in existing:                                                                   │
│    44 │   │   logger = root.manager.loggerDict[log]                                              │
│    45 │   │   if log in child_loggers:                                                           │
│ ❱  46 │   │   │   if not isinstance(logger, picologging.PlaceHolder):                            │
│    47 │   │   │   │   logger.setLevel(picologging.NOTSET)                                        │
│    48 │   │   │   │   logger.handlers = []                                                       │
│    49 │   │   │   │   logger.propagate = True                                                    │
│                                                                                                  │
│ ╭────────────────────────────────── locals ───────────────────────────────────╮                  │
│ │    child_loggers = ['starlite_bedrock.starlite.exceptions']                 │                  │
│ │ disable_existing = False                                                    │                  │
│ │         existing = [                                                        │                  │
│ │                    │   'opdba',                                             │                  │
│ │                    │   'opdba.domain.collection.routes',                    │                  │
│ │                    │   'starlite_bedrock.starlite.exceptions'               │                  │
│ │                    ]                                                        │                  │
│ │              log = 'starlite_bedrock.starlite.exceptions'                   │                  │
│ │           logger = <Logger 'starlite_bedrock.starlite.exceptions' (NOTSET)> │                  │
│ │             root = <Logger 'root' (INFO)>                                   │                  │
│ ╰─────────────────────────────────────────────────────────────────────────────╯                  │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: module 'picologging' has no attribute 'PlaceHolder'

Bug: missing py.typed

Hi, Currently there is no py.typed file in the package, which causes issues such as this:

starlite/logging/picologging.py:9: error: Class cannot subclass "QueueHandler" (has type "Any")  [misc]
Found 1 error in 1 file (checked 3 source files)

I will add a nano PR for this.

Run isort

And then run isort --check-only in workflow.

The module is not reported correctly on handled records

I noticed this issue while working on the QueueHandler.

The following test succeeds with CPython logging but fails with picologging:

def test_queue_handler_dispatch():
    logger = picologging.Logger("test", picologging.DEBUG)
    q = queue.Queue()
    handler = QueueHandler(q)
    logger.addHandler(handler)
    logger.debug("test")
    record = q.get(block=False)
    assert record.module == "test_queuehandler"

In picologging, the module is reported as simply "python" instead. I haven't dug in yet to see where the problem is.

getLogger() raises ValueError

import picologging as logging
logging.getLogger()
# raises ValueError: character U+96c240 is not in range [U+0000; U+10ffff]
# expected: <RootLogger root (WARNING)>

See #11

Run pyupgr

And then potentially run it in check mode in CI.

Non-blocking logger

Hi there,

First thanks for this library - python really needs something like this. Secondly - I currently implement non-blocking logging using the QueueListener from the stlib logging library. You can see this for example here. Using the QueueListener and QueueHandler logging becomes non-blocking, which is really crucial for work with async applications. I'd like to ask (a) what, if any, async support does this library offer? (b) Is there a roadmap for async feautures? And (c) what features are planned for this?

Cheers!

Set `tp_base` for child extension types

None of the C++ types that are children of other types have tp_base set.

https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_base

I'm not sure of the implications of this. There's a possibility that the base types' dealloc method isn't being called.

The child types are

  • Logger (Filterer)
  • Handler (Filterer)
  • StreamHandler (Handler)

To verify, create a very simple test and set break points in the dealloc methods for the child types and trace the dealloc into the child type (or put a print statement in Filterer_dealloc to see if it ever gets called.

Issue with `setLevel` not resetting other levels

After we set the level with setLevel, the higher levels should work but the lower levels should not work.
For example:

logger = picologging.getLogger("test")
logger.setLevel(picologging.WARNING)

logger.debug("test") # Should not log, but it does
logger.warning("test") # Works ok

logger.setLevel(picologging.ERROR)

logger.warning("test") # Should not log, but it does
logger.error("test") # Works ok

`getLevelName` is missing a part of the behavior

logging.getLevelName works as a two way street between level and level names:

>>> import logging
>>> logging.getLevelName(logging.WARNING)
'WARNING'
>>> logging.getLevelName("WARNING")
30

In picologging, the second way raises a TypeError:

>>> logging.getLevelName("WARNING")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: level must be an integer

LogRecord is not copy-able

CPython's LogRecord is copyable, i.e. copy.copy(record) is a successful call. It's only pickable if there is no exc_info and the arguments are pickleable, however, so pickle.dumps(record varies in its success.

Our own LogRecord is not copyable, copy.copy(record) results in an immediate error: "cannot pickle LogRecord object". That's due to the logic in reduce_ex:
https://github.com/python/cpython/blob/main/Lib/copyreg.py#L65

I've seen several suggestions of the right way to make a custom C type copyable, either getstate (described in https://pythonextensionpatterns.readthedocs.io/en/latest/pickle.html) or getnewargs (a newer way?).

Not sure whether we need it to be copyable, as I believe my approach in the latest PR works as well as calling copy.copy(), but maybe it's flawed in some way.

Loggers created with getLogger don't have parent set

I discovered this by trying to run CPython's BasicFilterTest suite on picologging.

You can check it out in the shell:

>>> import logging
>>> root_logger = logging.getLogger('')
>>> spam_logger = logging.getLogger('spam')
>>> spam_logger.parent
<RootLogger root (WARNING)>
>>> import picologging as logging
>>> root_logger = logging.getLogger('')
>>> spam_logger = logging.getLogger('spam')
>>> spam_logger.parent
>>> 

Or you can see its effect on getEffectiveLevel() on the child logger, in this small test which succeeds in CPython logging but not picologging:

def test_parent_levels():
    root_logger = picologging.getLogger('')
    root_logger.setLevel(logging.DEBUG)
    spam_logger = picologging.getLogger('spam')
    assert spam_logger.getEffectiveLevel() == logging.DEBUG

CPython's getLogger method calls _fixUpParents to set parent, but there's nothing done to that effect in picologging.

basicConfig discrepancies

I tested picologging as a drop-in replacement for logging (CPython 3.10), it looks really great! A few things I picked up:

  • I opened a small PR to fix the README instruction regarding basicConfig()

  • when running:

    import picologging as logging
    logging.basicConfig()
    logging.warning("test")
    # expected: WARNING:root:test

    On standard logging, basicConfig configures a StreamHandler at WARNING level. picologging does not seem to do that, as the logging.warning does not show in the console.

  • when running:

    import picologging as logging
    logging.getLogger()
    # raises ValueError: character U+96c240 is not in range [U+0000; U+10ffff]
    # expected: <RootLogger root (WARNING)>
  • another small issue:

    import picologging as logging
    logging.basicConfig()
    logging.basicConfig(force=True)
    # raises AttributeError: 'picologging.StreamHandler' object has no attribute 'close'
  • finally, but this is expected, logging.getLevelName is not implemented, so I patched it on my end to run the tests

No logging.shutdown() function

CPython logging has a shutdown function:
https://docs.python.org/3/library/logging.html#logging.shutdown

The documentation describes it as taking 0 arguments, but it actually takes a handlers argument which defaults to the module internal _handersList, a list of handlers. Picologging doesnt have _handlersList, but it does have root, so I think it should be able to find all the handlers that way.

Is the lack of shutdown() a limitation or a bug?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.