-
Notifications
You must be signed in to change notification settings - Fork 0
/
SimpleLog.py
1618 lines (1429 loc) · 67.8 KB
/
SimpleLog.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Usage
=====
.. code-block:: python
# import python 2.7.x 3.x.y compatible print function
from __future__ import print_function
# import Logger
from pysimplelog import Logger
# initialize
l=Logger("log test")
# change log file basename from simplelog to mylog
l.set_log_file_basename("mylog")
# change log file extension from .log to .pylog
l.set_log_file_extension("pylog")
# Add new log types.
l.add_log_type("super critical", name="SUPER CRITICAL", level=200, color='red', attributes=["bold","underline"])
l.add_log_type("wrong", name="info", color='magenta', attributes=["strike through"])
l.add_log_type("important", name="info", color='black', highlight="orange", attributes=["bold"])
# update error log type
l.update_log_type(logType='error', color='pink', attributes=['underline','bold'])
# print logger
print(l, end="\\n\\n")
# test logging
l.info("I am info, called using my shortcut method.")
l.log("info", "I am info, called using log method.")
l.warn("I am warn, called using my shortcut method.")
l.log("warn", "I am warn, called using log method.")
l.error("I am error, called using my shortcut method.")
l.log("error", "I am error, called using log method.")
l.critical("I am critical, called using my shortcut method.")
l.log("critical", "I am critical, called using log method.")
l.debug("I am debug, called using my shortcut method.")
l.log("debug", "I am debug, called using log method.")
l.log("super critical", "I am super critical, called using log method because I have no shortcut method.")
l.log("wrong", "I am wrong, called using log method because I have no shortcut method.")
l.log("important", "I am important, called using log method because I have no shortcut method.")
# print last logged messages
print("")
print("Last logged messages are:")
print("=========================")
print(l.lastLoggedMessage)
print(l.lastLoggedDebug)
print(l.lastLoggedInfo)
print(l.lastLoggedWarning)
print(l.lastLoggedError)
print(l.lastLoggedCritical)
# log data
print("")
print("Log random data and traceback stack:")
print("====================================")
l.info("Check out this data", data=list(range(10)))
print("")
# log error with traceback
import traceback
try:
1/range(10)
except Exception as err:
l.error('%s (is this python ?)'%err, tback=traceback.extract_stack())
output
======
.. raw:: html
<body>
<pre>
Logger (Version %AUTO_VERSION)
log type |log name |level |std flag |file flag |
---------------|---------------|----------|----------|----------|
wrong |info |0.0 |True |True |
debug |DEBUG |0.0 |True |True |
important |info |0.0 |True |True |
info |INFO |10.0 |True |True |
warn |WARNING |20.0 |True |True |
error |ERROR |30.0 |True |True |
critical |CRITICAL |100.0 |True |True |
super critical |SUPER CRITICAL |200.0 |True |True |
2018-09-07 16:07:58 - log test <INFO> I am info, called using my shortcut method.
2018-09-07 16:07:58 - log test <INFO> I am info, called using log method.
2018-09-07 16:07:58 - log test <WARNING> I am warn, called using my shortcut method.
2018-09-07 16:07:58 - log test <WARNING> I am warn, called using log method.
<font color="pink"><b><ins>2018-09-07 16:07:58 - log test <ERROR> I am error, called using my shortcut method.</ins></b></font>
<font color="pink"><b><ins>2018-09-07 16:07:58 - log test <ERROR> I am error, called using log method.</ins></b></font>
2018-09-07 16:07:58 - log test <CRITICAL> I am critical, called using my shortcut method.
2018-09-07 16:07:58 - log test <CRITICAL> I critical, called using log method.
2018-09-07 16:07:58 - log test <DEBUG> I am debug, called using my shortcut method.
2018-09-07 16:07:58 - log test <DEBUG> I am debug, called using log method.
<font color="red"><b><ins>2018-09-07 16:07:58 - log test <SUPER CRITICAL> I am super critical, called using log method because I have no shortcut method.</ins></b></font>
<font color="magenta"><del>2018-09-07 16:07:58 - log test <info> I am wrong, called using log method because I have no shortcut method.</del></font>
<style>mark{background-color: orange}</style><mark><b>2015-11-18 14:25:08 - log test <info> I am important, called using log method because I have no shortcut method.</b></mark>
Last logged messages are:
=========================
I am important, called using log method because I have no shortcut method.
I am debug, called using log method.
I am info, called using log method.
I am warn, called using log method.
I am error, called using log method.
I am critical, called using log method.
Log random data and traceback stack:
====================================
2018-09-07 16:07:58 - log test <INFO> Check out this data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<font color="pink"><b><ins>2015-11-18 14:25:08 - log test <ERROR> unsupported operand type(s) for /: 'int' and 'list' (is this python ?)</ins></b></font>
<font color="pink"><b><ins> File "<stdin>", line 4, in <module></ins></b></font>
</pre>
<body>
"""
# python standard distribution imports
import os, sys, copy, re, atexit
from datetime import datetime
# python version dependant imports
if sys.version_info >= (3, 0):
# This is python 3
str = str
long = int
unicode = str
bytes = bytes
basestring = str
else:
str = str
unicode = unicode
bytes = str
long = long
basestring = basestring
# import pysimplelog version
try:
from __pkginfo__ import __version__
except:
from .__pkginfo__ import __version__
# useful definitions
def _is_number(number):
if isinstance(number, (int, long, float, complex)):
return True
try:
float(number)
except:
return False
else:
return True
def _normalize_path(path):
if os.sep=='\\':
path = re.sub(r'([\\])\1+', r'\1', path).replace('\\','\\\\')
return path
class Logger(object):
"""
This is simplelog main Logger class definition.\n
A logging is constituted of a header a message and a footer.
In the current implementation the footer is empty and the header is as the following:\n
date time - loggerName <logTypeName>\n
In order to change any of the header or the footer, '_get_header' and '_get_footer'
methods must be overloaded.
When used in a python application, it is advisable to use Logger singleton
implementation and not Logger itself.
if no overloading is needed one can simply import the singleton as the following:
.. code-block:: python
from pysimplelog import SingleLogger as Logger
A new Logger instanciates with the following logType list (logTypes <NAME>: level)
* debug <DEBUG>: 0
* info <INFO>: 10
* warn <WARNING>: 20
* error <ERROR>: 30
* critical <CRITICAL>: 100
Recommended overloading implementation, this is how it could be done:
.. code-block:: python
from pysimplelog import SingleLogger as LOG
class Logger(LOG):
# *args and **kwargs can be replace by fixed arguments
def custom_init(self, *args, **kwargs):
# hereinafter any further instanciation can be coded
In case overloading __init__ is needed, this is how it could be done:
.. code-block:: python
from pysimplelog import SingleLogger as LOG
class Logger(LOG):
# custom_init will still be called in super(Logger, self).__init__(*args, **kwargs)
def __init__(self, *args, **kwargs):
if self._isInitialized: return
super(Logger, self).__init__(*args, **kwargs)
# hereinafter any further instanciation can be coded
:Parameters:
#. name (string): The logger name.
#. flush (boolean): Whether to always flush the logging streams.
#. logToStdout (boolean): Whether to log to the standard output stream.
#. stdout (None, stream): The standard output stream. If None, system
standard output will be set automatically. Otherwise any stream with
read and write methods can be passed
#. logToFile (boolean): Whether to log to to file.
#. logFile (None, string): the full log file path including directory
basename and extension. If this is given, all of logFileBasename and
logFileExtension will be discarded. logfile is equivalent to
logFileBasename.logFileExtension
#. logFileBasename (string): Logging file directory path and file
basename. A logging file full name is set as
logFileBasename.logFileExtension
#. logFileExtension (string): Logging file extension. A logging file
full name is set as logFileBasename.logFileExtension
#. logFileMaxSize (None, number): The maximum size in Megabytes
of a logging file. Once exceeded, another logging file as
logFileBasename_N.logFileExtension will be created.
Where N is an automatically incremented number. If None or a
negative number is given, the logging file will grow
indefinitely
#. logFileFirstNumber (None, integer): first log file number 'N' in
logFileBasename_N.logFileExtension. If None is given then
first log file will be logFileBasename.logFileExtension and ince
logFileMaxSize is reached second log file will be
logFileBasename_0.logFileExtension and so on and so forth.
If number is given it must be an integer >=0
#. logFileRoll (None, intger): If given, it sets the maximum number of
log files to write. Exceeding the number will result in deleting
previous ones. This also insures always increasing files numbering.
#. stdoutMinLevel(None, number): The minimum logging to system standard
output level. If None, standard output minimum level checking is left
out.
#. stdoutMaxLevel(None, number): The maximum logging to system standard
output level. If None, standard output maximum level checking is
left out.
#. fileMinLevel(None, number): The minimum logging to file level.
If None, file minimum level checking is left out.
#. fileMaxLevel(None, number): The maximum logging to file level.
If None, file maximum level checking is left out.
#. logTypes (None, dict): Used to create and update existing log types
upon initialization. Given dictionary keys are logType
(new or existing) and values can be None or a dictionary of kwargs
to call update_log_type upon. This argument will be called after
custom_init
#. timezone (None, str): Logging time timezone. If provided
pytz must be installed and it must be the timezone name. If not
provided, the machine default timezone will be used.
#. \*args: This is used to send non-keyworded variable length argument
list to custom initialize. args will be parsed and used in
custom_init method.
#. \**kwargs: This allows passing keyworded variable length of
arguments to custom_init method. kwargs can be anything other
than __init__ arguments.
"""
def __init__(self, name="logger", flush=True,
logToStdout=True, stdout=None,
logToFile=True, logFile=None,
logFileBasename="simplelog", logFileExtension="log",
logFileMaxSize=10, logFileFirstNumber=0, logFileRoll=None,
stdoutMinLevel=None, stdoutMaxLevel=None,
fileMinLevel=None, fileMaxLevel=None,
logTypes=None, timezone=None,*args, **kwargs):
# set last logged message
self.__lastLogged = {}
# instanciate file stream
self.__logFileStream = None
# set timezone
self.set_timezone(timezone)
# set name
self.set_name(name)
# set flush
self.set_flush(flush)
# set log to stdout
self.set_log_to_stdout_flag(logToStdout)
# set stdout
self.set_stdout(stdout)
# set log file roll
self.set_log_file_roll(logFileRoll)
# set log to file
self.set_log_to_file_flag(logToFile)
# set maximum logFile size
self.set_log_file_maximum_size(logFileMaxSize)
# set logFile first number
self.set_log_file_first_number(logFileFirstNumber)
# set logFile basename and extension
if logFile is not None:
self.set_log_file(logFile)
else:
self.__set_log_file_basename(logFileBasename)
self.set_log_file_extension(logFileExtension)
# initialize types parameters
self.__logTypeFileFlags = {}
self.__logTypeStdoutFlags = {}
self.__logTypeNames = {}
self.__logTypeLevels = {}
self.__logTypeFormat = {}
self.__logTypeColor = {}
self.__logTypeHighlight = {}
self.__logTypeAttributes = {}
# initialize forced levels
self.__forcedStdoutLevels = {}
self.__forcedFileLevels = {}
# set levels
self.__stdoutMinLevel = None
self.__stdoutMaxLevel = None
self.__fileMinLevel = None
self.__fileMaxLevel = None
# create log messages counter
self.__logMessagesCounter = {}
self.set_minimum_level(stdoutMinLevel, stdoutFlag=True, fileFlag=False)
self.set_maximum_level(stdoutMaxLevel, stdoutFlag=True, fileFlag=False)
self.set_minimum_level(fileMinLevel, stdoutFlag=False, fileFlag=True)
self.set_maximum_level(fileMaxLevel, stdoutFlag=False, fileFlag=True)
# create default types
self.add_log_type("debug", name="DEBUG", level=0, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None)
self.add_log_type("info", name="INFO", level=10, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None)
self.add_log_type("warn", name="WARNING", level=20, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None)
self.add_log_type("error", name="ERROR", level=30, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None)
self.add_log_type("critical", name="CRITICAL", level=100, stdoutFlag=None, fileFlag=None, color=None, highlight=None, attributes=None)
# custom initialize
self.custom_init( *args, **kwargs )
# add logTypes
if logTypes is not None:
assert isinstance(logTypes, dict),"logTypes must be None or a dictionary"
assert all([isinstance(lt, basestring) for lt in logTypes]), "logTypes dictionary keys must be strings"
assert all([isinstance(logTypes[lt], dict) for lt in logTypes if logTypes[lt] is not None]), "logTypes dictionary values must be all None or dictionaries"
for lt in logTypes:
ltv = logTypes[lt]
if ltv is None:
ltv = {}
if not self.is_logType(lt):
self.add_log_type(lt, **ltv)
elif len(ltv):
self.update_log_type(lt, **ltv)
# flush at python exit
atexit.register(self._flush_atexit_logfile)
def __str__(self):
# create version
string = self.__class__.__name__+" (Version "+str(__version__)+")"
# add general properties
#string += "\n - Log To Standard Output General Flag: %s"%(self.__logToStdout)
#string += "\n - Log To Standard Output Minimum Level: %s"%(self.__stdoutMinLevel)
#string += "\n - Log To Standard Output Maximum Level: %s"%(self.__stdoutMaxLevel)
#string += "\n - Log To File General Flag: %s"%(self.__logToFile)
#string += "\n - Log To File Minimum Level: %s"%(self.__fileMinLevel)
#string += "\n - Log To File Maximum Level: %s"%(self.__fileMaxLevel)
string += "\n - Log To Stdout: Flag (%s) - Min Level (%s) - Max Level (%s)"%(self.__logToStdout,self.__stdoutMinLevel,self.__stdoutMaxLevel)
string += "\n - Log To File: Flag (%s) - Min Level (%s) - Max Level (%s)"%(self.__logToFile,self.__fileMinLevel,self.__fileMaxLevel)
string += "\n File Size (%s) - First Number (%s) - Roll (%s)"%(self.__logFileMaxSize,self.__logFileFirstNumber,self.__logFileRoll)
string += "\n Current log file (%s)"%(self.__logFileName)
# add log types table
if not len(self.__logTypeNames):
string += "\nlog type |log name |level |std flag |file flag"
string += "\n----------|----------|----------|-----------|---------"
return string
# get maximum logType and logTypeZName and maxLogLevel
maxLogType = max( max([len(k)+1 for k in self.__logTypeNames]), len("log type "))
maxLogName = max( max([len(self.__logTypeNames[k])+1 for k in self.__logTypeNames]), len("log name "))
maxLogLevel = max( max([len(str(self.__logTypeLevels[k]))+1 for k in self.__logTypeLevels]), len("level "))
# create header
string += "\n"+ "log type".ljust(maxLogType) + "|" +\
"log name".ljust(maxLogName) + "|" +\
"level".ljust(maxLogLevel) + "|" +\
"std flag".ljust(10) + "|" +\
"file flag".ljust(10) + "|"
# create separator
string += "\n"+ "-"*maxLogType + "|" +\
"-"*maxLogName + "|" +\
"-"*maxLogLevel + "|" +\
"-"*10 + "|" +\
"-"*10 + "|"
# order from min level to max level
keys = sorted(self.__logTypeLevels, key=self.__logTypeLevels.__getitem__)
# append log types
for k in keys:
string += "\n"+ str(k).ljust(maxLogType) + "|" +\
str(self.__logTypeNames[k]).ljust(maxLogName) + "|" +\
str(self.__logTypeLevels[k]).ljust(maxLogLevel) + "|" +\
str(self.__logTypeStdoutFlags[k]).ljust(10) + "|" +\
str(self.logTypeFileFlags[k]).ljust(10) + "|"
return string
def __stream_format_allowed(self, stream):
"""
Check whether a stream allows formatting such as coloring.
Inspired from Python cookbook, #475186
"""
# curses isn't available on all platforms
try:
import curses as CURSES
except:
return False
try:
CURSES.setupterm()
return CURSES.tigetnum("colors") >= 2
except:
return False
def __get_stream_fonts_attributes(self, stream):
# foreground color
fgNames = ["black","red","green","orange","blue","magenta","cyan","grey"]
fgCode = [str(idx) for idx in range(30,38,1)]
fgNames.extend(["dark grey","light red","light green","yellow","light blue","pink","light cyan"])
fgCode.extend([str(idx) for idx in range(90,97,1)])
# background color
bgNames = ["black","red","green","orange","blue","magenta","cyan","grey"]
bgCode = [str(idx) for idx in range(40,48,1)]
# attributes
attrNames = ["bold","underline","blink","invisible","strike through"]
attrCode = ["1","4","5","8","9"]
# set reset
resetCode = "0"
# if attributing is not allowed
if not self.__stream_format_allowed(stream):
fgCode = ["" for idx in fgCode]
bgCode = ["" for idx in bgCode]
attrCode = ["" for idx in attrCode]
resetCode = ""
# set font attributes dict
color = dict( [(fgNames[idx],fgCode[idx]) for idx in range(len(fgCode))] )
highlight = dict( [(bgNames[idx],bgCode[idx]) for idx in range(len(bgCode))] )
attributes = dict( [(attrNames[idx],attrCode[idx]) for idx in range(len(attrCode))] )
return {"color":color, "highlight":highlight, "attributes":attributes, "reset":resetCode}
def _flush_atexit_logfile(self):
if self.__logFileStream is not None:
try:
self.__logFileStream.flush()
except:
pass
try:
os.fsync(self.__logFileStream.fileno())
except:
pass
self.__logFileStream.close()
@property
def lastLogged(self):
"""Get a dictionary of last logged messages.
Keys are log types and values are the the last messages."""
d = copy.deepcopy(self.__lastLogged)
d.pop(-1, None)
return d
@property
def lastLoggedMessage(self):
"""Get last logged message of any type. Retuns None if no message was logged."""
return self.__lastLogged.get(-1, None)
@property
def lastLoggedDebug(self):
"""Get last logged message of type 'debug'. Retuns None if no message was logged."""
return self.__lastLogged.get('debug', None)
@property
def lastLoggedInfo(self):
"""Get last logged message of type 'info'. Retuns None if no message was logged."""
return self.__lastLogged.get('info', None)
@property
def lastLoggedWarning(self):
"""Get last logged message of type 'warn'. Retuns None if no message was logged."""
return self.__lastLogged.get('warn', None)
@property
def lastLoggedError(self):
"""Get last logged message of type 'error'. Retuns None if no message was logged."""
return self.__lastLogged.get('error', None)
@property
def lastLoggedCritical(self):
"""Get last logged message of type 'critical'. Retuns None if no message was logged."""
return self.__lastLogged.get('critical', None)
@property
def flush(self):
"""Flush flag."""
return self.__flush
@property
def logTypes(self):
"""list of all defined log types."""
return list(self.__logTypeNames)
@property
def logLevels(self):
"""dictionary copy of all defined log types levels."""
return copy.deepcopy(self.__logTypeLevels)
@property
def logTypeFileFlags(self):
"""dictionary copy of all defined log types logging to a file flags."""
return copy.deepcopy(self.__logTypeFileFlags)
@property
def logTypeStdoutFlags(self):
"""dictionary copy of all defined log types logging to Standard output flags."""
return copy.deepcopy(self.__logTypeStdoutFlags)
@property
def stdoutMinLevel(self):
"""Standard output minimum logging level."""
return self.__stdoutMinLevel
@property
def stdoutMaxLevel(self):
"""Standard output maximum logging level."""
return self.__stdoutMaxLevel
@property
def fileMinLevel(self):
"""file logging minimum level."""
return self.__fileMinLevel
@property
def fileMaxLevel(self):
"""file logging maximum level."""
return self.__fileMaxLevel
@property
def forcedStdoutLevels(self):
"""dictionary copy of forced flags of logging to standard output."""
return copy.deepcopy(self.__forcedStdoutLevels)
@property
def forcedFileLevels(self):
"""dictionary copy of forced flags of logging to file."""
return copy.deepcopy(self.__forcedFileLevels)
@property
def logTypeNames(self):
"""dictionary copy of all defined log types logging names."""
return copy.deepcopy(self.__logTypeNames)
@property
def logTypeLevels(self):
"""dictionary copy of all defined log types levels showing when logging."""
return copy.deepcopy(self.__logTypeLevels)
@property
def logTypeFormats(self):
"""dictionary copy of all defined log types format showing when logging."""
return copy.deepcopy(self.__logTypeFormats)
@property
def name(self):
"""logger name."""
return self.__name
@property
def logToStdout(self):
"""log to stdout flag."""
return self.__logToStdout
@property
def logFileRoll(self):
"""Log file roll parameter."""
return self.__logFileRoll
@property
def logToFile(self):
"""log to file flag."""
return self.__logToFile
@property
def logFileName(self):
"""currently used log file name."""
return self.__logFileName
@property
def logFileBasename(self):
"""log file basename."""
return self.__logFileBasename
@property
def logFileExtension(self):
"""log file extension."""
return self.__logFileExtension
@property
def logFileMaxSize(self):
"""maximum allowed logfile size in megabytes."""
return self.__logFileMaxSize
@property
def logFileFirstNumber(self):
"""log file first number"""
return self.__logFileFirstNumber
@property
def timezone(self):
"""The timezone if given"""
timezone = self.__timezone
if timezone is not None:
timezone = timezone.zone
return timezone
@property
def _timezone(self):
return self.__timezone
@property
def logMessagesCounter(self):
"""Counter look up table for all logged messages that were
count constrainted"""
return self.__logMessagesCounter
def set_timezone(self, timezone):
"""
Set logging timezone
:Parameters:
#. timezone (None, str): Logging time timezone. If provided
pytz must be installed and it must be the timezone name. If not
provided, the machine default timezone will be used
"""
if timezone is not None:
assert isinstance(timezone, basestring), "timezone must be None or a string"
import pytz
timezone = pytz.timezone(timezone)
self.__timezone = timezone
def is_logType(self, logType):
"""
Get whether given logType is defined or not
:Parameters:
#. logType (string): A defined logging type.
:Result:
#. result (boolean): Whether given logType is defined or not
"""
try:
result = logType in self.__logTypeNames
except:
result = False
finally:
return result
def update(self, **kwargs):
"""Update logger general parameters using key value pairs.
Updatable parameters are name, flush, stdout, logToStdout, logFileRoll,
logToFile, logFileMaxSize, stdoutMinLevel, stdoutMaxLevel, fileMinLevel,
fileMaxLevel and logFileFirstNumber.
"""
# update name
if "name" in kwargs:
self.set_name(kwargs["name"])
# update flush
if "flush" in kwargs:
self.set_flush(kwargs["flush"])
# update stdout
if "stdout" in kwargs:
self.set_stdout(kwargs["stdout"])
# update logToStdout
if "logToStdout" in kwargs:
self.set_log_to_stdout_flag(kwargs["logToStdout"])
# update logFileRoll
if "logFileRoll" in kwargs:
self.set_log_file_roll(kwargs["logFileRoll"])
# update logToFile
if "logToFile" in kwargs:
self.set_log_to_file_flag(kwargs["logToFile"])
# update logFileMaxSize
if "logFileMaxSize" in kwargs:
self.set_log_file_maximum_size(kwargs["logFileMaxSize"])
# update logFileFirstNumber
if "logFileFirstNumber" in kwargs:
self.set_log_file_first_number(kwargs["logFileFirstNumber"])
# update stdoutMinLevel
if "stdoutMinLevel" in kwargs:
self.set_minimum_level(kwargs["stdoutMinLevel"], stdoutFlag=True, fileFlag=False)
# update stdoutMaxLevel
if "stdoutMaxLevel" in kwargs:
self.set_maximum_level(kwargs["stdoutMaxLevel"], stdoutFlag=True, fileFlag=False)
# update fileMinLevel
if "fileMinLevel" in kwargs:
self.set_minimum_level(kwargs["fileMinLevel"], stdoutFlag=False, fileFlag=True)
# update fileMaxLevel
if "fileMaxLevel" in kwargs:
self.set_maximum_level(kwargs["fileMaxLevel"], stdoutFlag=False, fileFlag=True)
# update logfile
if "logFile" in kwargs:
self.set_log_file(kwargs["logFile"])
@property
def parameters(self):
"""get a dictionary of logger general parameters. The same dictionary
can be used to update another logger instance using update method"""
return {"name":self.__name,
"flush":self.__flush,
"stdout":None if self.__stdout is sys.stdout else self.__stdout,
"logToStdout":self.__logToStdout,
"logFileRoll":self.__logFileRoll,
"logToFile":self.__logToFile,
"logFileMaxSize":self.__logFileMaxSize,
"logFileFirstNumber":self.__logFileFirstNumber,
"stdoutMinLevel":self.__stdoutMinLevel,
"stdoutMaxLevel":self.__stdoutMaxLevel,
"fileMinLevel":self.__fileMinLevel,
"fileMaxLevel":self.__fileMaxLevel,
"logFile":self.__logFileBasename+"."+self.__logFileExtension}
def custom_init(self, *args, **kwargs):
"""
Custom initialize abstract method. This method will be called at the end of
initialzation. This method needs to be overloaded to custom initialize
Logger instances.
:Parameters:
#. \*args (): This is used to send non-keyworded variable length argument
list to custom initialize.
#. \**kwargs (): This is keyworded variable length of arguments.
kwargs can be anything other than __init__ arguments.
"""
pass
def set_name(self, name):
"""
Set the logger name.
:Parameters:
#. name (string): The logger name.
"""
assert isinstance(name, basestring), "name must be a string"
self.__name = name
def set_flush(self, flush):
"""
Set the logger flush flag.
:Parameters:
#. flush (boolean): Whether to always flush the logging streams.
"""
assert isinstance(flush, bool), "flush must be boolean"
self.__flush = flush
def set_stdout(self, stream=None):
"""
Set the logger standard output stream.
:Parameters:
#. stdout (None, stream): The standard output stream. If None, system standard
output will be set automatically. Otherwise any stream with read and write
methods can be passed
"""
if stream is None:
self.__stdout = sys.stdout
else:
assert hasattr(stream, 'read') and hasattr(stream, 'write'), "stdout stream is not valid"
self.__stdout = stream
# set stdout colors
self.__stdoutFontFormat = self.__get_stream_fonts_attributes(stream)
def set_log_to_stdout_flag(self, logToStdout):
"""
Set the logging to the defined standard output flag. When set to False,
no logging to standard output will happen regardless of a logType
standard output flag.
:Parameters:
#. logToStdout (boolean): Whether to log to the standard output stream.
"""
assert isinstance(logToStdout, bool), "logToStdout must be boolean"
self.__logToStdout = logToStdout
def set_log_to_file_flag(self, logToFile):
"""
Set the logging to a file general flag. When set to False, no logging
to file will happen regardless of a logType file flag.
:Parameters:
#. logToFile (boolean): Whether to log to to file.
"""
assert isinstance(logToFile, bool), "logToFile must be boolean"
self.__logToFile = logToFile
def set_log_type_flags(self, logType, stdoutFlag, fileFlag):
"""
Set a defined log type flags.
:Parameters:
#. logType (string): A defined logging type.
#. stdoutFlag (boolean): Whether to log to the standard output stream.
#. fileFlag (boolean): Whether to log to to file.
"""
assert logType in self.__logTypeStdoutFlags, "logType '%s' not defined" %logType
assert isinstance(stdoutFlag, bool), "stdoutFlag must be boolean"
assert isinstance(fileFlag, bool), "fileFlag must be boolean"
self.__logTypeStdoutFlags[logType] = stdoutFlag
self.__logTypeFileFlags[logType] = fileFlag
def set_log_file_roll(self, logFileRoll):
"""
Set roll parameter to determine the maximum number of log files allowed.
Beyond the maximum, older will be removed.
:Parameters:
#. logFileRoll (None, intger): If given, it sets the maximum number of
log files to write. Exceeding the number will result in deleting
older files. This also insures always increasing files numbering.
Log files will be identified in increasing N order of
logFileBasename_N.logFileExtension pattern. Be careful setting
this parameter as old log files will be permanently deleted if
the number of files exceeds the value of logFileRoll
"""
if logFileRoll is not None:
assert isinstance(logFileRoll, int), "logFileRoll must be None or integer"
assert logFileRoll>0, "integer logFileRoll must be >0"
self.__logFileRoll = logFileRoll
def set_log_file(self, logfile):
"""
Set the log file full path including directory path basename and extension.
:Parameters:
#. logFile (string): the full log file path including basename and
extension. If this is given, all of logFileBasename and logFileExtension
will be discarded. logfile is equivalent to logFileBasename.logFileExtension
"""
assert isinstance(logfile, basestring), "logfile must be a string"
basename, extension = os.path.splitext(logfile)
self.__set_log_file_basename(logFileBasename=basename)
self.set_log_file_extension(logFileExtension=extension)
def set_log_file_extension(self, logFileExtension):
"""
Set the log file extension.
:Parameters:
#. logFileExtension (string): Logging file extension. A logging file full name is
set as logFileBasename.logFileExtension
"""
assert isinstance(logFileExtension, basestring), "logFileExtension must be a basestring"
assert len(logFileExtension), "logFileExtension can't be empty"
if logFileExtension[0] == ".":
logFileExtension = logFileExtension[1:]
assert len(logFileExtension), "logFileExtension is not allowed to be single dot"
if logFileExtension[-1] == ".":
logFileExtension = logFileExtension[:-1]
assert len(logFileExtension), "logFileExtension is not allowed to be double dots"
self.__logFileExtension = logFileExtension
# set log file name
self.__set_log_file_name()
def set_log_file_basename(self, logFileBasename):
"""
Set the log file basename.
:Parameters:
#. logFileBasename (string): Logging file directory path and file basename.
A logging file full name is set as logFileBasename.logFileExtension
"""
self.__set_log_file_basename(logFileBasename)
# set log file name
self.__set_log_file_name()
def __set_log_file_basename(self, logFileBasename):
assert isinstance(logFileBasename, basestring), "logFileBasename must be a basestring"
self.__logFileBasename = _normalize_path(logFileBasename)#logFileBasename
def __set_log_file_name(self):
"""Automatically set logFileName attribute"""
# ensure directory exists
dir, _ = os.path.split(self.__logFileBasename)
if len(dir) and not os.path.exists(dir):
os.makedirs(dir)
# get existing logfiles
numsLUT = {}
filesLUT = {}
ordered = []
if not len(dir) or os.path.isdir(dir):
listDir = os.listdir(dir) if len(dir) else os.listdir('.')
for f in listDir:
p = os.path.join(dir,f)
if not os.path.isfile(p):
continue
if re.match("^{bsn}(_\\d+)?.{ext}$".format(bsn=self.__logFileBasename, ext=self.__logFileExtension), p) is None:
continue
n = p.split(self.__logFileBasename)[1].split('.%s'%self.__logFileExtension)[0]
n = int(n[1:]) if len(n) else ''
assert n not in numsLUT , "filelog number is found in LUT shouldn't have happened. PLEASE REPORT BUG"
numsLUT[n] = p
filesLUT[p] = n
ordered = ([''] if '' in numsLUT else []) + sorted([n for n in numsLUT if isinstance(n, int)])
ordered = [numsLUT[n] for n in ordered]
# get last file number
if len(ordered):
number = filesLUT[ordered[-1]]
else:
number = self.__logFileFirstNumber
# limit number of log files to logFileRoll
if self.__logFileRoll is not None:
while len(ordered)>self.__logFileRoll:
os.remove(ordered.pop(0))
if len(ordered) == self.__logFileRoll and self.__logFileMaxSize is not None:
if os.stat(ordered[-1]).st_size/(1024.**2) >= self.__logFileMaxSize:
#if os.stat(ordered[-1]).st_size/1e6 >= self.__logFileMaxSize:
os.remove(ordered.pop(0))
if isinstance(number, int):
number = number + 1
# temporarily set self.__logFileName
if not isinstance(number, int):
self.__logFileName = self.__logFileBasename+"."+self.__logFileExtension
number = -1
else:
self.__logFileName = self.__logFileBasename+"_"+str(number)+"."+self.__logFileExtension
# check temporarily set logFileName file size
if self.__logFileMaxSize is not None:
while os.path.isfile(self.__logFileName):
if os.stat(self.__logFileName).st_size/(1024.**2) < self.__logFileMaxSize:
#if os.stat(self.__logFileName).st_size/1e6 < self.__logFileMaxSize:
break
number += 1
self.__logFileName = self.__logFileBasename+"_"+str(number)+"."+self.__logFileExtension
# create log file stream
if self.__logFileStream is not None:
try:
self.__logFileStream.close()
except:
pass
self.__logFileStream = None
def set_log_file_maximum_size(self, logFileMaxSize):
"""
Set the log file maximum size in megabytes
:Parameters:
#. logFileMaxSize (None, number): The maximum size in Megabytes
of a logging file. Once exceeded, another logging file as
logFileBasename_N.logFileExtension will be created.
Where N is an automatically incremented number. If None or a
negative number is given, the logging file will grow
indefinitely
"""
if logFileMaxSize is not None:
assert _is_number(logFileMaxSize), "logFileMaxSize must be a number"
logFileMaxSize = float(logFileMaxSize)
if logFileMaxSize <=0:
logFileMaxSize = None
#assert logFileMaxSize>=1, "logFileMaxSize minimum size is 1 megabytes"
self.__logFileMaxSize = logFileMaxSize
def set_log_file_first_number(self, logFileFirstNumber):
"""
Set log file first number
:Parameters:
#. logFileFirstNumber (None, integer): first log file number 'N' in
logFileBasename_N.logFileExtension. If None is given then
first log file will be logFileBasename.logFileExtension and ince
logFileMaxSize is reached second log file will be
logFileBasename_0.logFileExtension and so on and so forth.
If number is given it must be an integer >=0
"""
if logFileFirstNumber is not None:
assert isinstance(logFileFirstNumber, int), "logFileFirstNumber must be None or an integer"
assert logFileFirstNumber>=0, "logFileFirstNumber integer must be >=0"
self.__logFileFirstNumber = logFileFirstNumber
def set_minimum_level(self, level=0, stdoutFlag=True, fileFlag=True):
"""
Set the minimum logging level. All levels below the minimum will be ignored at logging.
:Parameters: