Skip to content

Commit

Permalink
Remove Google-specific code.
Browse files Browse the repository at this point in the history
Neither the .gcloudignore file nor the Google logger are useful outside
the Google Cloud environment.
  • Loading branch information
kfindeisen committed Nov 7, 2023
1 parent c5c1225 commit da747fd
Show file tree
Hide file tree
Showing 3 changed files with 2 additions and 223 deletions.
18 changes: 0 additions & 18 deletions .gcloudignore

This file was deleted.

63 changes: 1 addition & 62 deletions python/activator/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

__all__ = ["GCloudStructuredLogFormatter", "UsdfJsonFormatter", "setup_google_logger", "setup_usdf_logger",
"RecordFactoryContextAdapter"]
__all__ = ["UsdfJsonFormatter", "setup_usdf_logger", "RecordFactoryContextAdapter"]

import collections.abc
from contextlib import contextmanager
Expand Down Expand Up @@ -99,34 +98,6 @@ def _set_context_logger():
logging.setLogRecordFactory(RecordFactoryContextAdapter(logging.getLogRecordFactory()))


# TODO: replace with something more extensible, once we know what needs to
# vary besides the formatter (handler type?).
def setup_google_logger(labels=None):
"""Set global logging settings for prompt_prototype.
Calling this function makes `GCloudStructuredLogFormatter` the root
formatter and redirects all warnings to go through it.
Parameters
----------
labels : `dict` [`str`, `str`]
Any metadata that should be attached to all logs. See
``LogEntry.labels`` in Google Cloud REST API documentation.
Returns
-------
handler : `logging.Handler`
The handler used by the root logger.
"""
_set_context_logger()
log_handler = logging.StreamHandler()
log_handler.setFormatter(GCloudStructuredLogFormatter(labels))
logging.basicConfig(handlers=[log_handler])
_channel_all_to_pylog()
_set_lsst_logging_levels()
return log_handler


def setup_usdf_logger(labels=None):
"""Set global logging settings for prompt_prototype.
Expand All @@ -151,38 +122,6 @@ def setup_usdf_logger(labels=None):
return log_handler


class GCloudStructuredLogFormatter(logging.Formatter):
"""A formatter that can be parsed by the Google Cloud logging agent.
The formatter's output is a JSON-encoded message containing keywords
recognized by the logging agent.
Parameters
----------
labels : `dict` [`str`, `str`]
Any metadata that should be attached to the log. See ``LogEntry.labels``
in Google Cloud REST API documentation.
"""
def __init__(self, labels=None):
super().__init__()

if labels:
self._labels = labels
else:
self._labels = {}

def format(self, record):
# format updates record.message, but the full info is *only* in the return value.
msg = super().format(record)

entry = {
"severity": record.levelname,
"logging.googleapis.com/labels": self._labels | record.logging_context,
"message": msg,
}
return json.dumps(entry, default=_encode_json_extras)


class UsdfJsonFormatter(logging.Formatter):
"""A formatter that can be parsed by the Loki/Grafana system at USDF.
Expand Down
144 changes: 1 addition & 143 deletions tests/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

import pytest

from activator.logger import GCloudStructuredLogFormatter, UsdfJsonFormatter, \
from activator.logger import UsdfJsonFormatter, \
_parse_log_levels, RecordFactoryContextAdapter


Expand Down Expand Up @@ -59,148 +59,6 @@ def test_root_(self):
)


class GoogleFormatterTest(unittest.TestCase):
"""Test GCloudStructuredLogFormatter with fake log messages.
"""
def setUp(self):
super().setUp()

# Buffer for log output.
# Can't use assertLogs, because it inserts its own handler/formatter.
self.output = io.StringIO()
self.addCleanup(io.StringIO.close, self.output)

# GCloudStructuredLogFormatter assumes a logging_context field is present.
old_factory = logging.getLogRecordFactory()
self.addCleanup(logging.setLogRecordFactory, old_factory)
logging.setLogRecordFactory(RecordFactoryContextAdapter(old_factory))

log_handler = logging.StreamHandler(self.output)
log_handler.setFormatter(GCloudStructuredLogFormatter(
labels={"instrument": "NotACam"},
))
# Unique logger per test
self.log = logging.getLogger(self.id())
self.log.propagate = False
self.log.addHandler(log_handler)
self.log.setLevel(logging.DEBUG)

def _check_log(self, outputs, level, labels, texts):
"""Check that the log output is formatted correctly.
Parameters
----------
outputs : `list` [`str`]
A list of the formatted log messages.
level : `str`
The emitted log level.
labels : `dict` [`str`, `str`]
The labels attached to the log message.
texts : `list` [`str`]
The expected log messages.
"""
self.assertEqual(len(outputs), len(texts))
for output, text in zip(outputs, texts):
parsed = json.loads(output)
self.assertEqual(parsed["severity"], level)
self.assertEqual(parsed["message"], text)
self.assertEqual(parsed["logging.googleapis.com/labels"], labels)

def test_direct(self):
"""Test the translation of verbatim log messages.
"""
msg = "Consider a spherical cow..."
self.log.info(msg)
self._check_log(self.output.getvalue().splitlines(),
"INFO", {"instrument": "NotACam"},
[msg])

def test_args(self):
"""Test the translation of arg-based log messages.
"""
msg = "Consider a %s..."
args = "rotund bovine"
self.log.warning(msg, args)
self._check_log(self.output.getvalue().splitlines(),
"WARNING", {"instrument": "NotACam"},
[msg % args])

def test_quotes(self):
"""Test handling of messages containing single or double quotes.
"""
msgs = ["Consider a so-called 'spherical cow'.",
'Consider a so-called "spherical cow".',
]
for msg in msgs:
self.log.info(msg)
self._check_log(self.output.getvalue().splitlines(),
"INFO", {"instrument": "NotACam"},
msgs)

def test_multiline(self):
"""Test handling of messages that split across multiple lines.
"""
msg = """This is a multiline
message with internal line
breaks."""
self.log.error(msg)
self._check_log(self.output.getvalue().splitlines(),
"ERROR", {"instrument": "NotACam"},
[msg])

def test_exception(self):
"""Test that exception messages include the stack trace.
"""
try:
raise RuntimeError("I take exception to that!")
except RuntimeError as e:
self.log.exception(e)
shredded = self.output.getvalue().splitlines()
self.assertEqual(len(shredded), 1)
self.assertIn("Traceback (most recent call last)", shredded[0])

def test_context(self):
"""Test handling of messages that have free-form context.
"""
msg = "Consider a spherical exposure."
exposures = {1, 2, 3}
visit = 42
ratio = 3.5
group = "group A"
settings = {"option": True}
with logging.getLogRecordFactory().add_context(
exposures=exposures,
visit=visit,
ratio=ratio,
group=group,
settings=settings,
):
self.log.info(msg)
self._check_log(self.output.getvalue().splitlines(),
"INFO",
{"instrument": "NotACam",
"exposures": list(exposures),
"visit": visit,
"ratio": ratio,
"group": group,
"settings": settings,
},
[msg])

def test_side_effects(self):
"""Test that format still modifies exposure records in the same way
as Formatter.format.
"""
msg = "Consider a %s..."
args = "rotund bovine"
factory = logging.getLogRecordFactory()
record = factory(self.id(), logging.INFO, "file.py", 42, msg, args, None)
formatter = GCloudStructuredLogFormatter()
formatter.format(record)
# If format has no side effects, record.message does not exist.
self.assertEqual(record.message, msg % args)


class UsdfJsonFormatterTest(unittest.TestCase):
"""Test UsdfJsonFormatter with fake log messages.
"""
Expand Down

0 comments on commit da747fd

Please sign in to comment.