File: //snap/google-cloud-cli/396/platform/bq/utils/bq_logging.py
#!/usr/bin/env python
"""Utility functions for BQ CLI logging."""
import datetime
import logging
import os
import sys
from typing import Optional, TextIO, Union
from absl import flags
from absl import logging as absl_logging
from googleapiclient import model
_UNIQUE_SUFFIX: str = ''
def GetUniqueSuffix() -> str:
global _UNIQUE_SUFFIX
if not _UNIQUE_SUFFIX:
_UNIQUE_SUFFIX = datetime.datetime.now().strftime('%z_%Y%m%d_%H%M%S.%f')
return _UNIQUE_SUFFIX
def GetLogDirectory(apilog: Optional[str] = None) -> Optional[str]:
"""Returns a directory to log to."""
if apilog and os.path.isdir(apilog):
full_path = apilog
# If this is a blaze test put the logs in a directory that will be seen in the
# artifact tab in the sponge UI.
elif 'TEST_UNDECLARED_OUTPUTS_DIR' in os.environ:
full_path = os.path.join(
os.environ['TEST_UNDECLARED_OUTPUTS_DIR'], 'bq_logs'
)
# If this is a Kokoro test put the logs in a directory that will be seen in
# the artifact tab in the sponge UI.
elif 'KOKORO_ARTIFACTS_DIR' in os.environ:
full_path = os.path.join(os.environ['KOKORO_ARTIFACTS_DIR'], 'bq_logs')
else:
return None
os.makedirs(full_path, exist_ok=True)
return full_path
def SaveStringToLogDirectoryIfAvailable(
file_prefix: str,
content: Union[str, bytes],
apilog: Optional[str] = None,
) -> None:
"""Saves string content to a file in the log directory."""
log_dir = GetLogDirectory(apilog)
if not log_dir:
return
if isinstance(content, bytes):
content = content.decode('utf-8')
filename = f'{file_prefix}_{GetUniqueSuffix()}.log'
path = os.path.join(log_dir, filename)
with open(path, 'w') as f:
f.write(content)
def _SetLogFile(logfile: TextIO):
absl_logging.use_python_logging(quiet=True)
absl_logging.get_absl_handler().python_handler.stream = logfile
def ConfigurePythonLogger(apilog: Optional[str] = None):
"""Sets up Python logger.
Applications can configure logging however they want, but this
captures one pattern of logging which seems useful when dealing with
a single command line option for determining logging.
Args:
apilog: To log to sys.stdout, specify '', '-', '1', 'true', or 'stdout'. To
log to sys.stderr, specify 'stderr'. To log to a file, specify the file
path. Specify None to disable logging.
"""
log_messages = []
if apilog is None:
apilog = GetLogDirectory()
log_messages.append(
'No logging set and we are in a test environment, logs will be in a'
' directory based on the test environment.'
)
if apilog is None:
# Effectively turn off logging.
logging.debug(
'There is no apilog flag so non-critical logging is disabled.'
)
logging.disable(logging.CRITICAL)
else:
if apilog in ('', '-', '1', 'true', 'stdout'):
_SetLogFile(sys.stdout)
elif apilog == 'stderr':
_SetLogFile(sys.stderr)
elif apilog:
if os.path.isdir(apilog):
log_messages.append(f'Logging to directory: {apilog}')
apilog = os.path.join(
apilog,
f'bq_cli_{GetUniqueSuffix()}.log',
)
_SetLogFile(open(apilog, 'a'))
else:
logging.basicConfig(level=logging.INFO)
# Turn on apiclient logging of http requests and responses. (Here
# we handle both the flags interface from apiclient < 1.2 and the
# module global in apiclient >= 1.2.)
if hasattr(flags.FLAGS, 'dump_request_response'):
flags.FLAGS.dump_request_response = True
else:
model.dump_request_response = True
for log in log_messages:
logging.info(log)
def EncodeForPrinting(o: object) -> str:
"""Safely encode an object as the encoding for sys.stdout."""
# Not all file objects provide an encoding attribute, so we make sure to
# handle the case where the attribute is completely absent.
encoding = getattr(sys.stdout, 'encoding', None) or 'ascii'
# We want to prevent conflicts in python2 between formatting
# a str type with a unicode type, e.g. b'abc%s' % (u'[unicode]',)
# where the byte type will be auto decoded as ascii thus causing
# an error.
# Thus we only want to encode the object if it's passed in as a
# unicode type and the unicode type is not a str type.
if isinstance(o, type('')) and not isinstance(o, str):
return o.encode(encoding, 'backslashreplace')
else:
return str(o)
def ConfigureLogging(apilog: Optional[str] = None):
try:
ConfigurePythonLogger(apilog)
except IOError as e:
if e.errno == 2:
print('Could not configure logging. %s: %s' % (e.strerror, e.filename))
sys.exit(1)
raise e