We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] XGBoost autologging breaks when metric with @ is used
Thank you for submitting an issue. Please refer to our [issue policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md) for additional information about bug reports. For help with debugging your code, please refer to [Stack Overflow](https://stackoverflow.com/questions/tagged/mlflow).

**Please fill in this bug report template to ensure a timely and thorough response.**

### Willingness to contribute
The MLflow Community encourages bug fix contributions. Would you or another member of your organization be willing to contribute a fix for this bug to the MLflow code base?

- [x] Yes. I can contribute a fix for this bug independently.
- [ ] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.
- [ ] No. I cannot contribute a bug fix at this time.

### System information
- **Have I written custom code (as opposed to using a stock example script provided in MLflow)**: yes
- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: macOS 11.6.3
- **MLflow installed from (source or binary)**: binary
- **MLflow version (run ``mlflow --version``)**: 1.23.1
- **Python version**: 3.7.9
- **npm version, if running the dev UI**:
- **Exact command to reproduce**: `python xgb_metrics_bug.py`

### Describe the problem
XGBoost comes with a few metrics with @ in their name, e.g. `ndcg@2`, see [XGBoost parameters](https://xgboost.readthedocs.io/en/stable/parameter.html#learning-task-parameters). 

Expected behavior: MLflow should be able to autolog all built-in XGBoost metrics.

Actual behavior: when using a metric like this, the following warning is shown:

> WARNING mlflow.utils.autologging_utils: Encountered unexpected error during xgboost autologging: Invalid metric name: 'train-ndcg@2'. Names may only contain alphanumerics, underscores (_), dashes (-), periods (.), spaces ( ), and slashes (/).

but not only this metric is discarded, instead autologging completely breaks, i.e. other metrics, the model artifact and the feature importance plot also are not recorded.

![image](https://user-images.githubusercontent.com/1640908/154653567-1de2e15b-9ad7-4f8b-a9fe-5a7808602916.png)

I would propose to string-replace `@` with `_at_` in the XGBoost autologging metrics callback so that MLflow is compatible with all XGBoost built-in metrics.

### Code to reproduce issue

`xgb_metrics_bug.py`:

```python
import mlflow
import numpy as np
import xgboost as xgb

mlflow.set_experiment("xgb_autolog_metrics_bug")

for metrics in [["ndcg"], ["ndcg", "ndcg@2"]]:
    with mlflow.start_run():
        mlflow.xgboost.autolog()

        train_matrix = xgb.DMatrix(np.array([[0], [1]]), label=[1, 0])

        params = {
            "objective": "rank:pairwise",
            "eval_metric": metrics,
        }
        xgb.train(params, train_matrix, evals=[(train_matrix, "train")])
```


### Other info / logs
- I tried this with xgboost versions 1.3.3 and 1.5.1


### What component(s), interfaces, languages, and integrations does this bug affect?
Components 
- [ ] `area/artifacts`: Artifact stores and artifact logging
- [ ] `area/build`: Build and test infrastructure for MLflow
- [ ] `area/docs`: MLflow documentation pages
- [ ] `area/examples`: Example code
- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry
- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors
- [ ] `area/projects`: MLproject format, project running backends
- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs
- [ ] `area/server-infra`: MLflow Tracking server backend
- [x] `area/tracking`: Tracking Service, tracking client APIs, autologging

Interface 
- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server
- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models
- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry
- [ ] `area/windows`: Windows support

Language 
- [ ] `language/r`: R APIs and clients
- [ ] `language/java`: Java APIs and clients
- [ ] `language/new`: Proposals for new client languages

Integrations
- [ ] `integrations/azure`: Azure and Azure ML integrations
- [ ] `integrations/sagemaker`: SageMaker integrations
- [ ] `integrations/databricks`: Databricks integrations


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mlflow/xgboost/_autolog.py`
Content:
```
1 from packaging.version import Version
2 import xgboost
3 
4 # Suppress a false positive pylint error: https://github.com/PyCQA/pylint/issues/1630
5 # pylint: disable=unused-import
6 from mlflow.utils.autologging_utils import ExceptionSafeAbstractClass
7 
8 
9 def autolog_callback(env, metrics_logger, eval_results):
10     metrics_logger.record_metrics(dict(env.evaluation_result_list), env.iteration)
11     eval_results.append(dict(env.evaluation_result_list))
12 
13 
14 IS_TRAINING_CALLBACK_SUPPORTED = Version(xgboost.__version__.replace("SNAPSHOT", "dev")) >= Version(
15     "1.3.0"
16 )
17 
18 if IS_TRAINING_CALLBACK_SUPPORTED:
19 
20     class AutologCallback(
21         xgboost.callback.TrainingCallback,
22         metaclass=ExceptionSafeAbstractClass,
23     ):
24         def __init__(self, metrics_logger, eval_results):
25             # pylint: disable=super-init-not-called
26             self.metrics_logger = metrics_logger
27             self.eval_results = eval_results
28 
29         def after_iteration(self, model, epoch, evals_log):
30             """
31             Run after each iteration. Return True when training should stop.
32             """
33             # `evals_log` is a nested dict (type: Dict[str, Dict[str, List[float]]])
34             # that looks like this:
35             # {
36             #   "train": {
37             #     "auc": [0.5, 0.6, 0.7, ...],
38             #     ...
39             #   },
40             #   ...
41             # }
42             evaluation_result_dict = {}
43             for data_name, metric_dict in evals_log.items():
44                 for metric_name, metric_values_on_each_iter in metric_dict.items():
45                     key = "{}-{}".format(data_name, metric_name)
46                     # The last element in `metric_values_on_each_iter` corresponds to
47                     # the meric on the current iteration
48                     evaluation_result_dict[key] = metric_values_on_each_iter[-1]
49 
50             self.metrics_logger.record_metrics(evaluation_result_dict, epoch)
51             self.eval_results.append(evaluation_result_dict)
52 
53             # Return `False` to indicate training should not stop
54             return False
55
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mlflow/xgboost/_autolog.py b/mlflow/xgboost/_autolog.py
--- a/mlflow/xgboost/_autolog.py
+++ b/mlflow/xgboost/_autolog.py
@@ -6,9 +6,15 @@
 from mlflow.utils.autologging_utils import ExceptionSafeAbstractClass
 
 
+def _patch_metric_names(metric_dict):
+    # XGBoost provides some metrics with "@", e.g. "ndcg@3" that are not valid MLflow metric names
+    return {metric_name.replace("@", "_at_"): value for metric_name, value in metric_dict.items()}
+
+
 def autolog_callback(env, metrics_logger, eval_results):
-    metrics_logger.record_metrics(dict(env.evaluation_result_list), env.iteration)
-    eval_results.append(dict(env.evaluation_result_list))
+    metric_dict = _patch_metric_names(dict(env.evaluation_result_list))
+    metrics_logger.record_metrics(metric_dict, env.iteration)
+    eval_results.append(metric_dict)
 
 
 IS_TRAINING_CALLBACK_SUPPORTED = Version(xgboost.__version__.replace("SNAPSHOT", "dev")) >= Version(
@@ -41,10 +47,11 @@
             # }
             evaluation_result_dict = {}
             for data_name, metric_dict in evals_log.items():
+                metric_dict = _patch_metric_names(metric_dict)
                 for metric_name, metric_values_on_each_iter in metric_dict.items():
                     key = "{}-{}".format(data_name, metric_name)
                     # The last element in `metric_values_on_each_iter` corresponds to
-                    # the meric on the current iteration
+                    # the metric on the current iteration
                     evaluation_result_dict[key] = metric_values_on_each_iter[-1]
 
             self.metrics_logger.record_metrics(evaluation_result_dict, epoch)

{"golden_diff": "diff --git a/mlflow/xgboost/_autolog.py b/mlflow/xgboost/_autolog.py\n--- a/mlflow/xgboost/_autolog.py\n+++ b/mlflow/xgboost/_autolog.py\n@@ -6,9 +6,15 @@\n from mlflow.utils.autologging_utils import ExceptionSafeAbstractClass\n \n \n+def _patch_metric_names(metric_dict):\n+    # XGBoost provides some metrics with \"@\", e.g. \"ndcg@3\" that are not valid MLflow metric names\n+    return {metric_name.replace(\"@\", \"_at_\"): value for metric_name, value in metric_dict.items()}\n+\n+\n def autolog_callback(env, metrics_logger, eval_results):\n-    metrics_logger.record_metrics(dict(env.evaluation_result_list), env.iteration)\n-    eval_results.append(dict(env.evaluation_result_list))\n+    metric_dict = _patch_metric_names(dict(env.evaluation_result_list))\n+    metrics_logger.record_metrics(metric_dict, env.iteration)\n+    eval_results.append(metric_dict)\n \n \n IS_TRAINING_CALLBACK_SUPPORTED = Version(xgboost.__version__.replace(\"SNAPSHOT\", \"dev\")) >= Version(\n@@ -41,10 +47,11 @@\n             # }\n             evaluation_result_dict = {}\n             for data_name, metric_dict in evals_log.items():\n+                metric_dict = _patch_metric_names(metric_dict)\n                 for metric_name, metric_values_on_each_iter in metric_dict.items():\n                     key = \"{}-{}\".format(data_name, metric_name)\n                     # The last element in `metric_values_on_each_iter` corresponds to\n-                    # the meric on the current iteration\n+                    # the metric on the current iteration\n                     evaluation_result_dict[key] = metric_values_on_each_iter[-1]\n \n             self.metrics_logger.record_metrics(evaluation_result_dict, epoch)\n", "issue": "[BUG] XGBoost autologging breaks when metric with @ is used\nThank you for submitting an issue. Please refer to our [issue policy](https://www.github.com/mlflow/mlflow/blob/master/ISSUE_POLICY.md) for additional information about bug reports. For help with debugging your code, please refer to [Stack Overflow](https://stackoverflow.com/questions/tagged/mlflow).\r\n\r\n**Please fill in this bug report template to ensure a timely and thorough response.**\r\n\r\n### Willingness to contribute\r\nThe MLflow Community encourages bug fix contributions. Would you or another member of your organization be willing to contribute a fix for this bug to the MLflow code base?\r\n\r\n- [x] Yes. I can contribute a fix for this bug independently.\r\n- [ ] Yes. I would be willing to contribute a fix for this bug with guidance from the MLflow community.\r\n- [ ] No. I cannot contribute a bug fix at this time.\r\n\r\n### System information\r\n- **Have I written custom code (as opposed to using a stock example script provided in MLflow)**: yes\r\n- **OS Platform and Distribution (e.g., Linux Ubuntu 16.04)**: macOS 11.6.3\r\n- **MLflow installed from (source or binary)**: binary\r\n- **MLflow version (run ``mlflow --version``)**: 1.23.1\r\n- **Python version**: 3.7.9\r\n- **npm version, if running the dev UI**:\r\n- **Exact command to reproduce**: `python xgb_metrics_bug.py`\r\n\r\n### Describe the problem\r\nXGBoost comes with a few metrics with @ in their name, e.g. `ndcg@2`, see [XGBoost parameters](https://xgboost.readthedocs.io/en/stable/parameter.html#learning-task-parameters). \r\n\r\nExpected behavior: MLflow should be able to autolog all built-in XGBoost metrics.\r\n\r\nActual behavior: when using a metric like this, the following warning is shown:\r\n\r\n> WARNING mlflow.utils.autologging_utils: Encountered unexpected error during xgboost autologging: Invalid metric name: 'train-ndcg@2'. Names may only contain alphanumerics, underscores (_), dashes (-), periods (.), spaces ( ), and slashes (/).\r\n\r\nbut not only this metric is discarded, instead autologging completely breaks, i.e. other metrics, the model artifact and the feature importance plot also are not recorded.\r\n\r\n![image](https://user-images.githubusercontent.com/1640908/154653567-1de2e15b-9ad7-4f8b-a9fe-5a7808602916.png)\r\n\r\nI would propose to string-replace `@` with `_at_` in the XGBoost autologging metrics callback so that MLflow is compatible with all XGBoost built-in metrics.\r\n\r\n### Code to reproduce issue\r\n\r\n`xgb_metrics_bug.py`:\r\n\r\n```python\r\nimport mlflow\r\nimport numpy as np\r\nimport xgboost as xgb\r\n\r\nmlflow.set_experiment(\"xgb_autolog_metrics_bug\")\r\n\r\nfor metrics in [[\"ndcg\"], [\"ndcg\", \"ndcg@2\"]]:\r\n    with mlflow.start_run():\r\n        mlflow.xgboost.autolog()\r\n\r\n        train_matrix = xgb.DMatrix(np.array([[0], [1]]), label=[1, 0])\r\n\r\n        params = {\r\n            \"objective\": \"rank:pairwise\",\r\n            \"eval_metric\": metrics,\r\n        }\r\n        xgb.train(params, train_matrix, evals=[(train_matrix, \"train\")])\r\n```\r\n\r\n\r\n### Other info / logs\r\n- I tried this with xgboost versions 1.3.3 and 1.5.1\r\n\r\n\r\n### What component(s), interfaces, languages, and integrations does this bug affect?\r\nComponents \r\n- [ ] `area/artifacts`: Artifact stores and artifact logging\r\n- [ ] `area/build`: Build and test infrastructure for MLflow\r\n- [ ] `area/docs`: MLflow documentation pages\r\n- [ ] `area/examples`: Example code\r\n- [ ] `area/model-registry`: Model Registry service, APIs, and the fluent client calls for Model Registry\r\n- [ ] `area/models`: MLmodel format, model serialization/deserialization, flavors\r\n- [ ] `area/projects`: MLproject format, project running backends\r\n- [ ] `area/scoring`: MLflow Model server, model deployment tools, Spark UDFs\r\n- [ ] `area/server-infra`: MLflow Tracking server backend\r\n- [x] `area/tracking`: Tracking Service, tracking client APIs, autologging\r\n\r\nInterface \r\n- [ ] `area/uiux`: Front-end, user experience, plotting, JavaScript, JavaScript dev server\r\n- [ ] `area/docker`: Docker use across MLflow's components, such as MLflow Projects and MLflow Models\r\n- [ ] `area/sqlalchemy`: Use of SQLAlchemy in the Tracking Service or Model Registry\r\n- [ ] `area/windows`: Windows support\r\n\r\nLanguage \r\n- [ ] `language/r`: R APIs and clients\r\n- [ ] `language/java`: Java APIs and clients\r\n- [ ] `language/new`: Proposals for new client languages\r\n\r\nIntegrations\r\n- [ ] `integrations/azure`: Azure and Azure ML integrations\r\n- [ ] `integrations/sagemaker`: SageMaker integrations\r\n- [ ] `integrations/databricks`: Databricks integrations\r\n\n", "before_files": [{"content": "from packaging.version import Version\nimport xgboost\n\n# Suppress a false positive pylint error: https://github.com/PyCQA/pylint/issues/1630\n# pylint: disable=unused-import\nfrom mlflow.utils.autologging_utils import ExceptionSafeAbstractClass\n\n\ndef autolog_callback(env, metrics_logger, eval_results):\n    metrics_logger.record_metrics(dict(env.evaluation_result_list), env.iteration)\n    eval_results.append(dict(env.evaluation_result_list))\n\n\nIS_TRAINING_CALLBACK_SUPPORTED = Version(xgboost.__version__.replace(\"SNAPSHOT\", \"dev\")) >= Version(\n    \"1.3.0\"\n)\n\nif IS_TRAINING_CALLBACK_SUPPORTED:\n\n    class AutologCallback(\n        xgboost.callback.TrainingCallback,\n        metaclass=ExceptionSafeAbstractClass,\n    ):\n        def __init__(self, metrics_logger, eval_results):\n            # pylint: disable=super-init-not-called\n            self.metrics_logger = metrics_logger\n            self.eval_results = eval_results\n\n        def after_iteration(self, model, epoch, evals_log):\n            \"\"\"\n            Run after each iteration. Return True when training should stop.\n            \"\"\"\n            # `evals_log` is a nested dict (type: Dict[str, Dict[str, List[float]]])\n            # that looks like this:\n            # {\n            #   \"train\": {\n            #     \"auc\": [0.5, 0.6, 0.7, ...],\n            #     ...\n            #   },\n            #   ...\n            # }\n            evaluation_result_dict = {}\n            for data_name, metric_dict in evals_log.items():\n                for metric_name, metric_values_on_each_iter in metric_dict.items():\n                    key = \"{}-{}\".format(data_name, metric_name)\n                    # The last element in `metric_values_on_each_iter` corresponds to\n                    # the meric on the current iteration\n                    evaluation_result_dict[key] = metric_values_on_each_iter[-1]\n\n            self.metrics_logger.record_metrics(evaluation_result_dict, epoch)\n            self.eval_results.append(evaluation_result_dict)\n\n            # Return `False` to indicate training should not stop\n            return False\n", "path": "mlflow/xgboost/_autolog.py"}], "after_files": [{"content": "from packaging.version import Version\nimport xgboost\n\n# Suppress a false positive pylint error: https://github.com/PyCQA/pylint/issues/1630\n# pylint: disable=unused-import\nfrom mlflow.utils.autologging_utils import ExceptionSafeAbstractClass\n\n\ndef _patch_metric_names(metric_dict):\n    # XGBoost provides some metrics with \"@\", e.g. \"ndcg@3\" that are not valid MLflow metric names\n    return {metric_name.replace(\"@\", \"_at_\"): value for metric_name, value in metric_dict.items()}\n\n\ndef autolog_callback(env, metrics_logger, eval_results):\n    metric_dict = _patch_metric_names(dict(env.evaluation_result_list))\n    metrics_logger.record_metrics(metric_dict, env.iteration)\n    eval_results.append(metric_dict)\n\n\nIS_TRAINING_CALLBACK_SUPPORTED = Version(xgboost.__version__.replace(\"SNAPSHOT\", \"dev\")) >= Version(\n    \"1.3.0\"\n)\n\nif IS_TRAINING_CALLBACK_SUPPORTED:\n\n    class AutologCallback(\n        xgboost.callback.TrainingCallback,\n        metaclass=ExceptionSafeAbstractClass,\n    ):\n        def __init__(self, metrics_logger, eval_results):\n            # pylint: disable=super-init-not-called\n            self.metrics_logger = metrics_logger\n            self.eval_results = eval_results\n\n        def after_iteration(self, model, epoch, evals_log):\n            \"\"\"\n            Run after each iteration. Return True when training should stop.\n            \"\"\"\n            # `evals_log` is a nested dict (type: Dict[str, Dict[str, List[float]]])\n            # that looks like this:\n            # {\n            #   \"train\": {\n            #     \"auc\": [0.5, 0.6, 0.7, ...],\n            #     ...\n            #   },\n            #   ...\n            # }\n            evaluation_result_dict = {}\n            for data_name, metric_dict in evals_log.items():\n                metric_dict = _patch_metric_names(metric_dict)\n                for metric_name, metric_values_on_each_iter in metric_dict.items():\n                    key = \"{}-{}\".format(data_name, metric_name)\n                    # The last element in `metric_values_on_each_iter` corresponds to\n                    # the metric on the current iteration\n                    evaluation_result_dict[key] = metric_values_on_each_iter[-1]\n\n            self.metrics_logger.record_metrics(evaluation_result_dict, epoch)\n            self.eval_results.append(evaluation_result_dict)\n\n            # Return `False` to indicate training should not stop\n            return False\n", "path": "mlflow/xgboost/_autolog.py"}]}