Skip to content

Commit 434688b

Browse files
Merge pull request #56 from Rajendra-R/metric-grouping
#55 implemented metric grouping feature and removed "_" key and modified the code structure
2 parents 6d5f33d + 38957a0 commit 434688b

3 files changed

Lines changed: 255 additions & 197 deletions

File tree

basescript/basescript.py

Lines changed: 14 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
from __future__ import absolute_import
22

33
import sys
4-
import uuid
5-
import atexit
6-
import logging
74
import argparse
85
import socket
9-
import structlog
10-
from datetime import timedelta, datetime
11-
from functools import wraps
12-
from threading import Thread
136

14-
from .log import LevelLoggerFactory, BoundLevelLogger, StdlibStructlogHandler, StderrConsoleRenderer, Stream
7+
from .log import init_logger
158
from .utils import Dummy # FIXME: delete this code and use deeputil.Dummy
169

1710
class BaseScript(object):
1811
DESC = 'Base script abstraction'
1912
LOG_LEVEL = 'INFO'
20-
21-
# stdlib to structlog handlers should be configured only once.
22-
_GLOBAL_LOG_CONFIGURED = False
13+
METRIC_GROUPING_INTERVAL = 1
2314

2415
def __init__(self, args=None):
2516
# argparse parser obj
@@ -37,7 +28,17 @@ def __init__(self, args=None):
3728
self.args = self.parser.parse_args(args=args)
3829

3930
self.hostname = socket.gethostname()
40-
self.log = self.init_logger()
31+
32+
self.log = init_logger(
33+
fmt=self.args.log_format,
34+
quiet=self.args.quiet,
35+
level=self.args.log_level,
36+
fpath=self.args.log_file,
37+
pre_hooks=self.define_log_pre_format_hooks(),
38+
post_hooks=self.define_log_post_format_hooks(),
39+
metric_grouping_interval=self.METRIC_GROUPING_INTERVAL
40+
).bind(name=self.args.name)
41+
4142
self.stats = Dummy()
4243

4344
args = { n: getattr(self.args, n) for n in vars(self.args) }
@@ -69,61 +70,6 @@ def start(self):
6970
def name(self):
7071
return '.'.join([x for x in (sys.argv[0].split('.')[0], self.args.name) if x])
7172

72-
def _structlog_uniqueid_processor(self, logger_class, log_method, event):
73-
''' Attach a unique id per event '''
74-
if 'id' not in event:
75-
event['id'] = '%s_%s' % (
76-
datetime.utcnow().strftime('%Y%m%dT%H%M%S'),
77-
uuid.uuid1().hex
78-
)
79-
return event
80-
81-
def _structlog_logtype_processor(self, logger_class, log_method, event):
82-
if 'type' not in event:
83-
event['type'] = 'log'
84-
return event
85-
86-
def define_log_processors(self):
87-
"""
88-
log processors that structlog executes before final rendering
89-
"""
90-
# these processors should accept logger, method_name and event_dict
91-
# and return a new dictionary which will be passed as event_dict to the next one.
92-
93-
processors = []
94-
95-
processors.extend([
96-
structlog.processors.TimeStamper(fmt="iso"),
97-
self._structlog_uniqueid_processor,
98-
self._structlog_logtype_processor,
99-
structlog.stdlib.PositionalArgumentsFormatter(),
100-
structlog.processors.StackInfoRenderer(),
101-
structlog.processors.format_exc_info,
102-
])
103-
104-
return processors
105-
106-
def define_log_renderer(self):
107-
"""
108-
the final log processor that structlog requires to render.
109-
"""
110-
# it must accept a logger, method_name and event_dict (just like processors)
111-
# but must return the rendered string, not a dictionary.
112-
# TODO tty logic
113-
if self.args.log_format == "json":
114-
return structlog.processors.JSONRenderer()
115-
116-
if self.args.log_format == "pretty":
117-
return structlog.dev.ConsoleRenderer()
118-
119-
if self.args.log_file is not None:
120-
return structlog.processors.JSONRenderer()
121-
122-
if sys.stderr.isatty() and not self.args.quiet:
123-
return structlog.dev.ConsoleRenderer()
124-
125-
return structlog.processors.JSONRenderer()
126-
12773
def define_log_pre_format_hooks(self):
12874
"""
12975
these hooks are called before the log has been rendered, but after
@@ -143,103 +89,6 @@ def define_log_post_format_hooks(self):
14389
# these hooks accept a 'msg' and do not return anything
14490
return []
14591

146-
def _configure_logger(self):
147-
"""
148-
configures a logger when required write to stderr or a file
149-
"""
150-
151-
# NOTE not thread safe. Multiple BaseScripts cannot be instantiated concurrently.
152-
level = getattr(logging, self.args.log_level.upper())
153-
154-
if self._GLOBAL_LOG_CONFIGURED:
155-
return
156-
157-
# TODO different processors for different basescripts ?
158-
# TODO dynamically inject processors ?
159-
160-
# since the hooks need to run through structlog, need to wrap them like processors
161-
def wrap_hook(fn):
162-
@wraps(fn)
163-
def processor(logger, method_name, event_dict):
164-
fn(event_dict)
165-
return event_dict
166-
167-
return processor
168-
169-
processors = self.define_log_processors()
170-
processors.extend(
171-
[ wrap_hook(h) for h in self.define_log_pre_format_hooks() ]
172-
)
173-
174-
log_renderer = self.define_log_renderer()
175-
stderr_required = (not self.args.quiet)
176-
pretty_to_stderr = (
177-
stderr_required
178-
and (
179-
self.args.log_format == "pretty"
180-
or (self.args.log_format is None and sys.stderr.isatty())
181-
)
182-
)
183-
184-
should_inject_pretty_renderer = (
185-
pretty_to_stderr
186-
and not isinstance(log_renderer, structlog.dev.ConsoleRenderer)
187-
)
188-
if should_inject_pretty_renderer:
189-
stderr_required = False
190-
processors.append(StderrConsoleRenderer())
191-
192-
processors.append(log_renderer)
193-
processors.extend(
194-
[ wrap_hook(h) for h in self.define_log_post_format_hooks() ]
195-
)
196-
197-
streams = []
198-
# we need to use a stream if we are writing to both file and stderr, and both are json
199-
if stderr_required:
200-
streams.append(sys.stderr)
201-
202-
if self.args.log_file is not None:
203-
# TODO handle creating a directory for this log file ?
204-
# TODO set mode and encoding appropriately
205-
streams.append(open(self.args.log_file, 'a'))
206-
207-
assert len(streams) != 0, "cannot configure logger for 0 streams"
208-
209-
stream = streams[0] if len(streams) == 1 else Stream(*streams)
210-
atexit.register(stream.close)
211-
212-
# a global level struct log config unless otherwise specified.
213-
structlog.configure(
214-
processors=processors,
215-
context_class=dict,
216-
logger_factory=LevelLoggerFactory(stream, level=level),
217-
wrapper_class=BoundLevelLogger,
218-
cache_logger_on_first_use=True,
219-
)
220-
221-
# TODO take care of removing other handlers
222-
stdlib_root_log = logging.getLogger()
223-
stdlib_root_log.addHandler(StdlibStructlogHandler())
224-
stdlib_root_log.setLevel(level)
225-
226-
self._GLOBAL_LOG_CONFIGURED = True
227-
228-
def init_logger(self):
229-
if self.args.quiet and self.args.log_file is None:
230-
# no need for a log - return a dummy
231-
return Dummy()
232-
233-
self._configure_logger()
234-
235-
# TODO bind relevant things to the basescript here ? name / hostname etc ?
236-
log = structlog.get_logger()
237-
level = getattr(logging, self.args.log_level.upper())
238-
log.setLevel(level)
239-
240-
# TODO functionality to change even the level of global stdlib logger.
241-
return log
242-
24392
def define_subcommands(self, subcommands):
24493
'''
24594
Define subcommands (as defined at https://docs.python.org/2/library/argparse.html#sub-commands)
@@ -257,7 +106,7 @@ def define_baseargs(self, parser):
257106
@parser is a parser object created using the `argparse` module.
258107
returns: None
259108
'''
260-
parser.add_argument('--name', default=None,
109+
parser.add_argument('--name', default=sys.argv[0],
261110
help='Name to identify this instance')
262111
parser.add_argument('--log-level', default=self.LOG_LEVEL,
263112
help='Logging level as picked from the logging module')

0 commit comments

Comments
 (0)