We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Better separation of audit log from privacyidea*
Hi,

python logging may be easily separated using the qualname. However, privacyidea uses the module/class names. Since they all start with "privacyidea.", it is not possible to log the audit to one place and all the rest to a different place (python logging cannot *exclude* qualnames).

To solve this, one could use a custom qualname for the privacyidea audit. I think here:
https://github.com/privacyidea/privacyidea/blob/ea7d9e53d42504288ba3909f7057924fe8d250b0/privacyidea/lib/auditmodules/loggeraudit.py#L62

Best regards,

Henning



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `privacyidea/lib/auditmodules/loggeraudit.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 #
3 #  2019-11-06 Cornelius Kölbel <cornelius.koelbel@netknights.it>
4 #             initial code for writing audit information to a file
5 #
6 # This code is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
8 # License as published by the Free Software Foundation; either
9 # version 3 of the License, or any later version.
10 #
11 # This code is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU AFFERO GENERAL PUBLIC LICENSE for more details.
15 #
16 # You should have received a copy of the GNU Affero General Public
17 # License along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 #
19 #
20 __doc__ = """The Logger Audit Module is used to write audit entries to the Python logging module.
21 
22 The Logger Audit Module is configured like this:
23 
24     PI_AUDIT_MODULE = "privacyidea.lib.auditmodules.loggeraudit"
25     PI_AUDIT_SERVERNAME = "your choice"
26 
27     PI_LOGCONFIG = "/etc/privacyidea/logging.cfg"
28 
29 The LoggerAudit Class uses the same PI logging config as you could use anyways.
30 To explicitly write audit logs, you need to add something like the following to
31 the logging.cfg
32 
33 Example:
34 
35 [handlers]
36 keys=file,audit
37 
38 [loggers]
39 keys=root,privacyidea,audit
40 
41 ...
42 
43 [logger_audit]
44 handlers=audit
45 qualname=privacyidea.lib.auditmodules.loggeraudit
46 level=INFO
47 
48 [handler_audit]
49 class=logging.handlers.RotatingFileHandler
50 backupCount=14
51 maxBytes=10000000
52 formatter=detail
53 level=INFO
54 args=('/var/log/privacyidea/audit.log',)
55 
56 """
57 
58 import logging
59 from privacyidea.lib.auditmodules.base import (Audit as AuditBase)
60 import datetime
61 
62 log = logging.getLogger(__name__)
63 
64 
65 class Audit(AuditBase):
66     """
67     This is the LoggerAudit module, which writes the audit entries
68     to the Python logging
69 
70     .. note:: This audit module does not provide a *Read* capability.
71     """
72 
73     def __init__(self, config=None):
74         super(Audit, self).__init__(config)
75         self.name = "loggeraudit"
76 
77     def finalize_log(self):
78         """
79         This method is used to log the data
80         e.g. write the data to a file.
81         """
82         self.audit_data["policies"] = ",".join(self.audit_data.get("policies", []))
83         self.audit_data["timestamp"] = datetime.datetime.utcnow()
84         log.info(u"{0!s}".format(self.audit_data))
85         self.audit_data = {}
86 
87 
88
```
--- 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/privacyidea/lib/auditmodules/loggeraudit.py b/privacyidea/lib/auditmodules/loggeraudit.py
--- a/privacyidea/lib/auditmodules/loggeraudit.py
+++ b/privacyidea/lib/auditmodules/loggeraudit.py
@@ -56,10 +56,9 @@
 """
 
 import logging
+import json
 from privacyidea.lib.auditmodules.base import (Audit as AuditBase)
-import datetime
-
-log = logging.getLogger(__name__)
+from datetime import datetime
 
 
 class Audit(AuditBase):
@@ -73,6 +72,8 @@
     def __init__(self, config=None):
         super(Audit, self).__init__(config)
         self.name = "loggeraudit"
+        self.qualname = self.config.get('PI_AUDIT_LOGGER_QUALNAME', __name__)
+        self.logger = logging.getLogger(self.qualname)
 
     def finalize_log(self):
         """
@@ -80,8 +81,6 @@
         e.g. write the data to a file.
         """
         self.audit_data["policies"] = ",".join(self.audit_data.get("policies", []))
-        self.audit_data["timestamp"] = datetime.datetime.utcnow()
-        log.info(u"{0!s}".format(self.audit_data))
+        self.audit_data["timestamp"] = datetime.utcnow().isoformat()
+        self.logger.info("{0!s}".format(json.dumps(self.audit_data, sort_keys=True)))
         self.audit_data = {}
-
-

{"golden_diff": "diff --git a/privacyidea/lib/auditmodules/loggeraudit.py b/privacyidea/lib/auditmodules/loggeraudit.py\n--- a/privacyidea/lib/auditmodules/loggeraudit.py\n+++ b/privacyidea/lib/auditmodules/loggeraudit.py\n@@ -56,10 +56,9 @@\n \"\"\"\n \n import logging\n+import json\n from privacyidea.lib.auditmodules.base import (Audit as AuditBase)\n-import datetime\n-\n-log = logging.getLogger(__name__)\n+from datetime import datetime\n \n \n class Audit(AuditBase):\n@@ -73,6 +72,8 @@\n     def __init__(self, config=None):\n         super(Audit, self).__init__(config)\n         self.name = \"loggeraudit\"\n+        self.qualname = self.config.get('PI_AUDIT_LOGGER_QUALNAME', __name__)\n+        self.logger = logging.getLogger(self.qualname)\n \n     def finalize_log(self):\n         \"\"\"\n@@ -80,8 +81,6 @@\n         e.g. write the data to a file.\n         \"\"\"\n         self.audit_data[\"policies\"] = \",\".join(self.audit_data.get(\"policies\", []))\n-        self.audit_data[\"timestamp\"] = datetime.datetime.utcnow()\n-        log.info(u\"{0!s}\".format(self.audit_data))\n+        self.audit_data[\"timestamp\"] = datetime.utcnow().isoformat()\n+        self.logger.info(\"{0!s}\".format(json.dumps(self.audit_data, sort_keys=True)))\n         self.audit_data = {}\n-\n-\n", "issue": "Better separation of audit log from privacyidea*\nHi,\r\n\r\npython logging may be easily separated using the qualname. However, privacyidea uses the module/class names. Since they all start with \"privacyidea.\", it is not possible to log the audit to one place and all the rest to a different place (python logging cannot *exclude* qualnames).\r\n\r\nTo solve this, one could use a custom qualname for the privacyidea audit. I think here:\r\nhttps://github.com/privacyidea/privacyidea/blob/ea7d9e53d42504288ba3909f7057924fe8d250b0/privacyidea/lib/auditmodules/loggeraudit.py#L62\r\n\r\nBest regards,\r\n\r\nHenning\r\n\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  2019-11-06 Cornelius K\u00f6lbel <cornelius.koelbel@netknights.it>\n#             initial code for writing audit information to a file\n#\n# This code is free software; you can redistribute it and/or\n# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE\n# License as published by the Free Software Foundation; either\n# version 3 of the License, or any later version.\n#\n# This code is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU AFFERO GENERAL PUBLIC LICENSE for more details.\n#\n# You should have received a copy of the GNU Affero General Public\n# License along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n#\n__doc__ = \"\"\"The Logger Audit Module is used to write audit entries to the Python logging module.\n\nThe Logger Audit Module is configured like this:\n\n    PI_AUDIT_MODULE = \"privacyidea.lib.auditmodules.loggeraudit\"\n    PI_AUDIT_SERVERNAME = \"your choice\"\n\n    PI_LOGCONFIG = \"/etc/privacyidea/logging.cfg\"\n\nThe LoggerAudit Class uses the same PI logging config as you could use anyways.\nTo explicitly write audit logs, you need to add something like the following to\nthe logging.cfg\n\nExample:\n\n[handlers]\nkeys=file,audit\n\n[loggers]\nkeys=root,privacyidea,audit\n\n...\n\n[logger_audit]\nhandlers=audit\nqualname=privacyidea.lib.auditmodules.loggeraudit\nlevel=INFO\n\n[handler_audit]\nclass=logging.handlers.RotatingFileHandler\nbackupCount=14\nmaxBytes=10000000\nformatter=detail\nlevel=INFO\nargs=('/var/log/privacyidea/audit.log',)\n\n\"\"\"\n\nimport logging\nfrom privacyidea.lib.auditmodules.base import (Audit as AuditBase)\nimport datetime\n\nlog = logging.getLogger(__name__)\n\n\nclass Audit(AuditBase):\n    \"\"\"\n    This is the LoggerAudit module, which writes the audit entries\n    to the Python logging\n\n    .. note:: This audit module does not provide a *Read* capability.\n    \"\"\"\n\n    def __init__(self, config=None):\n        super(Audit, self).__init__(config)\n        self.name = \"loggeraudit\"\n\n    def finalize_log(self):\n        \"\"\"\n        This method is used to log the data\n        e.g. write the data to a file.\n        \"\"\"\n        self.audit_data[\"policies\"] = \",\".join(self.audit_data.get(\"policies\", []))\n        self.audit_data[\"timestamp\"] = datetime.datetime.utcnow()\n        log.info(u\"{0!s}\".format(self.audit_data))\n        self.audit_data = {}\n\n\n", "path": "privacyidea/lib/auditmodules/loggeraudit.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n#\n#  2019-11-06 Cornelius K\u00f6lbel <cornelius.koelbel@netknights.it>\n#             initial code for writing audit information to a file\n#\n# This code is free software; you can redistribute it and/or\n# modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE\n# License as published by the Free Software Foundation; either\n# version 3 of the License, or any later version.\n#\n# This code is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranty of\n# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n# GNU AFFERO GENERAL PUBLIC LICENSE for more details.\n#\n# You should have received a copy of the GNU Affero General Public\n# License along with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n#\n__doc__ = \"\"\"The Logger Audit Module is used to write audit entries to the Python logging module.\n\nThe Logger Audit Module is configured like this:\n\n    PI_AUDIT_MODULE = \"privacyidea.lib.auditmodules.loggeraudit\"\n    PI_AUDIT_SERVERNAME = \"your choice\"\n\n    PI_LOGCONFIG = \"/etc/privacyidea/logging.cfg\"\n\nThe LoggerAudit Class uses the same PI logging config as you could use anyways.\nTo explicitly write audit logs, you need to add something like the following to\nthe logging.cfg\n\nExample:\n\n[handlers]\nkeys=file,audit\n\n[loggers]\nkeys=root,privacyidea,audit\n\n...\n\n[logger_audit]\nhandlers=audit\nqualname=privacyidea.lib.auditmodules.loggeraudit\nlevel=INFO\n\n[handler_audit]\nclass=logging.handlers.RotatingFileHandler\nbackupCount=14\nmaxBytes=10000000\nformatter=detail\nlevel=INFO\nargs=('/var/log/privacyidea/audit.log',)\n\n\"\"\"\n\nimport logging\nimport json\nfrom privacyidea.lib.auditmodules.base import (Audit as AuditBase)\nfrom datetime import datetime\n\n\nclass Audit(AuditBase):\n    \"\"\"\n    This is the LoggerAudit module, which writes the audit entries\n    to the Python logging\n\n    .. note:: This audit module does not provide a *Read* capability.\n    \"\"\"\n\n    def __init__(self, config=None):\n        super(Audit, self).__init__(config)\n        self.name = \"loggeraudit\"\n        self.qualname = self.config.get('PI_AUDIT_LOGGER_QUALNAME', __name__)\n        self.logger = logging.getLogger(self.qualname)\n\n    def finalize_log(self):\n        \"\"\"\n        This method is used to log the data\n        e.g. write the data to a file.\n        \"\"\"\n        self.audit_data[\"policies\"] = \",\".join(self.audit_data.get(\"policies\", []))\n        self.audit_data[\"timestamp\"] = datetime.utcnow().isoformat()\n        self.logger.info(\"{0!s}\".format(json.dumps(self.audit_data, sort_keys=True)))\n        self.audit_data = {}\n", "path": "privacyidea/lib/auditmodules/loggeraudit.py"}]}