GithubHelp home page GithubHelp logo

loggo2's People

Contributors

crbp avatar dabauxi avatar dwickwire avatar gcarq avatar hukkinj1 avatar ilkericyuz avatar interrogator avatar jantofelbp avatar josumoreno-bp avatar jpwood avatar l0rb avatar michdr avatar oskyk avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

loggo2's Issues

Module decoration?

So it turns out it is fairly possible to decorate entire third party modules (with about 20 additional lines of code). Here is an example of what happens if you log everything that happens while building a pandas dataframe:

In [1]: from loggo import Loggo                                                                                                      

In [2]: import pandas as pd                                                                                                          

In [3]: loggo = Loggo({'do_print': True})                                                                                            
    29.05 2019 15:56:04 Graylog not configured! Disabling it    30

Graylog not configured! Disabling it

In [4]: lp = loggo(pd)                                                                                                               

In [5]: df = lp.DataFrame(list(range(2, 20)))                                                                                        
    29.05 2019 15:56:18 *Called DatetimeTZDtype.construct_from_string(string=[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19])  20
    29.05 2019 15:56:18 *Errored during DatetimeTZDtype.construct_from_string(string=[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) with TypeError "Could not construct DatetimeTZDtype" 20 -- see below: 
Traceback (most recent call last):
  File "/home/dannio/work/loggo/loggo/loggo.py", line 314, in full_decoration
    response = function(*args, **kwargs)
  File "/home/dannio/.local/lib/python3.6/site-packages/pandas/core/dtypes/dtypes.py", line 701, in construct_from_string
    raise TypeError("Could not construct DatetimeTZDtype")
TypeError: Could not construct DatetimeTZDtype

    29.05 2019 15:56:18 *Called RangeIndex._validate_dtype(dtype=None)  20
    29.05 2019 15:56:18 *Returned None from RangeIndex._validate_dtype(dtype=None)  20
    29.05 2019 15:56:18 *Called RangeIndex._simple_new(start=0, stop=18, step=1, protected_name=None)   20
    29.05 2019 15:56:18 *Called Index._reset_identity() 20
    29.05 2019 15:56:18 *Called RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Returned None from RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Called RangeIndex._format_attrs()  20
    29.05 2019 15:56:18 *Called RangeIndex._get_data_as_items() 20
    29.05 2019 15:56:18 *Returned from RangeIndex._get_data_as_items() with list ([('start', 0), ('stop', 18), ('step', 1)])20
    29.05 2019 15:56:18 *Returned from RangeIndex._format_attrs() with list ([('start', 0), ('stop', 18), ('step', 1)]) 20
    29.05 2019 15:56:18 *Called Index._format_space()   20
    29.05 2019 15:56:18 *Returned from Index._format_space() with str (' ') 20
    29.05 2019 15:56:18 *Returned from Index._reset_identity() with RangeIndex (RangeIndex(start=0, stop=18, step=1))   20
    29.05 2019 15:56:18 *Called RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Returned None from RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Called RangeIndex._format_attrs()  20
    29.05 2019 15:56:18 *Called RangeIndex._get_data_as_items() 20
    29.05 2019 15:56:18 *Returned from RangeIndex._get_data_as_items() with list ([('start', 0), ('stop', 18), ('step', 1)])20
    29.05 2019 15:56:18 *Returned from RangeIndex._format_attrs() with list ([('start', 0), ('stop', 18), ('step', 1)]) 20
    29.05 2019 15:56:18 *Called Index._format_space()   20
    29.05 2019 15:56:18 *Returned from Index._format_space() with str (' ') 20
    29.05 2019 15:56:18 *Returned from RangeIndex._simple_new(start=0, stop=18, step=1, protected_name=None) with RangeIndex (RangeIndex(start=0, stop=18, step=1)) 20
    29.05 2019 15:56:18 *Called RangeIndex._validate_dtype(dtype=None)  20
    29.05 2019 15:56:18 *Returned None from RangeIndex._validate_dtype(dtype=None)  20
    29.05 2019 15:56:18 *Called RangeIndex._simple_new(start=0, stop=1, step=1, protected_name=None)    20
    29.05 2019 15:56:18 *Called Index._reset_identity() 20
    29.05 2019 15:56:18 *Called RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Returned None from RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Called RangeIndex._format_attrs()  20
    29.05 2019 15:56:18 *Called RangeIndex._get_data_as_items() 20
    29.05 2019 15:56:18 *Returned from RangeIndex._get_data_as_items() with list ([('start', 0), ('stop', 1), ('step', 1)]) 20
    29.05 2019 15:56:18 *Returned from RangeIndex._format_attrs() with list ([('start', 0), ('stop', 1), ('step', 1)])  20
    29.05 2019 15:56:18 *Called Index._format_space()   20
    29.05 2019 15:56:18 *Returned from Index._format_space() with str (' ') 20
    29.05 2019 15:56:18 *Returned from Index._reset_identity() with RangeIndex (RangeIndex(start=0, stop=1, step=1))    20
    29.05 2019 15:56:18 *Called RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Returned None from RangeIndex._format_data()   20
    29.05 2019 15:56:18 *Called RangeIndex._format_attrs()  20
    29.05 2019 15:56:18 *Called RangeIndex._get_data_as_items() 20
    29.05 2019 15:56:18 *Returned from RangeIndex._get_data_as_items() with list ([('start', 0), ('stop', 1), ('step', 1)]) 20
    29.05 2019 15:56:18 *Returned from RangeIndex._format_attrs() with list ([('start', 0), ('stop', 1), ('step', 1)])  20
    29.05 2019 15:56:18 *Called Index._format_space()   20
    29.05 2019 15:56:18 *Returned from Index._format_space() with str (' ') 20
    29.05 2019 15:56:18 *Returned from RangeIndex._simple_new(start=0, stop=1, step=1, protected_name=None) with RangeIndex (RangeIndex(start=0, stop=1, step=1))   20
    29.05 2019 15:56:18 *Called IntervalDtype.is_dtype(dtype=dtype('int64'))    20
    29.05 2019 15:56:18 *Returned from IntervalDtype.is_dtype(dtype=dtype('int64')) with bool (False)   20
    29.05 2019 15:56:18 *Called PeriodDtype.is_dtype(dtype=dtype('int64'))  20
    29.05 2019 15:56:18 *Returned from PeriodDtype.is_dtype(dtype=dtype('int64')) with bool (False) 20

Right now doing some further operations with the dataframe can cause stack overflow (lol), but probably because i'm recursively decorating everything with Loggo. It should be possible to fix this so everything callable is decorated exactly once. But ... is it worth it?

Classmethods bug

if you put loggo decorator on class and call a class method via self.__class__.method() you will get an attribute error because loggo overwrites class property

Loggo cannot handle lambda or functools.partial class attributes

Right now we are missing a coverage line for callables without __name__, because these are very rare. functools.partial objects don't have __name__ but are callable, so I decided to try to write a test, but it threw an unexpected error, which we probably should handle:

from loggo import Loggo
from functools import partial
loggo = Loggo(do_print=True)

@loggo 
class WithUnnamed: 
    basetwo = partial(int, base=2) 

WithUnnamed().basetwo()                                                 
ValueError                                Traceback (most recent call last)
<ipython-input-36-8625eace0720> in <module>
----> 1 WithUnnamed().basetwo()

~/work/loggo/loggo/loggo.py in full_decoration(*args, **kwargs)
    242             Args and kwargs are for/from the decorated function
    243             """
--> 244             bound = self._params_to_dict(function, *args, **kwargs)
    245             # bound will be none if inspect signature binding failed. in this
    246             # case, error log was created, raised if self.raise_logging_errors

~/work/loggo/loggo/loggo.py in _params_to_dict(self, function, *args, **kwargs)
    331         Turn args and kwargs into an OrderedDict of {param_name: value}
    332         """
--> 333         sig = inspect.signature(function)
    334         bound = sig.bind(*args, **kwargs).arguments
    335         if bound:

/usr/lib/python3.6/inspect.py in signature(obj, follow_wrapped)
   3055 def signature(obj, *, follow_wrapped=True):
   3056     """Get a signature object for the passed callable."""
-> 3057     return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
   3058 
   3059 

/usr/lib/python3.6/inspect.py in from_callable(cls, obj, follow_wrapped)
   2805         """Constructs Signature for the given callable object."""
   2806         return _signature_from_callable(obj, sigcls=cls,
-> 2807                                         follow_wrapper_chains=follow_wrapped)
   2808 
   2809     @property

/usr/lib/python3.6/inspect.py in _signature_from_callable(obj, follow_wrapper_chains, skip_bound_arg, sigcls)
   2276             follow_wrapper_chains=follow_wrapper_chains,
   2277             skip_bound_arg=skip_bound_arg,
-> 2278             sigcls=sigcls)
   2279         return _signature_get_partial(wrapped_sig, obj)
   2280 

/usr/lib/python3.6/inspect.py in _signature_from_callable(obj, follow_wrapper_chains, skip_bound_arg, sigcls)
   2345                 else:
   2346                     raise ValueError(
-> 2347                         'no signature found for builtin type {!r}'.format(obj))
   2348 
   2349     elif not isinstance(obj, _NonUserDefinedCallables):

ValueError: no signature found for builtin type <class 'int'>

@gcarq @hukkinj1 you guys ever used partial? How should loggo handle it?

100% coverage

We should get coverage to 100% and then disallow any code additions that are not covered.

Right now (coverage 96.42%) there are just a few edge cases that need tests, probably requiring a bit of mocking etc, but it's doable and would be nice.

Alert level semantics: your ultimate guide

Make yourself a cup of tea and consider:

Any time you log, whether you like it or not, there is an 'alert level', But what does it mean? It's actually surprisingly complex. We have to ask ourselves, why is there an alert? For whom? And what does changing such a level do? What can it do? What could it do?

Python's logging module (which we draw upon right now) has what we might call 'named numbers':

CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

These are supposed to represent a kind of scale of severity. Adding confusion, our code had to deal with bitpanda legacy features, and graylog compatibility. This is why Loggo allows us to pass strings, minor, dev and critical, as alert level.

We likely all agree that having multiple level types is a bad idea, but it's interesting to consider why it happened in the first place.

In short, there is an ever-present question of what the semantics of the numeric scale actually mean: should the scale progress from expected --> unexpected, from good --> bad, or common --> rare?

Competing standards arose to address these ambiguities. Then, compounding the problem a great deal was that that nobody really gives a shit about logging, and accordingly, nobody ever confronted the complexity of this ridiculous question.

One certainty, therefore, is that we will never do things right in an ontological sense. Internally consistent and expressive code would ideally handle multiple types of alert levels, with a multifaceted set of sequences. Imagine the joy and horror of:

log('overkill', expectedness=6, severity=2, badness=9)

Fortunately for us, however, the different types of alerts tend to pattern together. Unless a product really sucks a lot, you would not its logs to have high values for expectedness, severity and badness all at once. These things all mean different things, but they seem to overlap more than they don't. So, we're back to square one, only now we know that our implementation will stink.

What to do now: the wrong way

There is an obvious pathway for improving our alert level handling. That is to remove from Loggo the ability to handle legacy bitpanda/graylog stuff, such as 'minor', 'dev' and 'critical' strings. Instead, the user should use the integer defined in the logging module.

To make things a bit nicer, we can also do two other things:

  1. Make available Loggo.DEBUG == 10 and so on. I don't want to import loggo and logging, and I think at least at times, using the namespaced values is easier to understand than e.g., a random 50, which means critical.
  2. Allow the call signature from logging --- log(10, 'msg', data):
if isinstance(msg, int) and isinstance(level, str):
    msg, level = level, msg

Bitpanda's subclasses of Loggo can, if they like, interpret the deprecated strings, and derive graylog data from them if need be. None of my business really.

What to do: the right way

  1. Read this document: https://tools.ietf.org/html/rfc5424
  2. Internalise its teachings
  3. See if we can solve the complexities outlined above in a fantastic and novel way
  4. When this fails, just do the wrong way
  5. Sauna whenever possible

Generate a readthedocs page with mkdocs?

I recently set up readthedocs via mkdocs on another module of mine, it was easy and works nicely.

https://buzz.readthedocs.io/en/latest/

Would take 15 mins to set up for loggo using the README as the main page, but I'm not sure we should. Pros and cons are pretty obvious.

Have mentioned this before, but would like to reach a final decision. I lean toward a yes purely because I think it's kind of cool.

What should be configurable?

Right now it seems like some things should be configurable and are not; other things do need need to be and are.

  • minimum alert level could be configurable
  • Users might like to be able to pass in custom strings for autologging

Meanwhile:

  • will anybody care about max_dict_depth or the string with which private data is obscured?

Just something to think about.

Coverage badge

Would love to have a coverage badge on this project. Any takers?

TRAVIS

@jorop to set up travis CI to run tests unless there is nowadays a nicer way like we got with gitlab

Remove loggo frames from traceback

Some of us don't like the fact that loggo accounts for about half the traceback when there is an error. Loggo itself is fairly stable now, so pruning the traceback object to remove the Loggo frames would help a bit.

New feature discussion: Adding inspectable parent info to manual calls to `Loggo.log`

When we do a manual log (Loggo.log('msg', level, extra)), the extra data is just whatever we pass in, plus the (probably pointless) 'loggo': True item.

Question: @hukkinj1 , @gcarq , would it be nice or not nice to inspecta little bit during these calls, and add a few things to extra data? Would be trivial to get script name, class name, func name, etc., though doing it in a nice consistent way might be a bit harder. Code could likely be shared from how we reconstruct build the call signature string during autologging, though of course it would be hackier, as we aren't passing the callable around, but need to get it through inspect stuff instead.

Helpful or not?

Django hookup

Here is an example settings.py excerpt:

Using with DjangoIts easy to integrate graypy with Djangos logging settings. Just add a new handler in your settings.py like this:

LOGGING = {
    ...

    'handlers': {
        'graypy': {
            'level': 'WARNING',
            'class': 'graypy.GELFHandler',
            'host': 'localhost',
            'port': 12201,
        },
    },

    'loggers': {
        'django.request': {
            'handlers': ['graypy'],
            'level': 'ERROR',
            'propagate': True,
        },
    },
}

Seems like we could do more in loggo in terms of integration here. could even have a loggo.django_setttings() which returns all the relevant stuff, or something. I will likely be doing more django in future, so this might be something I'll have time to explore.

Time to remove emergency log?

_emergency_log() has been around for years and for historical reasons. @hukkinj1 you think we should just remove it? there is an option to raise/not raise errors during logging, i'd probably rather just use that and potentially raise instead.

Use wrapt

the wrapt module provides some helper decorators for making class decorators that attach to all methods. we do some creepy stuff to make this work, so it'd be better to rely on a well-known implementation.

Use flake8

This project shouldn't take too long to hook up to flake8. Why not? @gcarq can do.

@loggo class decorator tries to decorate parent class's methods also

repro:

class A:
  def f():
    pass

@loggo
class B(A):
  def g():
    pass

results in:

    def _decorate_all_methods(self, cls: type, just_errors: bool = False) -> type:
             [...]
>           if isinstance(vars(cls)[name], (staticmethod, classmethod)):
E           KeyError: 'f'

../../venv/lib/python3.8/site-packages/loggo/_loggo.py:228: KeyError

Combine Loggo() and loggo.events

  • Remove loggo.events, which has very similar code to logme anyway
  • Add its functionality to the line where Loggo is instantiated with config: loggo = Loggo(config, called='custom message --- {timestamp}, {call_signature}')
  • Change internal names pre -> called etc now that they are exposed to user
  • If user explicitly passes called=None, no logs for calling. Otherwise, existing defaults are used
  • Document available formatting strings in README
  • Move error_alert_level to config, default 50
  • build_string no longer needed, timestamp and trace can be in the default format strings

This will reduce size of codebase, which is good.

Loggo decorators not logging when class not instantiated

Proof:

In [15]: from loggo import Loggo                                                                                                                                                                                                            

In [16]: l = Loggo({'do_print': True})                                                                                                                                                                                                      

In [17]: @l 
    ...: class X():     
    ...:     @staticmethod 
    ...:     def f(): 
    ...:         return 1 
    ...:                                                                                                                                                                                                                                    

In [18]: X().f()                                                                                                                                                                                                                            
	14.01 2019 17:44:39	*Called __main__.f()	None

	14.01 2019 17:44:39	*Returned from __main__.f() with int (1)	None

Out[18]: 1

In [19]: X.f()                                                                                                                                                                                                                              
Out[19]: 1

Is traceback persisting?

I believe I saw a traceback key and an old traceback in a '*Returned' log, which should not be possible. Figure out how to properly clear traceback.

Loggo throws when `inspect.signature()` fails to get function/method signature

inspect.signature() is run for Loggo-decorated functions. There's no knowing that it succeeds in getting the signature for all callables however. It may throw ValueError. Try for example inspect.signature(print). Result: ValueError.
My suggestion: We simply don't log functions where inspect.signature() fails, silently eat the ValueError, and clearly document this limitation in the README and code.

Argument counting fail?

cilib: *Errored with RpcError "Internal Server Error" when calling cilib.rpc.stellarrpc._getquery function with 0 args, 0 kwargs: args=tuple(tuple(friendbot)).

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.