prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  singleuser server version check spams the logs
  **Describe the bug**
  
  We have ~277 active single user servers in our deployment right now and on restart of the hub service we see this for each one:
  
  > Mar 4 09:20:45 hub-7bccd48cd5-mp4fk hub [W 2020-03-04 15:20:45.996 JupyterHub _version:56] jupyterhub version 1.2.0dev != jupyterhub-singleuser version 1.1.0. This could cause failure to authenticate and result in redirect loops! 
  
  My only complaint is that logging that per server is redundant and spams the logs. Can we just log that once per restart of the hub?
  
  **To Reproduce**
  
  Have the jupyterhub and jupyterhub-singleuser services at different minor versions.
  
  **Expected behavior**
  
  Just log the warning once since there is no user/server specific context in the message.
  
  **Compute Information**
   - 1.2.0dev - we're running with a custom build based on b4391d0f796864a5b01167701d95eafce3ad987e so that we can pick up the performance fix for issue #2928.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `jupyterhub/_version.py`
  Content:
  ```
  1 """JupyterHub version info"""
  2 # Copyright (c) Jupyter Development Team.
  3 # Distributed under the terms of the Modified BSD License.
  4 
  5 version_info = (
  6     1,
  7     2,
  8     0,
  9     # "",  # release (b1, rc1, or "" for final or dev)
  10     "dev",  # dev or nothing for beta/rc/stable releases
  11 )
  12 
  13 # pep 440 version: no dot before beta/rc, but before .dev
  14 # 0.1.0rc1
  15 # 0.1.0a1
  16 # 0.1.0b1.dev
  17 # 0.1.0.dev
  18 
  19 __version__ = ".".join(map(str, version_info[:3])) + ".".join(version_info[3:])
  20 
  21 
  22 def _check_version(hub_version, singleuser_version, log):
  23     """Compare Hub and single-user server versions"""
  24     if not hub_version:
  25         log.warning(
  26             "Hub has no version header, which means it is likely < 0.8. Expected %s",
  27             __version__,
  28         )
  29         return
  30 
  31     if not singleuser_version:
  32         log.warning(
  33             "Single-user server has no version header, which means it is likely < 0.8. Expected %s",
  34             __version__,
  35         )
  36         return
  37 
  38     # compare minor X.Y versions
  39     if hub_version != singleuser_version:
  40         from distutils.version import LooseVersion as V
  41 
  42         hub_major_minor = V(hub_version).version[:2]
  43         singleuser_major_minor = V(singleuser_version).version[:2]
  44         extra = ""
  45         if singleuser_major_minor == hub_major_minor:
  46             # patch-level mismatch or lower, log difference at debug-level
  47             # because this should be fine
  48             log_method = log.debug
  49         else:
  50             # log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc.
  51             log_method = log.warning
  52             extra = " This could cause failure to authenticate and result in redirect loops!"
  53         log_method(
  54             "jupyterhub version %s != jupyterhub-singleuser version %s." + extra,
  55             hub_version,
  56             singleuser_version,
  57         )
  58     else:
  59         log.debug(
  60             "jupyterhub and jupyterhub-singleuser both on version %s" % hub_version
  61         )
  62
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/jupyterhub/_version.py b/jupyterhub/_version.py
  --- a/jupyterhub/_version.py
  +++ b/jupyterhub/_version.py
  @@ -18,6 +18,15 @@
   
   __version__ = ".".join(map(str, version_info[:3])) + ".".join(version_info[3:])
   
  +# Singleton flag to only log the major/minor mismatch warning once per mismatch combo.
  +_version_mismatch_warning_logged = {}
  +
  +
  +def reset_globals():
  +    """Used to reset globals between test cases."""
  +    global _version_mismatch_warning_logged
  +    _version_mismatch_warning_logged = {}
  +
   
   def _check_version(hub_version, singleuser_version, log):
       """Compare Hub and single-user server versions"""
  @@ -42,19 +51,27 @@
           hub_major_minor = V(hub_version).version[:2]
           singleuser_major_minor = V(singleuser_version).version[:2]
           extra = ""
  +        do_log = True
           if singleuser_major_minor == hub_major_minor:
               # patch-level mismatch or lower, log difference at debug-level
               # because this should be fine
               log_method = log.debug
           else:
               # log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc.
  -            log_method = log.warning
  -            extra = " This could cause failure to authenticate and result in redirect loops!"
  -        log_method(
  -            "jupyterhub version %s != jupyterhub-singleuser version %s." + extra,
  -            hub_version,
  -            singleuser_version,
  -        )
  +            key = '%s-%s' % (hub_version, singleuser_version)
  +            global _version_mismatch_warning_logged
  +            if _version_mismatch_warning_logged.get(key):
  +                do_log = False  # We already logged this warning so don't log it again.
  +            else:
  +                log_method = log.warning
  +                extra = " This could cause failure to authenticate and result in redirect loops!"
  +                _version_mismatch_warning_logged[key] = True
  +        if do_log:
  +            log_method(
  +                "jupyterhub version %s != jupyterhub-singleuser version %s." + extra,
  +                hub_version,
  +                singleuser_version,
  +            )
       else:
           log.debug(
               "jupyterhub and jupyterhub-singleuser both on version %s" % hub_version
  
verification_info: {"golden_diff": "diff --git a/jupyterhub/_version.py b/jupyterhub/_version.py\n--- a/jupyterhub/_version.py\n+++ b/jupyterhub/_version.py\n@@ -18,6 +18,15 @@\n \n __version__ = \".\".join(map(str, version_info[:3])) + \".\".join(version_info[3:])\n \n+# Singleton flag to only log the major/minor mismatch warning once per mismatch combo.\n+_version_mismatch_warning_logged = {}\n+\n+\n+def reset_globals():\n+    \"\"\"Used to reset globals between test cases.\"\"\"\n+    global _version_mismatch_warning_logged\n+    _version_mismatch_warning_logged = {}\n+\n \n def _check_version(hub_version, singleuser_version, log):\n     \"\"\"Compare Hub and single-user server versions\"\"\"\n@@ -42,19 +51,27 @@\n         hub_major_minor = V(hub_version).version[:2]\n         singleuser_major_minor = V(singleuser_version).version[:2]\n         extra = \"\"\n+        do_log = True\n         if singleuser_major_minor == hub_major_minor:\n             # patch-level mismatch or lower, log difference at debug-level\n             # because this should be fine\n             log_method = log.debug\n         else:\n             # log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc.\n-            log_method = log.warning\n-            extra = \" This could cause failure to authenticate and result in redirect loops!\"\n-        log_method(\n-            \"jupyterhub version %s != jupyterhub-singleuser version %s.\" + extra,\n-            hub_version,\n-            singleuser_version,\n-        )\n+            key = '%s-%s' % (hub_version, singleuser_version)\n+            global _version_mismatch_warning_logged\n+            if _version_mismatch_warning_logged.get(key):\n+                do_log = False  # We already logged this warning so don't log it again.\n+            else:\n+                log_method = log.warning\n+                extra = \" This could cause failure to authenticate and result in redirect loops!\"\n+                _version_mismatch_warning_logged[key] = True\n+        if do_log:\n+            log_method(\n+                \"jupyterhub version %s != jupyterhub-singleuser version %s.\" + extra,\n+                hub_version,\n+                singleuser_version,\n+            )\n     else:\n         log.debug(\n             \"jupyterhub and jupyterhub-singleuser both on version %s\" % hub_version\n", "issue": "singleuser server version check spams the logs\n**Describe the bug**\r\n\r\nWe have ~277 active single user servers in our deployment right now and on restart of the hub service we see this for each one:\r\n\r\n> Mar 4 09:20:45 hub-7bccd48cd5-mp4fk hub [W 2020-03-04 15:20:45.996 JupyterHub _version:56] jupyterhub version 1.2.0dev != jupyterhub-singleuser version 1.1.0. This could cause failure to authenticate and result in redirect loops! \r\n\r\nMy only complaint is that logging that per server is redundant and spams the logs. Can we just log that once per restart of the hub?\r\n\r\n**To Reproduce**\r\n\r\nHave the jupyterhub and jupyterhub-singleuser services at different minor versions.\r\n\r\n**Expected behavior**\r\n\r\nJust log the warning once since there is no user/server specific context in the message.\r\n\r\n**Compute Information**\r\n - 1.2.0dev - we're running with a custom build based on b4391d0f796864a5b01167701d95eafce3ad987e so that we can pick up the performance fix for issue #2928.\n", "before_files": [{"content": "\"\"\"JupyterHub version info\"\"\"\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nversion_info = (\n    1,\n    2,\n    0,\n    # \"\",  # release (b1, rc1, or \"\" for final or dev)\n    \"dev\",  # dev or nothing for beta/rc/stable releases\n)\n\n# pep 440 version: no dot before beta/rc, but before .dev\n# 0.1.0rc1\n# 0.1.0a1\n# 0.1.0b1.dev\n# 0.1.0.dev\n\n__version__ = \".\".join(map(str, version_info[:3])) + \".\".join(version_info[3:])\n\n\ndef _check_version(hub_version, singleuser_version, log):\n    \"\"\"Compare Hub and single-user server versions\"\"\"\n    if not hub_version:\n        log.warning(\n            \"Hub has no version header, which means it is likely < 0.8. Expected %s\",\n            __version__,\n        )\n        return\n\n    if not singleuser_version:\n        log.warning(\n            \"Single-user server has no version header, which means it is likely < 0.8. Expected %s\",\n            __version__,\n        )\n        return\n\n    # compare minor X.Y versions\n    if hub_version != singleuser_version:\n        from distutils.version import LooseVersion as V\n\n        hub_major_minor = V(hub_version).version[:2]\n        singleuser_major_minor = V(singleuser_version).version[:2]\n        extra = \"\"\n        if singleuser_major_minor == hub_major_minor:\n            # patch-level mismatch or lower, log difference at debug-level\n            # because this should be fine\n            log_method = log.debug\n        else:\n            # log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc.\n            log_method = log.warning\n            extra = \" This could cause failure to authenticate and result in redirect loops!\"\n        log_method(\n            \"jupyterhub version %s != jupyterhub-singleuser version %s.\" + extra,\n            hub_version,\n            singleuser_version,\n        )\n    else:\n        log.debug(\n            \"jupyterhub and jupyterhub-singleuser both on version %s\" % hub_version\n        )\n", "path": "jupyterhub/_version.py"}], "after_files": [{"content": "\"\"\"JupyterHub version info\"\"\"\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n\nversion_info = (\n    1,\n    2,\n    0,\n    # \"\",  # release (b1, rc1, or \"\" for final or dev)\n    \"dev\",  # dev or nothing for beta/rc/stable releases\n)\n\n# pep 440 version: no dot before beta/rc, but before .dev\n# 0.1.0rc1\n# 0.1.0a1\n# 0.1.0b1.dev\n# 0.1.0.dev\n\n__version__ = \".\".join(map(str, version_info[:3])) + \".\".join(version_info[3:])\n\n# Singleton flag to only log the major/minor mismatch warning once per mismatch combo.\n_version_mismatch_warning_logged = {}\n\n\ndef reset_globals():\n    \"\"\"Used to reset globals between test cases.\"\"\"\n    global _version_mismatch_warning_logged\n    _version_mismatch_warning_logged = {}\n\n\ndef _check_version(hub_version, singleuser_version, log):\n    \"\"\"Compare Hub and single-user server versions\"\"\"\n    if not hub_version:\n        log.warning(\n            \"Hub has no version header, which means it is likely < 0.8. Expected %s\",\n            __version__,\n        )\n        return\n\n    if not singleuser_version:\n        log.warning(\n            \"Single-user server has no version header, which means it is likely < 0.8. Expected %s\",\n            __version__,\n        )\n        return\n\n    # compare minor X.Y versions\n    if hub_version != singleuser_version:\n        from distutils.version import LooseVersion as V\n\n        hub_major_minor = V(hub_version).version[:2]\n        singleuser_major_minor = V(singleuser_version).version[:2]\n        extra = \"\"\n        do_log = True\n        if singleuser_major_minor == hub_major_minor:\n            # patch-level mismatch or lower, log difference at debug-level\n            # because this should be fine\n            log_method = log.debug\n        else:\n            # log warning-level for more significant mismatch, such as 0.8 vs 0.9, etc.\n            key = '%s-%s' % (hub_version, singleuser_version)\n            global _version_mismatch_warning_logged\n            if _version_mismatch_warning_logged.get(key):\n                do_log = False  # We already logged this warning so don't log it again.\n            else:\n                log_method = log.warning\n                extra = \" This could cause failure to authenticate and result in redirect loops!\"\n                _version_mismatch_warning_logged[key] = True\n        if do_log:\n            log_method(\n                \"jupyterhub version %s != jupyterhub-singleuser version %s.\" + extra,\n                hub_version,\n                singleuser_version,\n            )\n    else:\n        log.debug(\n            \"jupyterhub and jupyterhub-singleuser both on version %s\" % hub_version\n        )\n", "path": "jupyterhub/_version.py"}]}