code: def test_log_works_in_val_callback(tmpdir):
      """Tests that log can be called within callback."""
  
      class TestCallback(callbacks.Callback):
          count = 0
          choices = [False, True]
  
          # used to compute expected values
          logged_values = collections.defaultdict(list)
          call_counter = collections.Counter()
          logged_arguments = {}
  
          def make_logging(
              self, pl_module, func_name, on_steps, on_epochs, prob_bars
          ):
              self.call_counter.update([func_name])
  
              for idx, (on_step, on_epoch, prog_bar) in enumerate(
                  itertools.product(on_steps, on_epochs, prob_bars)
              ):
                  fx = f"{func_name}_{idx}"
                  if not on_step and not on_epoch:
                      with pytest.raises(
                          MisconfigurationException,
                          match="is not useful",
                      ):
                          pl_module.log(
                              fx,
                              self.count,
                              on_step=on_step,
                              on_epoch=on_epoch,
                          )
                      continue
                  pl_module.log(
                      fx,
                      self.count,
                      on_step=on_step,
                      on_epoch=on_epoch,
                      prog_bar=prog_bar,
                  )
                  self.logged_values[fx].append(self.count)
                  self.logged_arguments[fx] = {
                      "on_step": on_step,
                      "on_epoch": on_epoch,
                      "prog_bar": prog_bar,
                  }
                  self.count += 1
  
          def on_validation_start(self, _, pl_module):
              self.make_logging(
                  pl_module,
                  "on_validation_start",
                  on_steps=[False],
                  on_epochs=[True],
                  prob_bars=self.choices,
              )
  
          def on_validation_epoch_start(self, _, pl_module):
              self.make_logging(
                  pl_module,
                  "on_validation_epoch_start",
                  on_steps=[False],
                  on_epochs=[True],
                  prob_bars=self.choices,
              )
  
          def on_validation_batch_end(self, _, pl_module, *__):
              self.make_logging(
                  pl_module,
                  "on_validation_batch_end",
                  on_steps=self.choices,
                  on_epochs=self.choices,
                  prob_bars=self.choices,
              )
  
          def on_validation_epoch_end(self, _, pl_module):
              self.make_logging(
                  pl_module,
                  "on_validation_epoch_end",
                  on_steps=[False],
                  on_epochs=[True],
                  prob_bars=self.choices,
              )
  
      class TestModel(BoringModel):
          def validation_step(self, batch, batch_idx):
              loss = super().validation_step(batch, batch_idx)["x"]
              self.log("val_loss", loss)
  
      model = TestModel()
      cb = TestCallback()
      trainer = Trainer(
          default_root_dir=tmpdir,
          limit_train_batches=1,
          limit_val_batches=4,
          num_sanity_val_steps=0,
          max_epochs=1,
          callbacks=[cb],
      )
      trainer.fit(model)
  
      assert cb.call_counter == {
          "on_validation_batch_end": 4,
          "on_validation_start": 1,
          "on_validation_epoch_start": 1,
          "on_validation_epoch_end": 1,
      }
  
      def get_expected(on_epoch, values):
          reduction = np.mean if on_epoch else np.max
          return reduction(values)
  
      for fx, value in trainer.callback_metrics.items():
          actual = value.item()
          if fx not in cb.logged_arguments:
              continue
          on_epoch = cb.logged_arguments[fx]["on_epoch"]
          values = cb.logged_values[fx]
          expected = get_expected(on_epoch, values)
          assert actual == expected
  
      for fx, attrs in cb.logged_arguments.items():
          should_include = (
              attrs["prog_bar"] and attrs["on_step"] ^ attrs["on_epoch"]
          )
          is_included = fx in trainer.progress_bar_metrics
          assert is_included if should_include else not is_included
  
imports: import collections
  import itertools
  import os
  from contextlib import redirect_stdout
  from io import StringIO
  from unittest import mock
  from unittest.mock import ANY, call
  import numpy
  import pytest
  import torch
  from lightning.fabric.utilities.imports import _PYTHON_GREATER_EQUAL_3_8_0
  from lightning.pytorch import Trainer, callbacks
  from lightning.pytorch.callbacks.progress.rich_progress import _RICH_AVAILABLE
  from lightning.pytorch.demos.boring_classes import BoringModel, RandomDataset
  from lightning.pytorch.loggers import TensorBoardLogger
  from lightning.pytorch.loops import _EvaluationLoop
  from lightning.pytorch.trainer.states import RunningStage
  from lightning.pytorch.utilities.exceptions import MisconfigurationException
  from torch import Tensor
  from tests_pytorch.helpers.runif import RunIf
label_desc: Use image node_id 8 for calling a global function with example usage: test_log_works_in_val_callback(tmpdir) and returns: reduction