We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
1.1.0: using a logging config causes exception
I've been using Borg 1.0.x with a [simple logging config](https://github.com/borgbackup/borg/files/1369192/logging.conf.txt) to get a logging behaviour suitable for cronjobs, that is: everything goes to the logfile, errors and warnings also go to stderr (and are sent via mail, by cron). Using the same logging config with Borg 1.1.0 causes an exception:

```
2017-10-09 06:05:09 [ERROR] Local Exception
2017-10-09 06:05:09 [ERROR] Traceback (most recent call last):
  File "borg/archiver.py", line 4024, in main
  File "borg/archiver.py", line 3952, in run
  File "borg/archiver.py", line 130, in wrapper
  File "borg/remote.py", line 562, in __init__
  File "borg/remote.py", line 699, in call
  File "borg/remote.py", line 841, in call_many
  File "borg/remote.py", line 989, in handle_remote_line
AttributeError: 'Logger' object has no attribute 'json'
```

When not using a logging config, `setup_logging` will set up two loggers, the second one explicitly named `borg` and having a custom `json` attribute (see logging.py, lines 97-99). While I could add a `borg` logger to my logging config there seems to be no way to add the required custom `json` attribute within the fileConfig format.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/borg/logger.py`
Content:
```
1 """logging facilities
2 
3 The way to use this is as follows:
4 
5 * each module declares its own logger, using:
6 
7     from .logger import create_logger
8     logger = create_logger()
9 
10 * then each module uses logger.info/warning/debug/etc according to the
11   level it believes is appropriate:
12 
13     logger.debug('debugging info for developers or power users')
14     logger.info('normal, informational output')
15     logger.warning('warn about a non-fatal error or sth else')
16     logger.error('a fatal error')
17 
18   ... and so on. see the `logging documentation
19   <https://docs.python.org/3/howto/logging.html#when-to-use-logging>`_
20   for more information
21 
22 * console interaction happens on stderr, that includes interactive
23   reporting functions like `help`, `info` and `list`
24 
25 * ...except ``input()`` is special, because we can't control the
26   stream it is using, unfortunately. we assume that it won't clutter
27   stdout, because interaction would be broken then anyways
28 
29 * what is output on INFO level is additionally controlled by commandline
30   flags
31 """
32 
33 import inspect
34 import json
35 import logging
36 import logging.config
37 import logging.handlers  # needed for handlers defined there being configurable in logging.conf file
38 import os
39 import warnings
40 
41 configured = False
42 
43 # use something like this to ignore warnings:
44 # warnings.filterwarnings('ignore', r'... regex for warning message to ignore ...')
45 
46 
47 def _log_warning(message, category, filename, lineno, file=None, line=None):
48     # for warnings, we just want to use the logging system, not stderr or other files
49     msg = "{0}:{1}: {2}: {3}".format(filename, lineno, category.__name__, message)
50     logger = create_logger(__name__)
51     # Note: the warning will look like coming from here,
52     # but msg contains info about where it really comes from
53     logger.warning(msg)
54 
55 
56 def setup_logging(stream=None, conf_fname=None, env_var='BORG_LOGGING_CONF', level='info', is_serve=False, json=False):
57     """setup logging module according to the arguments provided
58 
59     if conf_fname is given (or the config file name can be determined via
60     the env_var, if given): load this logging configuration.
61 
62     otherwise, set up a stream handler logger on stderr (by default, if no
63     stream is provided).
64 
65     if is_serve == True, we configure a special log format as expected by
66     the borg client log message interceptor.
67     """
68     global configured
69     err_msg = None
70     if env_var:
71         conf_fname = os.environ.get(env_var, conf_fname)
72     if conf_fname:
73         try:
74             conf_fname = os.path.abspath(conf_fname)
75             # we open the conf file here to be able to give a reasonable
76             # error message in case of failure (if we give the filename to
77             # fileConfig(), it silently ignores unreadable files and gives
78             # unhelpful error msgs like "No section: 'formatters'"):
79             with open(conf_fname) as f:
80                 logging.config.fileConfig(f)
81             configured = True
82             logger = logging.getLogger(__name__)
83             logger.debug('using logging configuration read from "{0}"'.format(conf_fname))
84             warnings.showwarning = _log_warning
85             return None
86         except Exception as err:  # XXX be more precise
87             err_msg = str(err)
88     # if we did not / not successfully load a logging configuration, fallback to this:
89     logger = logging.getLogger('')
90     handler = logging.StreamHandler(stream)
91     if is_serve and not json:
92         fmt = '$LOG %(levelname)s %(name)s Remote: %(message)s'
93     else:
94         fmt = '%(message)s'
95     formatter = JsonFormatter(fmt) if json else logging.Formatter(fmt)
96     handler.setFormatter(formatter)
97     borg_logger = logging.getLogger('borg')
98     borg_logger.formatter = formatter
99     borg_logger.json = json
100     if configured and logger.handlers:
101         # The RepositoryServer can call setup_logging a second time to adjust the output
102         # mode from text-ish is_serve to json is_serve.
103         # Thus, remove the previously installed handler, if any.
104         logger.handlers[0].close()
105         logger.handlers.clear()
106     logger.addHandler(handler)
107     logger.setLevel(level.upper())
108     configured = True
109     logger = logging.getLogger(__name__)
110     if err_msg:
111         logger.warning('setup_logging for "{0}" failed with "{1}".'.format(conf_fname, err_msg))
112     logger.debug('using builtin fallback logging configuration')
113     warnings.showwarning = _log_warning
114     return handler
115 
116 
117 def find_parent_module():
118     """find the name of a the first module calling this module
119 
120     if we cannot find it, we return the current module's name
121     (__name__) instead.
122     """
123     try:
124         frame = inspect.currentframe().f_back
125         module = inspect.getmodule(frame)
126         while module is None or module.__name__ == __name__:
127             frame = frame.f_back
128             module = inspect.getmodule(frame)
129         return module.__name__
130     except AttributeError:
131         # somehow we failed to find our module
132         # return the logger module name by default
133         return __name__
134 
135 
136 def create_logger(name=None):
137     """lazily create a Logger object with the proper path, which is returned by
138     find_parent_module() by default, or is provided via the commandline
139 
140     this is really a shortcut for:
141 
142         logger = logging.getLogger(__name__)
143 
144     we use it to avoid errors and provide a more standard API.
145 
146     We must create the logger lazily, because this is usually called from
147     module level (and thus executed at import time - BEFORE setup_logging()
148     was called). By doing it lazily we can do the setup first, we just have to
149     be careful not to call any logger methods before the setup_logging() call.
150     If you try, you'll get an exception.
151     """
152     class LazyLogger:
153         def __init__(self, name=None):
154             self.__name = name or find_parent_module()
155             self.__real_logger = None
156 
157         @property
158         def __logger(self):
159             if self.__real_logger is None:
160                 if not configured:
161                     raise Exception("tried to call a logger before setup_logging() was called")
162                 self.__real_logger = logging.getLogger(self.__name)
163                 if self.__name.startswith('borg.debug.') and self.__real_logger.level == logging.NOTSET:
164                     self.__real_logger.setLevel('WARNING')
165             return self.__real_logger
166 
167         def getChild(self, suffix):
168             return LazyLogger(self.__name + '.' + suffix)
169 
170         def setLevel(self, *args, **kw):
171             return self.__logger.setLevel(*args, **kw)
172 
173         def log(self, *args, **kw):
174             if 'msgid' in kw:
175                 kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')
176             return self.__logger.log(*args, **kw)
177 
178         def exception(self, *args, **kw):
179             if 'msgid' in kw:
180                 kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')
181             return self.__logger.exception(*args, **kw)
182 
183         def debug(self, *args, **kw):
184             if 'msgid' in kw:
185                 kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')
186             return self.__logger.debug(*args, **kw)
187 
188         def info(self, *args, **kw):
189             if 'msgid' in kw:
190                 kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')
191             return self.__logger.info(*args, **kw)
192 
193         def warning(self, *args, **kw):
194             if 'msgid' in kw:
195                 kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')
196             return self.__logger.warning(*args, **kw)
197 
198         def error(self, *args, **kw):
199             if 'msgid' in kw:
200                 kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')
201             return self.__logger.error(*args, **kw)
202 
203         def critical(self, *args, **kw):
204             if 'msgid' in kw:
205                 kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')
206             return self.__logger.critical(*args, **kw)
207 
208     return LazyLogger(name)
209 
210 
211 class JsonFormatter(logging.Formatter):
212     RECORD_ATTRIBUTES = (
213         'levelname',
214         'name',
215         'message',
216         # msgid is an attribute we made up in Borg to expose a non-changing handle for log messages
217         'msgid',
218     )
219 
220     # Other attributes that are not very useful but do exist:
221     # processName, process, relativeCreated, stack_info, thread, threadName
222     # msg == message
223     # *args* are the unformatted arguments passed to the logger function, not useful now,
224     # become useful if sanitized properly (must be JSON serializable) in the code +
225     # fixed message IDs are assigned.
226     # exc_info, exc_text are generally uninteresting because the message will have that
227 
228     def format(self, record):
229         super().format(record)
230         data = {
231             'type': 'log_message',
232             'time': record.created,
233             'message': '',
234             'levelname': 'CRITICAL',
235         }
236         for attr in self.RECORD_ATTRIBUTES:
237             value = getattr(record, attr, None)
238             if value:
239                 data[attr] = value
240         return json.dumps(data)
241
```
--- 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/src/borg/logger.py b/src/borg/logger.py
--- a/src/borg/logger.py
+++ b/src/borg/logger.py
@@ -80,6 +80,8 @@
                 logging.config.fileConfig(f)
             configured = True
             logger = logging.getLogger(__name__)
+            borg_logger = logging.getLogger('borg')
+            borg_logger.json = json
             logger.debug('using logging configuration read from "{0}"'.format(conf_fname))
             warnings.showwarning = _log_warning
             return None

{"golden_diff": "diff --git a/src/borg/logger.py b/src/borg/logger.py\n--- a/src/borg/logger.py\n+++ b/src/borg/logger.py\n@@ -80,6 +80,8 @@\n                 logging.config.fileConfig(f)\n             configured = True\n             logger = logging.getLogger(__name__)\n+            borg_logger = logging.getLogger('borg')\n+            borg_logger.json = json\n             logger.debug('using logging configuration read from \"{0}\"'.format(conf_fname))\n             warnings.showwarning = _log_warning\n             return None\n", "issue": "1.1.0: using a logging config causes exception\nI've been using Borg 1.0.x with a [simple logging config](https://github.com/borgbackup/borg/files/1369192/logging.conf.txt) to get a logging behaviour suitable for cronjobs, that is: everything goes to the logfile, errors and warnings also go to stderr (and are sent via mail, by cron). Using the same logging config with Borg 1.1.0 causes an exception:\r\n\r\n```\r\n2017-10-09 06:05:09 [ERROR] Local Exception\r\n2017-10-09 06:05:09 [ERROR] Traceback (most recent call last):\r\n  File \"borg/archiver.py\", line 4024, in main\r\n  File \"borg/archiver.py\", line 3952, in run\r\n  File \"borg/archiver.py\", line 130, in wrapper\r\n  File \"borg/remote.py\", line 562, in __init__\r\n  File \"borg/remote.py\", line 699, in call\r\n  File \"borg/remote.py\", line 841, in call_many\r\n  File \"borg/remote.py\", line 989, in handle_remote_line\r\nAttributeError: 'Logger' object has no attribute 'json'\r\n```\r\n\r\nWhen not using a logging config, `setup_logging` will set up two loggers, the second one explicitly named `borg` and having a custom `json` attribute (see logging.py, lines 97-99). While I could add a `borg` logger to my logging config there seems to be no way to add the required custom `json` attribute within the fileConfig format.\n", "before_files": [{"content": "\"\"\"logging facilities\n\nThe way to use this is as follows:\n\n* each module declares its own logger, using:\n\n    from .logger import create_logger\n    logger = create_logger()\n\n* then each module uses logger.info/warning/debug/etc according to the\n  level it believes is appropriate:\n\n    logger.debug('debugging info for developers or power users')\n    logger.info('normal, informational output')\n    logger.warning('warn about a non-fatal error or sth else')\n    logger.error('a fatal error')\n\n  ... and so on. see the `logging documentation\n  <https://docs.python.org/3/howto/logging.html#when-to-use-logging>`_\n  for more information\n\n* console interaction happens on stderr, that includes interactive\n  reporting functions like `help`, `info` and `list`\n\n* ...except ``input()`` is special, because we can't control the\n  stream it is using, unfortunately. we assume that it won't clutter\n  stdout, because interaction would be broken then anyways\n\n* what is output on INFO level is additionally controlled by commandline\n  flags\n\"\"\"\n\nimport inspect\nimport json\nimport logging\nimport logging.config\nimport logging.handlers  # needed for handlers defined there being configurable in logging.conf file\nimport os\nimport warnings\n\nconfigured = False\n\n# use something like this to ignore warnings:\n# warnings.filterwarnings('ignore', r'... regex for warning message to ignore ...')\n\n\ndef _log_warning(message, category, filename, lineno, file=None, line=None):\n    # for warnings, we just want to use the logging system, not stderr or other files\n    msg = \"{0}:{1}: {2}: {3}\".format(filename, lineno, category.__name__, message)\n    logger = create_logger(__name__)\n    # Note: the warning will look like coming from here,\n    # but msg contains info about where it really comes from\n    logger.warning(msg)\n\n\ndef setup_logging(stream=None, conf_fname=None, env_var='BORG_LOGGING_CONF', level='info', is_serve=False, json=False):\n    \"\"\"setup logging module according to the arguments provided\n\n    if conf_fname is given (or the config file name can be determined via\n    the env_var, if given): load this logging configuration.\n\n    otherwise, set up a stream handler logger on stderr (by default, if no\n    stream is provided).\n\n    if is_serve == True, we configure a special log format as expected by\n    the borg client log message interceptor.\n    \"\"\"\n    global configured\n    err_msg = None\n    if env_var:\n        conf_fname = os.environ.get(env_var, conf_fname)\n    if conf_fname:\n        try:\n            conf_fname = os.path.abspath(conf_fname)\n            # we open the conf file here to be able to give a reasonable\n            # error message in case of failure (if we give the filename to\n            # fileConfig(), it silently ignores unreadable files and gives\n            # unhelpful error msgs like \"No section: 'formatters'\"):\n            with open(conf_fname) as f:\n                logging.config.fileConfig(f)\n            configured = True\n            logger = logging.getLogger(__name__)\n            logger.debug('using logging configuration read from \"{0}\"'.format(conf_fname))\n            warnings.showwarning = _log_warning\n            return None\n        except Exception as err:  # XXX be more precise\n            err_msg = str(err)\n    # if we did not / not successfully load a logging configuration, fallback to this:\n    logger = logging.getLogger('')\n    handler = logging.StreamHandler(stream)\n    if is_serve and not json:\n        fmt = '$LOG %(levelname)s %(name)s Remote: %(message)s'\n    else:\n        fmt = '%(message)s'\n    formatter = JsonFormatter(fmt) if json else logging.Formatter(fmt)\n    handler.setFormatter(formatter)\n    borg_logger = logging.getLogger('borg')\n    borg_logger.formatter = formatter\n    borg_logger.json = json\n    if configured and logger.handlers:\n        # The RepositoryServer can call setup_logging a second time to adjust the output\n        # mode from text-ish is_serve to json is_serve.\n        # Thus, remove the previously installed handler, if any.\n        logger.handlers[0].close()\n        logger.handlers.clear()\n    logger.addHandler(handler)\n    logger.setLevel(level.upper())\n    configured = True\n    logger = logging.getLogger(__name__)\n    if err_msg:\n        logger.warning('setup_logging for \"{0}\" failed with \"{1}\".'.format(conf_fname, err_msg))\n    logger.debug('using builtin fallback logging configuration')\n    warnings.showwarning = _log_warning\n    return handler\n\n\ndef find_parent_module():\n    \"\"\"find the name of a the first module calling this module\n\n    if we cannot find it, we return the current module's name\n    (__name__) instead.\n    \"\"\"\n    try:\n        frame = inspect.currentframe().f_back\n        module = inspect.getmodule(frame)\n        while module is None or module.__name__ == __name__:\n            frame = frame.f_back\n            module = inspect.getmodule(frame)\n        return module.__name__\n    except AttributeError:\n        # somehow we failed to find our module\n        # return the logger module name by default\n        return __name__\n\n\ndef create_logger(name=None):\n    \"\"\"lazily create a Logger object with the proper path, which is returned by\n    find_parent_module() by default, or is provided via the commandline\n\n    this is really a shortcut for:\n\n        logger = logging.getLogger(__name__)\n\n    we use it to avoid errors and provide a more standard API.\n\n    We must create the logger lazily, because this is usually called from\n    module level (and thus executed at import time - BEFORE setup_logging()\n    was called). By doing it lazily we can do the setup first, we just have to\n    be careful not to call any logger methods before the setup_logging() call.\n    If you try, you'll get an exception.\n    \"\"\"\n    class LazyLogger:\n        def __init__(self, name=None):\n            self.__name = name or find_parent_module()\n            self.__real_logger = None\n\n        @property\n        def __logger(self):\n            if self.__real_logger is None:\n                if not configured:\n                    raise Exception(\"tried to call a logger before setup_logging() was called\")\n                self.__real_logger = logging.getLogger(self.__name)\n                if self.__name.startswith('borg.debug.') and self.__real_logger.level == logging.NOTSET:\n                    self.__real_logger.setLevel('WARNING')\n            return self.__real_logger\n\n        def getChild(self, suffix):\n            return LazyLogger(self.__name + '.' + suffix)\n\n        def setLevel(self, *args, **kw):\n            return self.__logger.setLevel(*args, **kw)\n\n        def log(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.log(*args, **kw)\n\n        def exception(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.exception(*args, **kw)\n\n        def debug(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.debug(*args, **kw)\n\n        def info(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.info(*args, **kw)\n\n        def warning(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.warning(*args, **kw)\n\n        def error(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.error(*args, **kw)\n\n        def critical(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.critical(*args, **kw)\n\n    return LazyLogger(name)\n\n\nclass JsonFormatter(logging.Formatter):\n    RECORD_ATTRIBUTES = (\n        'levelname',\n        'name',\n        'message',\n        # msgid is an attribute we made up in Borg to expose a non-changing handle for log messages\n        'msgid',\n    )\n\n    # Other attributes that are not very useful but do exist:\n    # processName, process, relativeCreated, stack_info, thread, threadName\n    # msg == message\n    # *args* are the unformatted arguments passed to the logger function, not useful now,\n    # become useful if sanitized properly (must be JSON serializable) in the code +\n    # fixed message IDs are assigned.\n    # exc_info, exc_text are generally uninteresting because the message will have that\n\n    def format(self, record):\n        super().format(record)\n        data = {\n            'type': 'log_message',\n            'time': record.created,\n            'message': '',\n            'levelname': 'CRITICAL',\n        }\n        for attr in self.RECORD_ATTRIBUTES:\n            value = getattr(record, attr, None)\n            if value:\n                data[attr] = value\n        return json.dumps(data)\n", "path": "src/borg/logger.py"}], "after_files": [{"content": "\"\"\"logging facilities\n\nThe way to use this is as follows:\n\n* each module declares its own logger, using:\n\n    from .logger import create_logger\n    logger = create_logger()\n\n* then each module uses logger.info/warning/debug/etc according to the\n  level it believes is appropriate:\n\n    logger.debug('debugging info for developers or power users')\n    logger.info('normal, informational output')\n    logger.warning('warn about a non-fatal error or sth else')\n    logger.error('a fatal error')\n\n  ... and so on. see the `logging documentation\n  <https://docs.python.org/3/howto/logging.html#when-to-use-logging>`_\n  for more information\n\n* console interaction happens on stderr, that includes interactive\n  reporting functions like `help`, `info` and `list`\n\n* ...except ``input()`` is special, because we can't control the\n  stream it is using, unfortunately. we assume that it won't clutter\n  stdout, because interaction would be broken then anyways\n\n* what is output on INFO level is additionally controlled by commandline\n  flags\n\"\"\"\n\nimport inspect\nimport json\nimport logging\nimport logging.config\nimport logging.handlers  # needed for handlers defined there being configurable in logging.conf file\nimport os\nimport warnings\n\nconfigured = False\n\n# use something like this to ignore warnings:\n# warnings.filterwarnings('ignore', r'... regex for warning message to ignore ...')\n\n\ndef _log_warning(message, category, filename, lineno, file=None, line=None):\n    # for warnings, we just want to use the logging system, not stderr or other files\n    msg = \"{0}:{1}: {2}: {3}\".format(filename, lineno, category.__name__, message)\n    logger = create_logger(__name__)\n    # Note: the warning will look like coming from here,\n    # but msg contains info about where it really comes from\n    logger.warning(msg)\n\n\ndef setup_logging(stream=None, conf_fname=None, env_var='BORG_LOGGING_CONF', level='info', is_serve=False, json=False):\n    \"\"\"setup logging module according to the arguments provided\n\n    if conf_fname is given (or the config file name can be determined via\n    the env_var, if given): load this logging configuration.\n\n    otherwise, set up a stream handler logger on stderr (by default, if no\n    stream is provided).\n\n    if is_serve == True, we configure a special log format as expected by\n    the borg client log message interceptor.\n    \"\"\"\n    global configured\n    err_msg = None\n    if env_var:\n        conf_fname = os.environ.get(env_var, conf_fname)\n    if conf_fname:\n        try:\n            conf_fname = os.path.abspath(conf_fname)\n            # we open the conf file here to be able to give a reasonable\n            # error message in case of failure (if we give the filename to\n            # fileConfig(), it silently ignores unreadable files and gives\n            # unhelpful error msgs like \"No section: 'formatters'\"):\n            with open(conf_fname) as f:\n                logging.config.fileConfig(f)\n            configured = True\n            logger = logging.getLogger(__name__)\n            borg_logger = logging.getLogger('borg')\n            borg_logger.json = json\n            logger.debug('using logging configuration read from \"{0}\"'.format(conf_fname))\n            warnings.showwarning = _log_warning\n            return None\n        except Exception as err:  # XXX be more precise\n            err_msg = str(err)\n    # if we did not / not successfully load a logging configuration, fallback to this:\n    logger = logging.getLogger('')\n    handler = logging.StreamHandler(stream)\n    if is_serve and not json:\n        fmt = '$LOG %(levelname)s %(name)s Remote: %(message)s'\n    else:\n        fmt = '%(message)s'\n    formatter = JsonFormatter(fmt) if json else logging.Formatter(fmt)\n    handler.setFormatter(formatter)\n    borg_logger = logging.getLogger('borg')\n    borg_logger.formatter = formatter\n    borg_logger.json = json\n    if configured and logger.handlers:\n        # The RepositoryServer can call setup_logging a second time to adjust the output\n        # mode from text-ish is_serve to json is_serve.\n        # Thus, remove the previously installed handler, if any.\n        logger.handlers[0].close()\n        logger.handlers.clear()\n    logger.addHandler(handler)\n    logger.setLevel(level.upper())\n    configured = True\n    logger = logging.getLogger(__name__)\n    if err_msg:\n        logger.warning('setup_logging for \"{0}\" failed with \"{1}\".'.format(conf_fname, err_msg))\n    logger.debug('using builtin fallback logging configuration')\n    warnings.showwarning = _log_warning\n    return handler\n\n\ndef find_parent_module():\n    \"\"\"find the name of a the first module calling this module\n\n    if we cannot find it, we return the current module's name\n    (__name__) instead.\n    \"\"\"\n    try:\n        frame = inspect.currentframe().f_back\n        module = inspect.getmodule(frame)\n        while module is None or module.__name__ == __name__:\n            frame = frame.f_back\n            module = inspect.getmodule(frame)\n        return module.__name__\n    except AttributeError:\n        # somehow we failed to find our module\n        # return the logger module name by default\n        return __name__\n\n\ndef create_logger(name=None):\n    \"\"\"lazily create a Logger object with the proper path, which is returned by\n    find_parent_module() by default, or is provided via the commandline\n\n    this is really a shortcut for:\n\n        logger = logging.getLogger(__name__)\n\n    we use it to avoid errors and provide a more standard API.\n\n    We must create the logger lazily, because this is usually called from\n    module level (and thus executed at import time - BEFORE setup_logging()\n    was called). By doing it lazily we can do the setup first, we just have to\n    be careful not to call any logger methods before the setup_logging() call.\n    If you try, you'll get an exception.\n    \"\"\"\n    class LazyLogger:\n        def __init__(self, name=None):\n            self.__name = name or find_parent_module()\n            self.__real_logger = None\n\n        @property\n        def __logger(self):\n            if self.__real_logger is None:\n                if not configured:\n                    raise Exception(\"tried to call a logger before setup_logging() was called\")\n                self.__real_logger = logging.getLogger(self.__name)\n                if self.__name.startswith('borg.debug.') and self.__real_logger.level == logging.NOTSET:\n                    self.__real_logger.setLevel('WARNING')\n            return self.__real_logger\n\n        def getChild(self, suffix):\n            return LazyLogger(self.__name + '.' + suffix)\n\n        def setLevel(self, *args, **kw):\n            return self.__logger.setLevel(*args, **kw)\n\n        def log(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.log(*args, **kw)\n\n        def exception(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.exception(*args, **kw)\n\n        def debug(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.debug(*args, **kw)\n\n        def info(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.info(*args, **kw)\n\n        def warning(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.warning(*args, **kw)\n\n        def error(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.error(*args, **kw)\n\n        def critical(self, *args, **kw):\n            if 'msgid' in kw:\n                kw.setdefault('extra', {})['msgid'] = kw.pop('msgid')\n            return self.__logger.critical(*args, **kw)\n\n    return LazyLogger(name)\n\n\nclass JsonFormatter(logging.Formatter):\n    RECORD_ATTRIBUTES = (\n        'levelname',\n        'name',\n        'message',\n        # msgid is an attribute we made up in Borg to expose a non-changing handle for log messages\n        'msgid',\n    )\n\n    # Other attributes that are not very useful but do exist:\n    # processName, process, relativeCreated, stack_info, thread, threadName\n    # msg == message\n    # *args* are the unformatted arguments passed to the logger function, not useful now,\n    # become useful if sanitized properly (must be JSON serializable) in the code +\n    # fixed message IDs are assigned.\n    # exc_info, exc_text are generally uninteresting because the message will have that\n\n    def format(self, record):\n        super().format(record)\n        data = {\n            'type': 'log_message',\n            'time': record.created,\n            'message': '',\n            'levelname': 'CRITICAL',\n        }\n        for attr in self.RECORD_ATTRIBUTES:\n            value = getattr(record, attr, None)\n            if value:\n                data[attr] = value\n        return json.dumps(data)\n", "path": "src/borg/logger.py"}]}