11from __future__ import absolute_import
22
33import sys
4- import uuid
5- import atexit
6- import logging
74import argparse
85import 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
158from .utils import Dummy # FIXME: delete this code and use deeputil.Dummy
169
1710class 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