Skip to content

Commit

Permalink
Merge pull request #13 from databio/docs
Browse files Browse the repository at this point in the history
dev
  • Loading branch information
nsheff authored Aug 29, 2019
2 parents 8b9e904 + fa5b034 commit eec32a2
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 62 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
[![Build Status](https://travis-ci.org/vreuter/logmuse.svg?branch=master)](https://travis-ci.org/vreuter/logmuse)
[![Coverage Status](https://coveralls.io/repos/github/vreuter/logmuse/badge.svg?branch=master)](https://coveralls.io/github/vreuter/logmuse?branch=master)

Small logging setup package
Logmuse is a small logging setup package. The point of logmuse is to make it super simple to add CLI-control of logging to your python CLI tool. It just provides a simple interface so that standard arguments can be passed on to the logger.

It is only useful for CLI tools.

135 changes: 86 additions & 49 deletions docs/autodoc_build/logmuse.md
Original file line number Diff line number Diff line change
@@ -1,93 +1,130 @@
# Package logmuse Documentation

## Class AbsentOptionException
<script>
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('h3 code').forEach((block) => {
hljs.highlightBlock(block);
});
});
</script>

<style>
h3 .lucidoc{
padding-left: 22px;
text-indent: -15px;
}
h3 .hljs .lucidoc{
padding-left: 20px;
margin-left: 0px;
text-indent: -15px;
martin-bottom: 0px;
}
h4 .lucidoc, table .lucidoc, p .lucidoc, li .lucidoc { margin-left: 30px; }
h4 .lucidoc {
font-style: italic;
font-size: 1em;
margin-bottom: 0px;
}

</style>
<div class='lucidoc'>

# Package `logmuse` Documentation

## <a name="AbsentOptionException"></a> Class `AbsentOptionException`
Exception subtype suggesting that client should add log options.


### add\_logging\_options
Augment a CLI argument parser with this package's logging options.
```python
def add_logging_options(parser)
def init_logger(name='', level=None, stream=None, logfile=None, make_root=None, propagate=False, silent=False, devmode=False, verbosity=None, fmt=None, datefmt=None, plain_format=False, style=None)
```

**Parameters:**
Establish and configure primary logger.

This is intended to be called just once per "session", with a "session"
defined as an invocation of the main workflow, a testing session, or an
import of the primary abstractions, e.g. in an interactive iPython session.
#### Parameters:

- `name` (`str`): name for the logger
- `level` (`int | str`): minimal level of messages to listen for
- `stream` (`str`): standard stream to use as log destination. The defaultbehavior is to write logs to stdout, even if null is passed here. This is to allow a CLI argument as input to stream parameter, where it may be undesirable to require specification of a default value in the client application in order to prevent passing None if no CLI option value is given. To disable standard stream logging, set 'silent' to True or pass a path to a file to which to write logs, which gets priority over a standard stream as the destination for log messages.
- `logfile` (`str | FileIO[str]`): path to filesystem location to use aslogs destination. if provided, this mutes standard stream logging.
- `make_root` (`bool`): whether to use returned logger as root logger. Thismeans the name will be 'root' and that messages will not propagate.
- `propagate` (`bool`): whether to allow messages from this logger to reachparent logger(s).
- `silent` (`bool`): whether to silence logging; this is only guaranteed formessages from this logger and for those from loggers beneath this one in the runtime hierarchy without no separate handling. Propagation must also be turned off separately--if this is not the root logger--in order to ensure that messages are not handled and emitted from a potential parent to the logger built here.
- `devmode` (`bool`): whether to log in development mode; possibly amongother behavioral changes to logs handling, use a more information-rich message format template.
- `verbosity` (`int | str`): alternate mode of expression for logging levelthat better accords with intuition about how to convey this. It's positively associated with message volume rather than negatively so, as logging level is. This takes precedence over 'level' if both are present.
- `fmt` (`str`): message format/template.
- `datefmt` (`str`): format/template for time component of a log record.
- `plain_format` (`bool`): force use of plain message format, even ifin development mode (debug level)
- `style` (`str`): string indicating message formatting strategy; refer tohttps://docs.python.org/3/howto/logging-cookbook.html#use-of-alternative-formatting-styles; only valid in Python3.2+

- `parser` -- `argparse.ArgumentParser`: CLI options and argument parser toaugment with logging options.

#### Returns:

**Returns:**
- `logging.Logger`: configured Logger instance


#### Raises:

- `ValueError`: if attempting to name explicitly non-root logger witha root name, or if both level and verbosity are specified




```python
def setup_logger(name='', level=None, stream=None, logfile=None, make_root=None, propagate=False, silent=False, devmode=False, verbosity=None, fmt=None, datefmt=None, plain_format=False, style=None)
```

`argparse.ArgumentParser`: the input argument, supplemented with thispackage's logging options.
Old alias for init_logger for backwards compatibility



```python
def logger_via_cli(opts, strict=True, **kwargs)
```

### logger\_via\_cli
Convenience function creating a logger.

This module provides the ability to augment a CLI parser with
logging-related options/arguments so that client applications do not need
intimate knowledge of the implementation. This function completes that
lack of burden, parsing values for the options supplied herein.
```python
def logger_via_cli(opts, **kwargs)
```

**Parameters:**
#### Parameters:

- `opts` -- `argparse.Namespace`: command-line options/arguments.
- `opts` (`argparse.Namespace`): command-line options/arguments.
- `strict` (`bool`): whether to raise an exception


**Returns:**
#### Returns:

`logging.Logger`: configured logger instance.
- `logging.Logger`: configured logger instance.


**Raises:**
#### Raises:

- `pararead.logs.AbsentOptionException`: if one of the expected optionsisn't available in the given Namespace. Such a case suggests that a client application didn't use this module to add the expected logging options to a parser.
- `pararead.logs.AbsentOptionException`: if one of the expected optionsisn't available in the given Namespace, and the argument to the strict parameter is True. Such a case suggests that a client application didn't use this module to add the expected logging options to a parser.




### setup\_logger
Establish and configure primary logger.

This is intended to be called just once per "session", with a "session"
defined as an invocation of the main workflow, a testing session, or an
import of the primary abstractions, e.g. in an interactive iPython session.
```python
def setup_logger(name='', level=None, stream=None, logfile=None, make_root=None, propagate=False, silent=False, devmode=False, verbosity=None, fmt=None, datefmt=None, plain_format=False, style=None)
def add_logging_options(parser)
```

**Parameters:**

- `name` -- `str`: name for the logger
- `level` -- `int | str`: minimal level of messages to listen for
- `stream` -- `str`: standard stream to use as log destination. The defaultbehavior is to write logs to stdout, even if null is passed here. This is to allow a CLI argument as input to stream parameter, where it may be undesirable to require specification of a default value in the client application in order to prevent passing None if no CLI option value is given. To disable standard stream logging, set 'silent' to True or pass a path to a file to which to write logs, which gets priority over a standard stream as the destination for log messages.
- `logfile` -- `str | FileIO[str]`: path to filesystem location to use aslogs destination. if provided, this mutes standard stream logging.
- `make_root` -- `bool`: whether to use returned logger as root logger. Thismeans the name will be 'root' and that messages will not propagate.
- `propagate` -- `bool`: whether to allow messages from this logger to reachparent logger(s).
- `silent` -- `bool`: whether to silence logging; this is only guaranteed formessages from this logger and for those from loggers beneath this one in the runtime hierarchy without no separate handling. Propagation must also be turned off separately--if this is not the root logger--in order to ensure that messages are not handled and emitted from a potential parent to the logger built here.
- `devmode` -- `bool`: whether to log in development mode; possibly amongother behavioral changes to logs handling, use a more information-rich message format template.
- `verbosity` -- `int | str`: alternate mode of expression for logging levelthat better accords with intuition about how to convey this. It's positively associated with message volume rather than negatively so, as logging level is. This takes precedence over 'level' if both are present.
- `fmt` -- `str`: message format/template.
- `datefmt` -- `str`: format/template for time component of a log record.
- `plain_format` -- `bool`: force use of plain message format, even ifin development mode (debug level)
- `style` -- `str`: string indicating message formatting strategy; refer tohttps://docs.python.org/3/howto/logging-cookbook.html#use-of-alternative-formatting-styles; only valid in Python3.2+

Augment a CLI argument parser with this package's logging options.
#### Parameters:

**Returns:**
- `parser` (`argparse.ArgumentParser`): CLI options and argument parser toaugment with logging options.

`logging.Logger`: configured Logger instance

#### Returns:

**Raises:**
- `argparse.ArgumentParser`: the input argument, supplemented with thispackage's logging options.

- `ValueError`: if attempting to name explicitly non-root logger witha root name, or if both level and verbosity are specified



</div>


**Version Information**: `logmuse` v0.1, generated by `lucidoc` v0.3.1
*Version Information: `logmuse` v0.2.1, generated by `lucidoc` v0.4.0*
15 changes: 15 additions & 0 deletions docs/interactive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Using logmuse interactively


To set logmuse to DEBUG while in an interactive session, use:

```
logmuse.init_logger(PACKAGE, "DEBUG", devmode=True)
```

For example, for package divvy, which uses logmuse, run this in your interactive session:

```
logmuse.init_logger("divvy", "DEBUG", devmode=True)
```

100 changes: 100 additions & 0 deletions docs/tutorial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Tutorial

You are producing a CLI package that will use logmuse. Logmuse will provide command-line options to control logging, such as `--verbosity` and `--silent`. Here's how to set it up.

## Imported packages

For any packages that are imported by your CLI package, all you have to do is follow the normal usage of the built-in `logging` module. **No need to use logmuse in imported packages**. Just type, for example, something like:

```
_LOGGER = logging.getLogger(__name__)
```

At the top of each module, and then use `_LOGGER.debug` or whatever in the module. That's it. The command line arguments passed via your CLI will control the imported packages as well. Make sure your package isn't making a root logger or something silly like that.


## Your CLI package

Now, to use logmuse in your CLI package, *including* all imported loggers, all you have to do is:

## 1 Initialize

Just add to the `__init__.py` file:

```
import logmuse
logmuse.init_logger(PACKAGE)
```

Where `PACKAGE` is the name of your package. Now it will be set up with default parameters for your within-python-use. Remember, **this is only for the CLI package.** Do not add this code to client packages that do not implement CLIs.


## 2 Add CLI args

When you build your argparser, add the logmuse CLI options with this:


```
parser = logmuse.add_logging_options(parser)
```

This will give you:

- `--verbosity`
- `--silent`
- `--logdev`

And your logger will automatically respond to these command-line arguments. (PS, [pypiper](http://pypiper.databio.org) uses logmuse to add these; so if you're using pypiper to add args, don't repeat).


## 3 Activate logmuse

At the top of your module file, say:

```
import logmuse
```

In your `main` function say:

```
global _LOGGER
_LOGGER = logmuse.logger_via_cli(args, make_root=True)
```

Here, `args` is the result of argparse.parse_args().




## Old way

No need to read further. This is how it *used to* work, before it was awesome.

```
# Set the logging level.
if args.dbg:
# Debug mode takes precedence and will listen for all messages.
level = args.logging_level or logging.DEBUG
elif args.verbosity is not None:
# Verbosity-framed specification trumps logging_level.
level = _LEVEL_BY_VERBOSITY[args.verbosity]
else:
# Normally, we're not in debug mode, and there's no verbosity.
level = LOGGING_LEVEL
# Establish the project-root logger and attach one for this module.
logger_kwargs = {"level": level, "logfile": args.logfile, "devmode": args.dbg}
init_logger(name="peppy", **logger_kwargs)
init_logger(name="divvy", **logger_kwargs)
global _LOGGER
_LOGGER = init_logger(name=_PKGNAME, **logger_kwargs)
logger_kwargs = {"level": level, "logfile": args.logfile, "devmode": args.dbg}
init_logger(name="peppy", **logger_kwargs)
init_logger(name="divvy", **logger_kwargs)
```

We should move the logging level stuff into logmuse
1 change: 1 addition & 0 deletions logmuse/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .est import *
from ._version import __version__
from .est import LEVEL_BY_VERBOSITY, DEV_LOGGING_FMT
2 changes: 1 addition & 1 deletion logmuse/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.2.1"
__version__ = "0.2.2"
30 changes: 23 additions & 7 deletions logmuse/est.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
__email__ = "[email protected]"

__all__ = ["add_logging_options", "logger_via_cli", "init_logger",
"setup_logger", "AbsentOptionException"]
"setup_logger", "AbsentOptionException", "LOGGING_CLI_OPTDATA"]


BASIC_LOGGING_FORMAT = "%(message)s"
DEV_LOGGING_FMT = "[%(asctime)s] {%(name)s:%(lineno)d} (%(funcName)s) [%(levelname)s] > %(message)s "
DEV_LOGGING_FMT = "%(levelname).4s %(asctime)s | %(name)s:%(module)s:%(lineno)d > %(message)s "
DEFAULT_DATE_FMT = "%H:%M:%S"
PACKAGE_NAME = "logmuse"
STREAMS = {"OUT": sys.stdout, "ERR": sys.stderr}
DEFAULT_STREAM = STREAMS["ERR"]
Expand All @@ -30,9 +31,9 @@
TRACE_LEVEL_VALUE = 5
TRACE_LEVEL_NAME = "TRACE"
CUSTOM_LEVELS = {TRACE_LEVEL_NAME: TRACE_LEVEL_VALUE}
SILENCE_LOGS_OPTNAME = "--silent"
VERBOSITY_OPTNAME = "--verbosity"
DEVMODE_OPTNAME = "--logdev"
SILENCE_LOGS_OPTNAME = "silent"
VERBOSITY_OPTNAME = "verbosity"
DEVMODE_OPTNAME = "logdev"
PARAM_BY_OPTNAME = {DEVMODE_OPTNAME: "devmode"}

# Translation of verbosity into logging level.
Expand Down Expand Up @@ -70,7 +71,7 @@ def add_logging_options(parser):
package's logging options.
"""
for optname, optdata in LOGGING_CLI_OPTDATA.items():
parser.add_argument("{}".format(optname), **optdata)
parser.add_argument("--{}".format(optname), **optdata)
return parser


Expand Down Expand Up @@ -115,7 +116,7 @@ def logger_via_cli(opts, strict=True, **kwargs):
def init_logger(
name="", level=None, stream=None, logfile=None,
make_root=None, propagate=False, silent=False, devmode=False,
verbosity=None, fmt=None, datefmt=None, plain_format=False, style=None):
verbosity=None, fmt=None, datefmt=DEFAULT_DATE_FMT, plain_format=False, style=None):
"""
Establish and configure primary logger.
Expand Down Expand Up @@ -317,3 +318,18 @@ def __init__(self, missing_optname):
format(missing_optname, "{}.{}".format(
__name__, add_logging_options.__name__))
super(AbsentOptionException, self).__init__(likely_reason)




# Stolen from peppy. Probably need to make peppy/looper rely on this.
def get_logger(name):
"""
Return a logger with given name, equipped with custom method.
:param str name: name for the logger to get/create.
:return logging.Logger: named, custom logger instance.
"""
l = logging.getLogger(name)
l.whisper = lambda msg, *args, **kwargs: l.log(5, msg, *args, **kwargs)
return l
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pypi_name: logmuse
nav:
- Introduction:
- Home: README.md
- Tutorial: tutorial.md
- Interactive logmuse: interactive.md
- Reference:
- API: autodoc_build/logmuse.md
- Support: support.md
Expand Down
Loading

0 comments on commit eec32a2

Please sign in to comment.