<commit_before># Copyright 2022 The etils Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Logging utils."""

import functools
import logging as py_logging
import sys

from absl import app
from absl import flags
from absl import logging as absl_logging
from etils import epy

FLAGS = flags.FLAGS


class TqdmStream:
  """File-object-like abstraction which wrap`tqdm.write`.

  By default using `logging.info` inside a `tqdm` scope creates visual
  artifacts. This simple wrapper uses `tqdm.write` instead.

  Usage:

  ```python
  logger = logging.getLogger()
  logger.addHandler(logging.StreamHandler(TqdmStream()))

  for _ in tqdm.tqdm(range(10)):
    logger.info('No visual artifacts')
  ```
  """

  def write(self, x: str) -> None:
    import tqdm  # pylint: disable=g-import-not-at-top  # pytype: disable=import-error

    tqdm.tqdm.write(x, end='')

  def flush(self) -> None:
    pass

  def close(self) -> None:
    pass


def _better_logging() -> None:
  """Modify Python logging (internal)."""
  # If `absl.run` was not called (e.g. open source `pytest` tests)
  if not FLAGS.is_parsed():
    return
  # On borg, use default logs
  if epy.is_borg():
    return
  # User explicitly set --logtostderr, use default behavior
  if FLAGS.logtostderr or FLAGS.alsologtostderr:
    return

  file_link = '{filename}:{lineno}'

  # Using cleaner, less verbose logger
  formatter = py_logging.Formatter(
      # Only display single letter level (`INFO`, `DEBUG`,... -> `I`, `D`,...)
      f'{{levelname:1.1}} {{asctime}} [{file_link}]: {{message}}',
      # Do not display date by default (take a lot of space and is almost
      # never important locally.
      # Also milliseconds feel overkill
      datefmt='%H:%M:%S',
      style='{',
  )

  absl_logging.use_python_logging(quiet=True)
  # TODO(epot): Let user control if flag explicitly set
  absl_logging.set_verbosity(absl_logging.INFO)
  python_handler = absl_logging.get_absl_handler().python_handler
  python_handler.setFormatter(formatter)

  if 'tqdm' in sys.modules:
    # Replace `sys.stderr` by the TQDM file
    # This avoid visual artifacts when `logging.info` is used inside
    # a `tqdm.tqdm` context.
    python_handler.setStream(TqdmStream())


def _terminal_link(uri: str, text: str) -> str:
  """Returns a clickable link on the terminal."""
  parameters = ''
  # OSC 8 ; params ; URI ST <name> OSC 8 ;; ST
  return f'\033]8;{parameters};{uri}\033\\{text}\033]8;;\033\\'


def _new_factory(old_factory, *args, **kwargs) -> py_logging.LogRecord:
  """Update the logs."""
  # TODO(epot): Add color ?
  record = old_factory(*args, **kwargs)
  return record


def better_logging():
  """Improve Python logging when running locally.

  * Display Python logs by default (even when user forgot `--logtostderr`),
    without being polluted by hundreds of C++ logs.
  * Cleaner minimal log format (e.g. `I 15:04:05 [main.py:24]:`)
  * Avoid visual artifacts between TQDM & `logging`
  * Clickable hyperlinks redirecting to code search (require terminal support)

  Usage:

  ```python
  if __name__ == '__main__':
    eapp.better_logging()
    app.run(main)
  ```

  Note this has only effect when user run locally and without `--logtostderr`.
  """
  app.call_after_init(_better_logging)
<commit_msg>Remove set_verbosity (absl already use INFO by default, after flags have been parsed)<commit_after># Copyright 2022 The etils Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Logging utils."""

import functools
import logging as py_logging
import sys

from absl import app
from absl import flags
from absl import logging as absl_logging
from etils import epy

FLAGS = flags.FLAGS


class TqdmStream:
  """File-object-like abstraction which wrap`tqdm.write`.

  By default using `logging.info` inside a `tqdm` scope creates visual
  artifacts. This simple wrapper uses `tqdm.write` instead.

  Usage:

  ```python
  logger = logging.getLogger()
  logger.addHandler(logging.StreamHandler(TqdmStream()))

  for _ in tqdm.tqdm(range(10)):
    logger.info('No visual artifacts')
  ```
  """

  def write(self, x: str) -> None:
    import tqdm  # pylint: disable=g-import-not-at-top  # pytype: disable=import-error

    tqdm.tqdm.write(x, end='')

  def flush(self) -> None:
    pass

  def close(self) -> None:
    pass


def _better_logging() -> None:
  """Modify Python logging (internal)."""
  # If `absl.run` was not called (e.g. open source `pytest` tests)
  if not FLAGS.is_parsed():
    return
  # On borg, use default logs
  if epy.is_borg():
    return
  # User explicitly set --logtostderr, use default behavior
  if FLAGS.logtostderr or FLAGS.alsologtostderr:
    return

  # Display logs by default
  absl_logging.use_python_logging(quiet=True)

  file_link = '{filename}:{lineno}'

  # Using cleaner, less verbose logger
  formatter = py_logging.Formatter(
      # Only display single letter level (`INFO`, `DEBUG`,... -> `I`, `D`,...)
      f'{{levelname:1.1}} {{asctime}} [{file_link}]: {{message}}',
      # Do not display date by default (take a lot of space and is almost
      # never important locally.
      # Also milliseconds feel overkill
      datefmt='%H:%M:%S',
      style='{',
  )

  python_handler = absl_logging.get_absl_handler().python_handler
  python_handler.setFormatter(formatter)

  if 'tqdm' in sys.modules:
    # Replace `sys.stderr` by the TQDM file
    # This avoid visual artifacts when `logging.info` is used inside
    # a `tqdm.tqdm` context.
    python_handler.setStream(TqdmStream())


def _terminal_link(uri: str, text: str) -> str:
  """Returns a clickable link on the terminal."""
  parameters = ''
  # OSC 8 ; params ; URI ST <name> OSC 8 ;; ST
  return f'\033]8;{parameters};{uri}\033\\{text}\033]8;;\033\\'


def _new_factory(old_factory, *args, **kwargs) -> py_logging.LogRecord:
  """Update the logs."""
  # TODO(epot): Add color ?
  record = old_factory(*args, **kwargs)
  return record


def better_logging():
  """Improve Python logging when running locally.

  * Display Python logs by default (even when user forgot `--logtostderr`),
    without being polluted by hundreds of C++ logs.
  * Cleaner minimal log format (e.g. `I 15:04:05 [main.py:24]:`)
  * Avoid visual artifacts between TQDM & `logging`
  * Clickable hyperlinks redirecting to code search (require terminal support)

  Usage:

  ```python
  if __name__ == '__main__':
    eapp.better_logging()
    app.run(main)
  ```

  Note this has only effect when user run locally and without `--logtostderr`.
  """
  app.call_after_init(_better_logging)

{'fraction_non_alphanumeric': 0.11730438236462333, 'fraction_numerical': 0.009532636038660135, 'mean_word_length': 3.736050156739812, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6510269', 'n_tokens_mistral': 2492, 'n_tokens_neox': 2351, 'n_words': 809}