Skip to content

Commit 7b53657

Browse files
committed
initial commit
0 parents  commit 7b53657

9 files changed

+354
-0
lines changed

.gitignore

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# idea/intellij IDE project and module files
2+
.idea/
3+
*.iml
4+
5+
# vim swap files
6+
*.sw[mnop]
7+
*~
8+
9+
# python bytecode
10+
*.pyc
11+
12+
# unit testing generated files
13+
.coverage
14+
.noseids
15+
16+
# python packaging artifacts
17+
*.egg
18+
*.egg-info/
19+
build/
20+
dist/
21+
22+
# rpm packaging artifacts
23+
rpmbuild/
24+
25+
# log files
26+
*.log
27+
*.log.bz
28+
*.log.gz
29+
*.log.[0-9]
30+
*.log.[0-9][0-9]
31+
*.log.[0-9][0-9][0-9]
32+
*.\d\+\.log
33+
34+
# config files containing repo secrets
35+
pip.conf
36+
setup.cfg

.travis.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
language: python
2+
python:
3+
- "2.7"
4+
# command to install dependencies
5+
install:
6+
- "pip install -r requirements.txt"
7+
- "pip install -r test_requirements.txt"
8+
- "pip install -r setup_requirements.txt"
9+
# command to run tests
10+
script: nosetests --exe

VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.1.0

examples/configyamlwithenv.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/bin/env python2
2+
3+
"""
4+
A sample of YAML-based logging configuration.
5+
6+
The python standard logging library gets configured from YAML text. In this sample configuration three loggers are
7+
configured and each has a different level configured in a different way.
8+
- The root logger is configured.
9+
- The logger 'envlog' is configured using the environment variable LOGLEVEL. This is to illustrate how you can use
10+
the _ENV directive to customize configurations.
11+
- The logger 'envdefaultlog' would be configured using a variable, but since it doesnt exist the default in the _ENV
12+
directive is used. This is to illustrate the alternate syntax of the _ENV directive for default values.
13+
"""
14+
15+
import os
16+
import logging
17+
from StringIO import StringIO
18+
19+
from logging_yamlconfig import logging_yaml_init_fh
20+
21+
# make a logging configuration
22+
configtxt = """
23+
version: 1
24+
handlers:
25+
console:
26+
class: logging.StreamHandler
27+
stream: 'ext://sys.stdout'
28+
loggers:
29+
envlog:
30+
level: _ENV:LOGLEVEL
31+
envdefaultlog:
32+
level: _ENV:NONEXISTENTENVVAR:CRITICAL
33+
root:
34+
level: WARN
35+
handlers:
36+
- console
37+
"""
38+
39+
yamlfile = StringIO(configtxt)
40+
41+
# demonstrate env var support in yaml- DEBUG loglevel
42+
os.environ['LOGLEVEL'] = 'DEBUG'
43+
44+
# load the configuration with yaml dictconfig helper
45+
logging_yaml_init_fh(yamlfile)
46+
47+
# create a logger
48+
rootlog = logging.getLogger() # the effective log level will be WARN, as specified in the config
49+
envlog = logging.getLogger('envlog') # the level will be DEBUG since we put that in the environment
50+
envdefaultlog = logging.getLogger('envdefaultlog') # the environment variable (likely) doesn't exist so the default is used
51+
52+
# sample log messages- the different loggers will have different effective levels as determined by the yaml and the env
53+
for log in rootlog, envlog, envdefaultlog:
54+
print '### logger:', log.name
55+
log.debug('debug message')
56+
log.info('info message')
57+
log.warn('warn message')
58+
log.error('error message')
59+
log.critical('critical message')
60+
61+
# vim: set ts=4 sw=4 expandtab:

logging_yamlconfig/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .configloader import EnvLoggingYAMLConfigLoader, LoggingYAMLConfigLoader, logging_yaml_init, \
2+
logging_yaml_init_fh, logging_yaml_init_dict
3+
4+
__all__ = [ 'LoggingYAMLConfigLoader', 'EnvLoggingYAMLConfigLoader', 'logging_yaml_init', 'logging_yaml_init_fh',
5+
'logging_yaml_init_dict']
6+
7+
# vim: set ts=4 sw=4 expandtab:

logging_yamlconfig/configloader.py

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import os
2+
import re
3+
import sys
4+
import yaml
5+
import logging.config
6+
7+
8+
class YamlEnvLoader(yaml.Loader):
9+
"""
10+
an extended YAML loader which can get values from environment variables where _ENV is specified, e.g.
11+
12+
path: _ENV:PATH
13+
14+
will be replaced with the PATH environment variable, such that the resulting dictionary will be equivalent to:
15+
16+
{ "path": os.environ['PATH'] }
17+
18+
if the environment variable specified does not exist, the value will be None. you can otherwise specify a default
19+
value with the form:
20+
21+
_ENV:VARNAME:DEFAULT
22+
23+
NOTE: these _ENV statements will be replaced with either None, a string, an int, or a float depending solely on the
24+
content. this interpretation was added because,even though environment variables are always strings, you might want
25+
to pass arguments to a handler for configuration which expects a number (maxBytes on RotatingFileHandler, or port on
26+
SocketHandler for example).
27+
"""
28+
YAML_ENV_PATTERN = re.compile(r'^_ENV:(?P<envname>[^:]+)(:(?P<default>[^:]+))?$')
29+
30+
def __init__(self, stream):
31+
yaml.add_implicit_resolver('!pathex', self.YAML_ENV_PATTERN, Loader=self)
32+
yaml.add_constructor('!pathex', self._pathex_constructor, Loader=self)
33+
super(YamlEnvLoader, self).__init__(stream)
34+
35+
def _pathex_constructor(self, loader, node):
36+
value = self.construct_scalar(node)
37+
m = self.YAML_ENV_PATTERN.match(value)
38+
if m is None:
39+
return value
40+
else:
41+
envval = os.environ.get(m.group('envname'), m.group('default'))
42+
try:
43+
if int(envval) == float(envval):
44+
envval = int(envval)
45+
else:
46+
envval = float(envval)
47+
except (TypeError, ValueError):
48+
pass
49+
return envval
50+
51+
52+
class ConfigLocator(object):
53+
"""
54+
initialized with a search path and will return the first existent file on that path from the locate method. this is
55+
the interface expected by the locator member of the configloader objects below.
56+
"""
57+
DEFAULT_SEARCH_PATH = ['./pylogging.yaml',
58+
'/etc/pylogging.yaml']
59+
60+
def __init__(self, search_path=None, yaml_loader_class=yaml.BaseLoader):
61+
if search_path is None:
62+
search_path = self.DEFAULT_SEARCH_PATH
63+
64+
self.search_path = search_path
65+
66+
def locate(self):
67+
log_configs = filter(
68+
os.path.exists, map(os.path.abspath, self.search_path)
69+
)
70+
71+
if len(log_configs) > 0:
72+
return log_configs[0]
73+
else:
74+
print >>sys.stderr, 'ERROR: could not find log configuration file in search path [%s]' % \
75+
', '.join(log_configs)
76+
sys.exit(1)
77+
78+
79+
class LoggingYAMLConfigLoader(object):
80+
"""
81+
yaml configuration helper for the standard python logger. see the convenience methods named logging_yaml_init* for
82+
usage
83+
"""
84+
def __init__(self, locator=None, yaml_loader_class=yaml.BaseLoader):
85+
if locator is None:
86+
locator = ConfigLocator()
87+
88+
self.locator = locator
89+
self.yaml_loader_class = yaml_loader_class
90+
91+
def configure(self):
92+
configpath = self.locator.locate()
93+
94+
try:
95+
with open(configpath, 'r') as logfh:
96+
self.configure_fh(logfh)
97+
except Exception, e:
98+
print >>sys.stderr, "ERROR: could not open log file at \'%s\': %s" % (configpath, str(e),)
99+
sys.exit(1)
100+
101+
def configure_fh(self, fh):
102+
try:
103+
logconfig = yaml.load(fh, Loader=self.yaml_loader_class)
104+
except Exception, e:
105+
print >>sys.stderr, "ERROR: could not deserialize logging yaml: %s" % str(e)
106+
sys.exit(1)
107+
108+
self.configure_dict(logconfig)
109+
110+
def configure_dict(self, dct):
111+
try:
112+
logging.config.dictConfig(dct)
113+
except Exception, e:
114+
print >>sys.stderr, "ERROR: could not configure logger with logging yaml- %s" % str(e)
115+
116+
117+
class EnvLoggingYAMLConfigLoader(LoggingYAMLConfigLoader):
118+
def __init__(self, locator=None):
119+
super(EnvLoggingYAMLConfigLoader, self).__init__(locator, YamlEnvLoader)
120+
121+
122+
def logging_yaml_init(search_path=None, env_in_yaml=True):
123+
"""
124+
configures the logger using YAML from a search path, see
125+
https://docs.python.org/2/howto/logging.html#logging-advanced-tutorial for dictConfig format
126+
127+
if the first file found along the path does not contain a valid logging configuration in YAML the process will exit
128+
(fail closed).
129+
130+
:param search_path: optional, specifies a list of file paths to check for a yaml configuration file
131+
:param env_in_yaml: whether or not to extend the yaml loader with support for values of the form
132+
_ENV:<NAME>:<DEFAULT>
133+
:return: None
134+
"""
135+
# make config locator
136+
if search_path is not None:
137+
locator = ConfigLocator(search_path)
138+
else:
139+
locator = ConfigLocator()
140+
141+
# make loader
142+
if env_in_yaml:
143+
loader_class = EnvLoggingYAMLConfigLoader
144+
else:
145+
loader_class = LoggingYAMLConfigLoader
146+
147+
loader = loader_class(locator)
148+
149+
loader.configure()
150+
151+
152+
def logging_yaml_init_fh(fh, env_in_yaml=True):
153+
"""
154+
configures the logger using YAML from a filehandle, see
155+
https://docs.python.org/2/howto/logging.html#logging-advanced-tutorial for dictConfig format
156+
157+
if the file handle does not contain a valid logging configuration in YAML the process will call sys.exit (fail
158+
closed).
159+
160+
:param fh: an open filehandle containing yaml
161+
:param env_in_yaml: whether or not to extend the yaml loader with support for values of the form
162+
_ENV:<NAME>:<DEFAULT>
163+
:return: None
164+
"""
165+
if env_in_yaml:
166+
loader_class = EnvLoggingYAMLConfigLoader
167+
else:
168+
loader_class = LoggingYAMLConfigLoader
169+
170+
loader = loader_class()
171+
172+
loader.configure_fh(fh)
173+
174+
175+
def logging_yaml_init_dict(dct):
176+
"""
177+
configures the python standard logger using a dictionary, see
178+
https://docs.python.org/2/howto/logging.html#logging-advanced-tutorial for dictConfig format
179+
180+
if dictionary does not contain a valid logging configuration the process will call sys.exit (fail closed).
181+
182+
:param dct: dictionary containing logger configuration
183+
:return:
184+
"""
185+
loader = LoggingYAMLConfigLoader()
186+
187+
loader.configure_dict(dct)
188+
189+
190+
# vim: set ts=4 sw=4 expandtab:

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PyYAML>=3.11,<3.12

setup.py

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import os
2+
3+
from setuptools import setup, find_packages, findall
4+
5+
def get_version():
6+
with open(os.path.join(thisdir, 'VERSION'), 'r') as fh:
7+
return fh.readline().strip()
8+
9+
def get_requires(filename='requirements.txt'):
10+
requires = []
11+
with open(os.path.join(thisdir, filename), 'r') as fh:
12+
for line in fh.readlines():
13+
requires += [ line.strip() ]
14+
return requires
15+
16+
thisdir = os.path.dirname(os.path.realpath(__file__))
17+
18+
# parse version number from text file
19+
VERSION = get_version()
20+
21+
# find packages and binaries
22+
ALL_PACKAGES = find_packages()
23+
SCRIPTS = findall('bin')
24+
25+
# parse requirements from text files
26+
ALL_INSTALL_REQUIRES = get_requires()
27+
ALL_TEST_REQUIRES = get_requires('test_requirements.txt')
28+
ALL_SETUP_REQUIRES = get_requires('setup_requirements.txt')
29+
30+
setup(
31+
name='logging_yamlconfig',
32+
version=VERSION,
33+
description='Python dictconfig helpers for yaml config files',
34+
author='IBM',
35+
author_email='jdye@us.ibm.com',
36+
packages=ALL_PACKAGES,
37+
provides=ALL_PACKAGES,
38+
scripts=SCRIPTS,
39+
include_package_data=True,
40+
setup_requires=ALL_SETUP_REQUIRES,
41+
install_requires=ALL_INSTALL_REQUIRES,
42+
tests_require=ALL_TEST_REQUIRES,
43+
test_suite='nose.collector'
44+
)
45+
46+
# vim: set ts=4 sw=4 expandtab:

setup_requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
setuptools>=11.0,<13.0
2+
setuptools-git>=1.1,<1.2

0 commit comments

Comments
 (0)