onaplogging: Docstrings, refactor, type hinting
[logging-analytics.git] / pylog / onaplogging / colorFormatter.py
1 # Copyright 2018 ke liang <lokyse@163.com>.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import os
16
17 from logging import Formatter, LogRecord
18 from deprecated import deprecated
19 from warnings import warn
20 from typing import Optional, Union, Dict
21
22 from onaplogging.utils.system import is_above_python_2_7, is_above_python_3_2
23 from onaplogging.utils.styles import (
24     ATTRIBUTES,
25     HIGHLIGHTS,
26     COLORS,
27
28     ATTRIBUTE_TAG,
29     HIGHLIGHT_TAG,
30     COLOR_TAG,
31
32     RESET,
33     FMT_STR
34 )
35
36
37 class BaseColorFormatter(Formatter):
38     """Text color formatter class.
39
40     Wraps the logging. Uses Git shell coloring codes.  Doesn't support Windows
41     CMD yet. If `fmt` is not suppied, the `style` is used. Eventually converts
42     a LogRecord object to "colored" text.
43
44     TODO:
45         Support for Windows CMD.
46     Extends:
47         logging.Formatter
48     Properties:
49         style       : '%', '{' or '$' formatting.
50         datefrmt    : ISO8601-like (or RFC 3339-like) format.
51     Args:
52         fmt         : human-readable format.                  Defaults to None.
53         datefmt     : ISO8601-like (or RFC 3339-like) format. Defaults to None.
54         colorfmt    : Color schemas for logging levels.       Defaults to None.
55         style       : '%', '{' or '$' formatting.             Defaults to '%'.
56     Methods:
57         format      : formats a LogRecord record.
58         _parseColor : selects colors based on a logging levels.
59     """
60
61     @property
62     def style(self):
63         # type: () -> str
64         return self.__style  # name mangling with __ to avoid accidents
65
66     @property
67     def colorfmt(self):
68         # type: () -> str
69         return self.__colorfmt
70
71     @style.setter
72     def style(self, value):
73         # type: (str) -> None
74         """Assign new style."""
75         self.__style = value
76
77     @colorfmt.setter
78     def colorfmt(self, value):
79         # type: (str) -> None
80         """Assign new color format."""
81         self.__colorfmt = value
82
83     def __init__(self,
84                  fmt=None,          # type: Optional[str]
85                  datefmt=None,      # type: Optional[str]
86                  colorfmt=None,     # type: Optional[Dict]
87                  style="%"):        # type: Optional[str]
88
89         if is_above_python_3_2():
90             super(BaseColorFormatter, self). \
91             __init__(fmt=fmt,  # noqa: E122
92                     datefmt=datefmt,
93                     style=style)
94
95         elif is_above_python_2_7():
96             super(BaseColorFormatter, self). \
97             __init__(fmt, datefmt)  # noqa: E122
98
99         else:
100             Formatter. \
101             __init__(self, fmt, datefmt)  # noqa: E122
102         self.style = style
103         self.colorfmt = colorfmt
104
105     def format(self, record):
106         """Text formatter.
107
108         Connects 2 methods. First it extract a level and a colors
109         assigned to  this level in  the BaseColorFormatter  class.
110         Second it applied the colors to the text.
111
112         Args:
113             record   : an instance of a logged event.
114         Returns:
115             str      : "colored" text (formatted text).
116         """
117
118         if is_above_python_2_7():
119             s = super(BaseColorFormatter, self). \
120                 format(record)
121
122         else:
123             s = Formatter. \
124                 format(self, record)
125
126         color, highlight, attribute = self._parse_color(record)
127
128         return apply_color(s, color, highlight, attrs=attribute)
129
130     def _parse_color(self, record):
131         # type: (LogRecord) -> (Optional[str], Optional[str], Optional[str])
132         """Color formatter based on the logging level.
133
134         This method formats the  record according to  its  level
135         and a color format  set for that level.  If the level is
136         not found, then this method will eventually return None.
137
138         Args:
139             record  : an instance of a logged event.
140         Returns:
141             str     : Colors.
142             str     : Hightlight tag.
143             str     : Attribute tag.
144         """
145         if  self.colorfmt and \
146             isinstance(self.colorfmt, dict):
147
148             level = record.levelname
149             colors = self.colorfmt.get(level, None)
150
151             if  colors is not None and \
152                 isinstance(colors, dict):
153                 return (colors.get(COLOR_TAG, None),        # noqa: E201
154                         colors.get(HIGHLIGHT_TAG, None),
155                         colors.get(ATTRIBUTE_TAG, None))    # noqa: E202
156         return None, None, None
157
158     @deprecated(reason="Will be removed. Use _parse_color(record) instead.")
159     def _parseColor(self, record):
160         """
161         Color based on logging level.
162         See method _parse_color(record).
163         """
164         return self._parse_color(record)
165
166
167 def apply_color(text,           # type: str
168                 color=None,     # type: Optional[str]
169                 on_color=None,  # type: Optional[str]
170                 attrs=None):    # type: Optional[Union[str, list]]
171     # type: (...) -> str
172     """Applies color codes to the text.
173
174     Args:
175         text      : text to be "colored" (formatted).
176         color     : Color in human-readable format. Defaults to None.
177         highlight : Hightlight color in human-readable format.
178                          Previously called "on_color". Defaults to None.
179         attrs     : Colors for attribute(s). Defaults to None.
180     Returns:
181         str       : "colored" text (formatted text).
182     """
183     warn("`on_color` will be replaced with `highlight`.", DeprecationWarning)
184     highlight = on_color  # replace the parameter and remove
185
186     if os.name in ('nt', 'ce'):
187         return text
188
189     if isinstance(attrs, str):
190         attrs = [attrs]
191
192     ansi_disabled = os.getenv('ANSI_COLORS_DISABLED', None)
193
194     if ansi_disabled is None:
195
196         if  color is not None and \
197             isinstance(color, str):
198             text = FMT_STR % (COLORS.get(color, 0), text)
199
200         if  highlight is not None and \
201             isinstance(highlight, str):
202             text = FMT_STR % (HIGHLIGHTS.get(highlight, 0), text)
203
204         if attrs is not None:
205             for attr in attrs:
206                 text = FMT_STR % (ATTRIBUTES.get(attr, 0), text)
207
208         text += RESET  # keep origin color for tail spaces
209
210     return text
211
212
213 @deprecated(reason="Will be removed. Call apply_color(...) instead.")
214 def colored(text, color=None, on_color=None, attrs=None):
215     """
216     Format text with color codes.
217     See method apply_color(text, color, on_color, attrs).
218     """
219     return apply_color(text, color, on_color, attrs)