1# Copyright 2017 The Abseil Authors.
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"""Module to convert log levels between Abseil Python, C++, and Python standard.
16
17This converter has to convert (best effort) between three different
18logging level schemes:
19  cpp      = The C++ logging level scheme used in Abseil C++.
20  absl     = The absl.logging level scheme used in Abseil Python.
21  standard = The python standard library logging level scheme.
22
23Here is a handy ascii chart for easy mental mapping.
24
25  LEVEL    | cpp |  absl  | standard |
26  ---------+-----+--------+----------+
27  DEBUG    |  0  |    1   |    10    |
28  INFO     |  0  |    0   |    20    |
29  WARNING  |  1  |   -1   |    30    |
30  ERROR    |  2  |   -2   |    40    |
31  CRITICAL |  3  |   -3   |    50    |
32  FATAL    |  3  |   -3   |    50    |
33
34Note: standard logging CRITICAL is mapped to absl/cpp FATAL.
35However, only CRITICAL logs from the absl logger (or absl.logging.fatal) will
36terminate the program. CRITICAL logs from non-absl loggers are treated as
37error logs with a message prefix "CRITICAL - ".
38
39Converting from standard to absl or cpp is a lossy conversion.
40Converting back to standard will lose granularity.  For this reason,
41users should always try to convert to standard, the richest
42representation, before manipulating the levels, and then only to cpp
43or absl if those level schemes are absolutely necessary.
44"""
45
46from __future__ import absolute_import
47from __future__ import division
48from __future__ import print_function
49
50import logging
51
52STANDARD_CRITICAL = logging.CRITICAL
53STANDARD_ERROR = logging.ERROR
54STANDARD_WARNING = logging.WARNING
55STANDARD_INFO = logging.INFO
56STANDARD_DEBUG = logging.DEBUG
57
58# These levels are also used to define the constants
59# FATAL, ERROR, WARNING, INFO, and DEBUG in the
60# absl.logging module.
61ABSL_FATAL = -3
62ABSL_ERROR = -2
63ABSL_WARNING = -1
64ABSL_WARN = -1  # Deprecated name.
65ABSL_INFO = 0
66ABSL_DEBUG = 1
67
68ABSL_LEVELS = {ABSL_FATAL: 'FATAL',
69               ABSL_ERROR: 'ERROR',
70               ABSL_WARNING: 'WARNING',
71               ABSL_INFO: 'INFO',
72               ABSL_DEBUG: 'DEBUG'}
73
74# Inverts the ABSL_LEVELS dictionary
75ABSL_NAMES = {'FATAL': ABSL_FATAL,
76              'ERROR': ABSL_ERROR,
77              'WARNING': ABSL_WARNING,
78              'WARN': ABSL_WARNING,  # Deprecated name.
79              'INFO': ABSL_INFO,
80              'DEBUG': ABSL_DEBUG}
81
82ABSL_TO_STANDARD = {ABSL_FATAL: STANDARD_CRITICAL,
83                    ABSL_ERROR: STANDARD_ERROR,
84                    ABSL_WARNING: STANDARD_WARNING,
85                    ABSL_INFO: STANDARD_INFO,
86                    ABSL_DEBUG: STANDARD_DEBUG}
87
88# Inverts the ABSL_TO_STANDARD
89STANDARD_TO_ABSL = dict((v, k) for (k, v) in ABSL_TO_STANDARD.items())
90
91
92def get_initial_for_level(level):
93  """Gets the initial that should start the log line for the given level.
94
95  It returns:
96  - 'I' when: level < STANDARD_WARNING.
97  - 'W' when: STANDARD_WARNING <= level < STANDARD_ERROR.
98  - 'E' when: STANDARD_ERROR <= level < STANDARD_CRITICAL.
99  - 'F' when: level >= STANDARD_CRITICAL.
100
101  Args:
102    level: int, a Python standard logging level.
103
104  Returns:
105    The first initial as it would be logged by the C++ logging module.
106  """
107  if level < STANDARD_WARNING:
108    return 'I'
109  elif level < STANDARD_ERROR:
110    return 'W'
111  elif level < STANDARD_CRITICAL:
112    return 'E'
113  else:
114    return 'F'
115
116
117def absl_to_cpp(level):
118  """Converts an absl log level to a cpp log level.
119
120  Args:
121    level: int, an absl.logging level.
122
123  Raises:
124    TypeError: Raised when level is not an integer.
125
126  Returns:
127    The corresponding integer level for use in Abseil C++.
128  """
129  if not isinstance(level, int):
130    raise TypeError('Expect an int level, found {}'.format(type(level)))
131  if level >= 0:
132    # C++ log levels must be >= 0
133    return 0
134  else:
135    return -level
136
137
138def absl_to_standard(level):
139  """Converts an integer level from the absl value to the standard value.
140
141  Args:
142    level: int, an absl.logging level.
143
144  Raises:
145    TypeError: Raised when level is not an integer.
146
147  Returns:
148    The corresponding integer level for use in standard logging.
149  """
150  if not isinstance(level, int):
151    raise TypeError('Expect an int level, found {}'.format(type(level)))
152  if level < ABSL_FATAL:
153    level = ABSL_FATAL
154  if level <= ABSL_DEBUG:
155    return ABSL_TO_STANDARD[level]
156  # Maps to vlog levels.
157  return STANDARD_DEBUG - level + 1
158
159
160def string_to_standard(level):
161  """Converts a string level to standard logging level value.
162
163  Args:
164    level: str, case-insensitive 'debug', 'info', 'warning', 'error', 'fatal'.
165
166  Returns:
167    The corresponding integer level for use in standard logging.
168  """
169  return absl_to_standard(ABSL_NAMES.get(level.upper()))
170
171
172def standard_to_absl(level):
173  """Converts an integer level from the standard value to the absl value.
174
175  Args:
176    level: int, a Python standard logging level.
177
178  Raises:
179    TypeError: Raised when level is not an integer.
180
181  Returns:
182    The corresponding integer level for use in absl logging.
183  """
184  if not isinstance(level, int):
185    raise TypeError('Expect an int level, found {}'.format(type(level)))
186  if level < 0:
187    level = 0
188  if level < STANDARD_DEBUG:
189    # Maps to vlog levels.
190    return STANDARD_DEBUG - level + 1
191  elif level < STANDARD_INFO:
192    return ABSL_DEBUG
193  elif level < STANDARD_WARNING:
194    return ABSL_INFO
195  elif level < STANDARD_ERROR:
196    return ABSL_WARNING
197  elif level < STANDARD_CRITICAL:
198    return ABSL_ERROR
199  else:
200    return ABSL_FATAL
201
202
203def standard_to_cpp(level):
204  """Converts an integer level from the standard value to the cpp value.
205
206  Args:
207    level: int, a Python standard logging level.
208
209  Raises:
210    TypeError: Raised when level is not an integer.
211
212  Returns:
213    The corresponding integer level for use in cpp logging.
214  """
215  return absl_to_cpp(standard_to_absl(level))
216