Repository: pytorch/ignite

Issue:
WandBLogger and TensorboardLogger have different APIs for logging audio
## 🚀 Feature

The following code doesn't work:

```
logger = WandBLogger()
logger.writer
```

This is how you would typically add audio with a tensorboard logger:
```
logger.writer.add_audio('mixture', x.t(), engine.state.epoch)
```

The workaround (similar to discussed in https://github.com/Lightning-AI/lightning/issues/7028) would be to use the underlying _wandb object:
```
logger._wandb.log({"validation": [wandb.Audio(x.t(), caption="mixture", sample_rate=44100)]})
logger._wandb.log({"validation": [wandb.Audio(x.t(), caption="vocals", sample_rate=44100)]})
```

Is there a proposal for a standardized media logging API?


Discussion:
@turian thanks for reporting.

I think you do not need such workaround as we propagate automatically attributes to `_wandb`:
https://github.com/pytorch/ignite/blob/a36e62dbc6f88e186c3ea183f0b498fc2bf5e14b/ignite/contrib/handlers/wandb_logger.py#L137-L138

So, I think the following should work (haven't explicitly checked)
```python
logger.log({"validation": [wandb.Audio(x.t(), caption="mixture", sample_rate=44100)]})
logger.log({"validation": [wandb.Audio(x.t(), caption="vocals", sample_rate=44100)]})
```

> WandBLogger and TensorboardLogger have different APIs for logging audio

I agree that in case of TensorboardLogger, user can access `logger.writer` object and use directly tensorboard API as in your example. Maybe, it would make sense to propagate attributes to `writer` similarly to what we are doing for WandBLogger. What do you think ? 


Hey @vfdev-5 I want to contribute to this issue, can you please give me a general idea what should I do to make the APIs consistent
@guptaaryan16 as it is described in https://github.com/pytorch/ignite/issues/2791#issuecomment-1336521118 we can add `__getattr__` to `TensorboardLogger` to propagate call to `writer` such that user does not need to call it:
```diff
- logger.writer.add_audio('mixture', x.t(), engine.state.epoch)
+ logger.add_audio('mixture', x.t(), engine.state.epoch)
```
Is it more clear now ?
thanks for the guidance , I will try to complete it as fast as possible


Resolving patch:
diff --git a/ignite/contrib/handlers/tensorboard_logger.py b/ignite/contrib/handlers/tensorboard_logger.py
--- a/ignite/contrib/handlers/tensorboard_logger.py
+++ b/ignite/contrib/handlers/tensorboard_logger.py
@@ -160,6 +160,9 @@ def __init__(self, *args: Any, **kwargs: Any):
 
         self.writer = SummaryWriter(*args, **kwargs)
 
+    def __getattr__(self, attr: Any) -> Any:
+        return getattr(self.writer, attr)
+
     def close(self) -> None:
         self.writer.close()
 


Test patch:
diff --git a/tests/ignite/contrib/handlers/test_tensorboard_logger.py b/tests/ignite/contrib/handlers/test_tensorboard_logger.py
--- a/tests/ignite/contrib/handlers/test_tensorboard_logger.py
+++ b/tests/ignite/contrib/handlers/test_tensorboard_logger.py
@@ -32,6 +32,17 @@ def test_optimizer_params_handler_wrong_setup():
         handler(mock_engine, mock_logger, Events.ITERATION_STARTED)
 
 
+def test_getattr_method():
+    # Create a mock SummaryWriter object
+    mock_writer = MagicMock()
+    # Assign the mock object to the writer attribute of a TensorboardLoggerinstance
+    logger = TensorboardLogger()
+    logger.writer = mock_writer
+    # Test that a method passed through the __getattr__ method calls thecorresponding method on the mock object
+    logger.add_scalar("loss", 0.5)
+    mock_writer.add_scalar.assert_called_once_with("loss", 0.5)
+
+
 def test_optimizer_params():
 
     optimizer = torch.optim.SGD([torch.tensor(0.0)], lr=0.01)