We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
HTTP 400: data validation error: Problem validating JSON document against schema
Sometimes when sending an event to the APM instance a HTTP 400 response is returned:

    HTTP 400: data validation error: Problem validating JSON document against schema: 
    I[#] S[#] doesn't validate with "error#" I[#/errors/0/log/message] 
    S[#/properties/errors/items/properties/log/properties/message/type] 
    expected string, but got array

I've also seen a similar, but subtly different message:

    HTTP 400: data validation error: Problem validating JSON document against schema: 
    I[#] S[#] doesn't validate with "error#" I[#/errors/0/log/param_message] 
    S[#/properties/errors/items/properties/log/properties/param_message/type] 
    expected string or null, but got array

This is then followed by a log message:

    Failed to submit message: '<no message value>'

I'm using the latest elastic-apm client available from PyPI (3.0.1) with Python 3.6.5 and the APM server is using version 6.4.1.

(This seems similar to #135, but not quite the same).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `elasticapm/handlers/logging.py`
Content:
```
1 """
2 elasticapm.handlers.logging
3 ~~~~~~~~~~~~~~~~~~~~~~
4 
5 :copyright: (c) 2011-2017 Elasticsearch
6 
7 Large portions are
8 :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.
9 :license: BSD, see LICENSE for more details.
10 """
11 
12 from __future__ import absolute_import
13 
14 import datetime
15 import logging
16 import sys
17 import traceback
18 
19 from elasticapm.base import Client
20 from elasticapm.utils import compat
21 from elasticapm.utils.encoding import to_unicode
22 from elasticapm.utils.stacks import iter_stack_frames
23 
24 
25 class LoggingHandler(logging.Handler):
26     def __init__(self, *args, **kwargs):
27         client = kwargs.pop("client_cls", Client)
28         if len(args) == 1:
29             arg = args[0]
30             args = args[1:]
31             if isinstance(arg, Client):
32                 self.client = arg
33             else:
34                 raise ValueError(
35                     "The first argument to %s must be a Client instance, "
36                     "got %r instead." % (self.__class__.__name__, arg)
37                 )
38         elif "client" in kwargs:
39             self.client = kwargs.pop("client")
40         else:
41             self.client = client(*args, **kwargs)
42 
43         logging.Handler.__init__(self, level=kwargs.get("level", logging.NOTSET))
44 
45     def emit(self, record):
46         self.format(record)
47 
48         # Avoid typical config issues by overriding loggers behavior
49         if record.name.startswith("elasticapm.errors"):
50             sys.stderr.write(to_unicode(record.message) + "\n")
51             return
52 
53         try:
54             return self._emit(record)
55         except Exception:
56             sys.stderr.write("Top level ElasticAPM exception caught - failed creating log record.\n")
57             sys.stderr.write(to_unicode(record.msg + "\n"))
58             sys.stderr.write(to_unicode(traceback.format_exc() + "\n"))
59 
60             try:
61                 self.client.capture("Exception")
62             except Exception:
63                 pass
64 
65     def _emit(self, record, **kwargs):
66         data = {}
67 
68         for k, v in compat.iteritems(record.__dict__):
69             if "." not in k and k not in ("culprit",):
70                 continue
71             data[k] = v
72 
73         stack = getattr(record, "stack", None)
74         if stack is True:
75             stack = iter_stack_frames()
76 
77         if stack:
78             frames = []
79             started = False
80             last_mod = ""
81             for item in stack:
82                 if isinstance(item, (list, tuple)):
83                     frame, lineno = item
84                 else:
85                     frame, lineno = item, item.f_lineno
86 
87                 if not started:
88                     f_globals = getattr(frame, "f_globals", {})
89                     module_name = f_globals.get("__name__", "")
90                     if last_mod.startswith("logging") and not module_name.startswith("logging"):
91                         started = True
92                     else:
93                         last_mod = module_name
94                         continue
95                 frames.append((frame, lineno))
96             stack = frames
97 
98         custom = getattr(record, "data", {})
99         # Add in all of the data from the record that we aren't already capturing
100         for k in record.__dict__.keys():
101             if k in (
102                 "stack",
103                 "name",
104                 "args",
105                 "msg",
106                 "levelno",
107                 "exc_text",
108                 "exc_info",
109                 "data",
110                 "created",
111                 "levelname",
112                 "msecs",
113                 "relativeCreated",
114             ):
115                 continue
116             if k.startswith("_"):
117                 continue
118             custom[k] = record.__dict__[k]
119 
120         date = datetime.datetime.utcfromtimestamp(record.created)
121 
122         # If there's no exception being processed,
123         # exc_info may be a 3-tuple of None
124         # http://docs.python.org/library/sys.html#sys.exc_info
125         if record.exc_info and all(record.exc_info):
126             handler = self.client.get_handler("elasticapm.events.Exception")
127             exception = handler.capture(self.client, exc_info=record.exc_info)
128         else:
129             exception = None
130 
131         return self.client.capture(
132             "Message",
133             param_message={"message": record.msg, "params": record.args},
134             stack=stack,
135             custom=custom,
136             exception=exception,
137             date=date,
138             level=record.levelno,
139             logger_name=record.name,
140             **kwargs
141         )
142
```

Path: `elasticapm/handlers/logbook.py`
Content:
```
1 """
2 elasticapm.handlers.logbook
3 ~~~~~~~~~~~~~~~~~~~~~~
4 
5 :copyright: (c) 2011-2017 Elasticsearch
6 
7 Large portions are
8 :copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.
9 :license: BSD, see LICENSE for more details.
10 """
11 from __future__ import absolute_import
12 
13 import sys
14 import traceback
15 
16 import logbook
17 
18 from elasticapm.base import Client
19 from elasticapm.utils.encoding import to_unicode
20 
21 LOOKBOOK_LEVELS = {
22     logbook.DEBUG: "debug",
23     logbook.INFO: "info",
24     logbook.NOTICE: "info",
25     logbook.WARNING: "warning",
26     logbook.ERROR: "error",
27     logbook.CRITICAL: "fatal",
28 }
29 
30 
31 class LogbookHandler(logbook.Handler):
32     def __init__(self, *args, **kwargs):
33         if len(args) == 1:
34             arg = args[0]
35             # if isinstance(arg, compat.string_types):
36             # self.client = kwargs.pop('client_cls', Client)(dsn=arg)
37             if isinstance(arg, Client):
38                 self.client = arg
39             else:
40                 raise ValueError(
41                     "The first argument to %s must be a Client instance, "
42                     "got %r instead." % (self.__class__.__name__, arg)
43                 )
44             args = []
45         else:
46             try:
47                 self.client = kwargs.pop("client")
48             except KeyError:
49                 raise TypeError("Expected keyword argument for LoggingHandler: client")
50         super(LogbookHandler, self).__init__(*args, **kwargs)
51 
52     def emit(self, record):
53         self.format(record)
54 
55         # Avoid typical config issues by overriding loggers behavior
56         if record.channel.startswith("elasticapm.errors"):
57             sys.stderr.write(to_unicode(record.message + "\n"))
58             return
59 
60         try:
61             return self._emit(record)
62         except Exception:
63             sys.stderr.write("Top level ElasticAPM exception caught - failed creating log record.\n")
64             sys.stderr.write(to_unicode(record.msg + "\n"))
65             sys.stderr.write(to_unicode(traceback.format_exc() + "\n"))
66 
67             try:
68                 self.client.capture("Exception")
69             except Exception:
70                 pass
71 
72     def _emit(self, record):
73         # If there's no exception being processed,
74         # exc_info may be a 3-tuple of None
75         # http://docs.python.org/library/sys.html#sys.exc_info
76         if record.exc_info is True or (record.exc_info and all(record.exc_info)):
77             handler = self.client.get_handler("elasticapm.events.Exception")
78             exception = handler.capture(self.client, exc_info=record.exc_info)
79         else:
80             exception = None
81 
82         return self.client.capture_message(
83             param_message={"message": record.msg, "params": record.args},
84             exception=exception,
85             level=LOOKBOOK_LEVELS[record.level],
86             logger_name=record.channel,
87             custom=record.extra,
88             stack=record.kwargs.get("stack"),
89         )
90
```
--- 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/elasticapm/handlers/logbook.py b/elasticapm/handlers/logbook.py
--- a/elasticapm/handlers/logbook.py
+++ b/elasticapm/handlers/logbook.py
@@ -16,6 +16,7 @@
 import logbook
 
 from elasticapm.base import Client
+from elasticapm.utils import compat
 from elasticapm.utils.encoding import to_unicode
 
 LOOKBOOK_LEVELS = {
@@ -80,7 +81,7 @@
             exception = None
 
         return self.client.capture_message(
-            param_message={"message": record.msg, "params": record.args},
+            param_message={"message": compat.text_type(record.msg), "params": record.args},
             exception=exception,
             level=LOOKBOOK_LEVELS[record.level],
             logger_name=record.channel,
diff --git a/elasticapm/handlers/logging.py b/elasticapm/handlers/logging.py
--- a/elasticapm/handlers/logging.py
+++ b/elasticapm/handlers/logging.py
@@ -130,7 +130,7 @@
 
         return self.client.capture(
             "Message",
-            param_message={"message": record.msg, "params": record.args},
+            param_message={"message": compat.text_type(record.msg), "params": record.args},
             stack=stack,
             custom=custom,
             exception=exception,

{"golden_diff": "diff --git a/elasticapm/handlers/logbook.py b/elasticapm/handlers/logbook.py\n--- a/elasticapm/handlers/logbook.py\n+++ b/elasticapm/handlers/logbook.py\n@@ -16,6 +16,7 @@\n import logbook\n \n from elasticapm.base import Client\n+from elasticapm.utils import compat\n from elasticapm.utils.encoding import to_unicode\n \n LOOKBOOK_LEVELS = {\n@@ -80,7 +81,7 @@\n             exception = None\n \n         return self.client.capture_message(\n-            param_message={\"message\": record.msg, \"params\": record.args},\n+            param_message={\"message\": compat.text_type(record.msg), \"params\": record.args},\n             exception=exception,\n             level=LOOKBOOK_LEVELS[record.level],\n             logger_name=record.channel,\ndiff --git a/elasticapm/handlers/logging.py b/elasticapm/handlers/logging.py\n--- a/elasticapm/handlers/logging.py\n+++ b/elasticapm/handlers/logging.py\n@@ -130,7 +130,7 @@\n \n         return self.client.capture(\n             \"Message\",\n-            param_message={\"message\": record.msg, \"params\": record.args},\n+            param_message={\"message\": compat.text_type(record.msg), \"params\": record.args},\n             stack=stack,\n             custom=custom,\n             exception=exception,\n", "issue": "HTTP 400: data validation error: Problem validating JSON document against schema\nSometimes when sending an event to the APM instance a HTTP 400 response is returned:\r\n\r\n    HTTP 400: data validation error: Problem validating JSON document against schema: \r\n    I[#] S[#] doesn't validate with \"error#\" I[#/errors/0/log/message] \r\n    S[#/properties/errors/items/properties/log/properties/message/type] \r\n    expected string, but got array\r\n\r\nI've also seen a similar, but subtly different message:\r\n\r\n    HTTP 400: data validation error: Problem validating JSON document against schema: \r\n    I[#] S[#] doesn't validate with \"error#\" I[#/errors/0/log/param_message] \r\n    S[#/properties/errors/items/properties/log/properties/param_message/type] \r\n    expected string or null, but got array\r\n\r\nThis is then followed by a log message:\r\n\r\n    Failed to submit message: '<no message value>'\r\n\r\nI'm using the latest elastic-apm client available from PyPI (3.0.1) with Python 3.6.5 and the APM server is using version 6.4.1.\r\n\r\n(This seems similar to #135, but not quite the same).\n", "before_files": [{"content": "\"\"\"\nelasticapm.handlers.logging\n~~~~~~~~~~~~~~~~~~~~~~\n\n:copyright: (c) 2011-2017 Elasticsearch\n\nLarge portions are\n:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.\n:license: BSD, see LICENSE for more details.\n\"\"\"\n\nfrom __future__ import absolute_import\n\nimport datetime\nimport logging\nimport sys\nimport traceback\n\nfrom elasticapm.base import Client\nfrom elasticapm.utils import compat\nfrom elasticapm.utils.encoding import to_unicode\nfrom elasticapm.utils.stacks import iter_stack_frames\n\n\nclass LoggingHandler(logging.Handler):\n    def __init__(self, *args, **kwargs):\n        client = kwargs.pop(\"client_cls\", Client)\n        if len(args) == 1:\n            arg = args[0]\n            args = args[1:]\n            if isinstance(arg, Client):\n                self.client = arg\n            else:\n                raise ValueError(\n                    \"The first argument to %s must be a Client instance, \"\n                    \"got %r instead.\" % (self.__class__.__name__, arg)\n                )\n        elif \"client\" in kwargs:\n            self.client = kwargs.pop(\"client\")\n        else:\n            self.client = client(*args, **kwargs)\n\n        logging.Handler.__init__(self, level=kwargs.get(\"level\", logging.NOTSET))\n\n    def emit(self, record):\n        self.format(record)\n\n        # Avoid typical config issues by overriding loggers behavior\n        if record.name.startswith(\"elasticapm.errors\"):\n            sys.stderr.write(to_unicode(record.message) + \"\\n\")\n            return\n\n        try:\n            return self._emit(record)\n        except Exception:\n            sys.stderr.write(\"Top level ElasticAPM exception caught - failed creating log record.\\n\")\n            sys.stderr.write(to_unicode(record.msg + \"\\n\"))\n            sys.stderr.write(to_unicode(traceback.format_exc() + \"\\n\"))\n\n            try:\n                self.client.capture(\"Exception\")\n            except Exception:\n                pass\n\n    def _emit(self, record, **kwargs):\n        data = {}\n\n        for k, v in compat.iteritems(record.__dict__):\n            if \".\" not in k and k not in (\"culprit\",):\n                continue\n            data[k] = v\n\n        stack = getattr(record, \"stack\", None)\n        if stack is True:\n            stack = iter_stack_frames()\n\n        if stack:\n            frames = []\n            started = False\n            last_mod = \"\"\n            for item in stack:\n                if isinstance(item, (list, tuple)):\n                    frame, lineno = item\n                else:\n                    frame, lineno = item, item.f_lineno\n\n                if not started:\n                    f_globals = getattr(frame, \"f_globals\", {})\n                    module_name = f_globals.get(\"__name__\", \"\")\n                    if last_mod.startswith(\"logging\") and not module_name.startswith(\"logging\"):\n                        started = True\n                    else:\n                        last_mod = module_name\n                        continue\n                frames.append((frame, lineno))\n            stack = frames\n\n        custom = getattr(record, \"data\", {})\n        # Add in all of the data from the record that we aren't already capturing\n        for k in record.__dict__.keys():\n            if k in (\n                \"stack\",\n                \"name\",\n                \"args\",\n                \"msg\",\n                \"levelno\",\n                \"exc_text\",\n                \"exc_info\",\n                \"data\",\n                \"created\",\n                \"levelname\",\n                \"msecs\",\n                \"relativeCreated\",\n            ):\n                continue\n            if k.startswith(\"_\"):\n                continue\n            custom[k] = record.__dict__[k]\n\n        date = datetime.datetime.utcfromtimestamp(record.created)\n\n        # If there's no exception being processed,\n        # exc_info may be a 3-tuple of None\n        # http://docs.python.org/library/sys.html#sys.exc_info\n        if record.exc_info and all(record.exc_info):\n            handler = self.client.get_handler(\"elasticapm.events.Exception\")\n            exception = handler.capture(self.client, exc_info=record.exc_info)\n        else:\n            exception = None\n\n        return self.client.capture(\n            \"Message\",\n            param_message={\"message\": record.msg, \"params\": record.args},\n            stack=stack,\n            custom=custom,\n            exception=exception,\n            date=date,\n            level=record.levelno,\n            logger_name=record.name,\n            **kwargs\n        )\n", "path": "elasticapm/handlers/logging.py"}, {"content": "\"\"\"\nelasticapm.handlers.logbook\n~~~~~~~~~~~~~~~~~~~~~~\n\n:copyright: (c) 2011-2017 Elasticsearch\n\nLarge portions are\n:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.\n:license: BSD, see LICENSE for more details.\n\"\"\"\nfrom __future__ import absolute_import\n\nimport sys\nimport traceback\n\nimport logbook\n\nfrom elasticapm.base import Client\nfrom elasticapm.utils.encoding import to_unicode\n\nLOOKBOOK_LEVELS = {\n    logbook.DEBUG: \"debug\",\n    logbook.INFO: \"info\",\n    logbook.NOTICE: \"info\",\n    logbook.WARNING: \"warning\",\n    logbook.ERROR: \"error\",\n    logbook.CRITICAL: \"fatal\",\n}\n\n\nclass LogbookHandler(logbook.Handler):\n    def __init__(self, *args, **kwargs):\n        if len(args) == 1:\n            arg = args[0]\n            # if isinstance(arg, compat.string_types):\n            # self.client = kwargs.pop('client_cls', Client)(dsn=arg)\n            if isinstance(arg, Client):\n                self.client = arg\n            else:\n                raise ValueError(\n                    \"The first argument to %s must be a Client instance, \"\n                    \"got %r instead.\" % (self.__class__.__name__, arg)\n                )\n            args = []\n        else:\n            try:\n                self.client = kwargs.pop(\"client\")\n            except KeyError:\n                raise TypeError(\"Expected keyword argument for LoggingHandler: client\")\n        super(LogbookHandler, self).__init__(*args, **kwargs)\n\n    def emit(self, record):\n        self.format(record)\n\n        # Avoid typical config issues by overriding loggers behavior\n        if record.channel.startswith(\"elasticapm.errors\"):\n            sys.stderr.write(to_unicode(record.message + \"\\n\"))\n            return\n\n        try:\n            return self._emit(record)\n        except Exception:\n            sys.stderr.write(\"Top level ElasticAPM exception caught - failed creating log record.\\n\")\n            sys.stderr.write(to_unicode(record.msg + \"\\n\"))\n            sys.stderr.write(to_unicode(traceback.format_exc() + \"\\n\"))\n\n            try:\n                self.client.capture(\"Exception\")\n            except Exception:\n                pass\n\n    def _emit(self, record):\n        # If there's no exception being processed,\n        # exc_info may be a 3-tuple of None\n        # http://docs.python.org/library/sys.html#sys.exc_info\n        if record.exc_info is True or (record.exc_info and all(record.exc_info)):\n            handler = self.client.get_handler(\"elasticapm.events.Exception\")\n            exception = handler.capture(self.client, exc_info=record.exc_info)\n        else:\n            exception = None\n\n        return self.client.capture_message(\n            param_message={\"message\": record.msg, \"params\": record.args},\n            exception=exception,\n            level=LOOKBOOK_LEVELS[record.level],\n            logger_name=record.channel,\n            custom=record.extra,\n            stack=record.kwargs.get(\"stack\"),\n        )\n", "path": "elasticapm/handlers/logbook.py"}], "after_files": [{"content": "\"\"\"\nelasticapm.handlers.logging\n~~~~~~~~~~~~~~~~~~~~~~\n\n:copyright: (c) 2011-2017 Elasticsearch\n\nLarge portions are\n:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.\n:license: BSD, see LICENSE for more details.\n\"\"\"\n\nfrom __future__ import absolute_import\n\nimport datetime\nimport logging\nimport sys\nimport traceback\n\nfrom elasticapm.base import Client\nfrom elasticapm.utils import compat\nfrom elasticapm.utils.encoding import to_unicode\nfrom elasticapm.utils.stacks import iter_stack_frames\n\n\nclass LoggingHandler(logging.Handler):\n    def __init__(self, *args, **kwargs):\n        client = kwargs.pop(\"client_cls\", Client)\n        if len(args) == 1:\n            arg = args[0]\n            args = args[1:]\n            if isinstance(arg, Client):\n                self.client = arg\n            else:\n                raise ValueError(\n                    \"The first argument to %s must be a Client instance, \"\n                    \"got %r instead.\" % (self.__class__.__name__, arg)\n                )\n        elif \"client\" in kwargs:\n            self.client = kwargs.pop(\"client\")\n        else:\n            self.client = client(*args, **kwargs)\n\n        logging.Handler.__init__(self, level=kwargs.get(\"level\", logging.NOTSET))\n\n    def emit(self, record):\n        self.format(record)\n\n        # Avoid typical config issues by overriding loggers behavior\n        if record.name.startswith(\"elasticapm.errors\"):\n            sys.stderr.write(to_unicode(record.message) + \"\\n\")\n            return\n\n        try:\n            return self._emit(record)\n        except Exception:\n            sys.stderr.write(\"Top level ElasticAPM exception caught - failed creating log record.\\n\")\n            sys.stderr.write(to_unicode(record.msg + \"\\n\"))\n            sys.stderr.write(to_unicode(traceback.format_exc() + \"\\n\"))\n\n            try:\n                self.client.capture(\"Exception\")\n            except Exception:\n                pass\n\n    def _emit(self, record, **kwargs):\n        data = {}\n\n        for k, v in compat.iteritems(record.__dict__):\n            if \".\" not in k and k not in (\"culprit\",):\n                continue\n            data[k] = v\n\n        stack = getattr(record, \"stack\", None)\n        if stack is True:\n            stack = iter_stack_frames()\n\n        if stack:\n            frames = []\n            started = False\n            last_mod = \"\"\n            for item in stack:\n                if isinstance(item, (list, tuple)):\n                    frame, lineno = item\n                else:\n                    frame, lineno = item, item.f_lineno\n\n                if not started:\n                    f_globals = getattr(frame, \"f_globals\", {})\n                    module_name = f_globals.get(\"__name__\", \"\")\n                    if last_mod.startswith(\"logging\") and not module_name.startswith(\"logging\"):\n                        started = True\n                    else:\n                        last_mod = module_name\n                        continue\n                frames.append((frame, lineno))\n            stack = frames\n\n        custom = getattr(record, \"data\", {})\n        # Add in all of the data from the record that we aren't already capturing\n        for k in record.__dict__.keys():\n            if k in (\n                \"stack\",\n                \"name\",\n                \"args\",\n                \"msg\",\n                \"levelno\",\n                \"exc_text\",\n                \"exc_info\",\n                \"data\",\n                \"created\",\n                \"levelname\",\n                \"msecs\",\n                \"relativeCreated\",\n            ):\n                continue\n            if k.startswith(\"_\"):\n                continue\n            custom[k] = record.__dict__[k]\n\n        date = datetime.datetime.utcfromtimestamp(record.created)\n\n        # If there's no exception being processed,\n        # exc_info may be a 3-tuple of None\n        # http://docs.python.org/library/sys.html#sys.exc_info\n        if record.exc_info and all(record.exc_info):\n            handler = self.client.get_handler(\"elasticapm.events.Exception\")\n            exception = handler.capture(self.client, exc_info=record.exc_info)\n        else:\n            exception = None\n\n        return self.client.capture(\n            \"Message\",\n            param_message={\"message\": compat.text_type(record.msg), \"params\": record.args},\n            stack=stack,\n            custom=custom,\n            exception=exception,\n            date=date,\n            level=record.levelno,\n            logger_name=record.name,\n            **kwargs\n        )\n", "path": "elasticapm/handlers/logging.py"}, {"content": "\"\"\"\nelasticapm.handlers.logbook\n~~~~~~~~~~~~~~~~~~~~~~\n\n:copyright: (c) 2011-2017 Elasticsearch\n\nLarge portions are\n:copyright: (c) 2010 by the Sentry Team, see AUTHORS for more details.\n:license: BSD, see LICENSE for more details.\n\"\"\"\nfrom __future__ import absolute_import\n\nimport sys\nimport traceback\n\nimport logbook\n\nfrom elasticapm.base import Client\nfrom elasticapm.utils import compat\nfrom elasticapm.utils.encoding import to_unicode\n\nLOOKBOOK_LEVELS = {\n    logbook.DEBUG: \"debug\",\n    logbook.INFO: \"info\",\n    logbook.NOTICE: \"info\",\n    logbook.WARNING: \"warning\",\n    logbook.ERROR: \"error\",\n    logbook.CRITICAL: \"fatal\",\n}\n\n\nclass LogbookHandler(logbook.Handler):\n    def __init__(self, *args, **kwargs):\n        if len(args) == 1:\n            arg = args[0]\n            # if isinstance(arg, compat.string_types):\n            # self.client = kwargs.pop('client_cls', Client)(dsn=arg)\n            if isinstance(arg, Client):\n                self.client = arg\n            else:\n                raise ValueError(\n                    \"The first argument to %s must be a Client instance, \"\n                    \"got %r instead.\" % (self.__class__.__name__, arg)\n                )\n            args = []\n        else:\n            try:\n                self.client = kwargs.pop(\"client\")\n            except KeyError:\n                raise TypeError(\"Expected keyword argument for LoggingHandler: client\")\n        super(LogbookHandler, self).__init__(*args, **kwargs)\n\n    def emit(self, record):\n        self.format(record)\n\n        # Avoid typical config issues by overriding loggers behavior\n        if record.channel.startswith(\"elasticapm.errors\"):\n            sys.stderr.write(to_unicode(record.message + \"\\n\"))\n            return\n\n        try:\n            return self._emit(record)\n        except Exception:\n            sys.stderr.write(\"Top level ElasticAPM exception caught - failed creating log record.\\n\")\n            sys.stderr.write(to_unicode(record.msg + \"\\n\"))\n            sys.stderr.write(to_unicode(traceback.format_exc() + \"\\n\"))\n\n            try:\n                self.client.capture(\"Exception\")\n            except Exception:\n                pass\n\n    def _emit(self, record):\n        # If there's no exception being processed,\n        # exc_info may be a 3-tuple of None\n        # http://docs.python.org/library/sys.html#sys.exc_info\n        if record.exc_info is True or (record.exc_info and all(record.exc_info)):\n            handler = self.client.get_handler(\"elasticapm.events.Exception\")\n            exception = handler.capture(self.client, exc_info=record.exc_info)\n        else:\n            exception = None\n\n        return self.client.capture_message(\n            param_message={\"message\": compat.text_type(record.msg), \"params\": record.args},\n            exception=exception,\n            level=LOOKBOOK_LEVELS[record.level],\n            logger_name=record.channel,\n            custom=record.extra,\n            stack=record.kwargs.get(\"stack\"),\n        )\n", "path": "elasticapm/handlers/logbook.py"}]}