Skip to content

Instantly share code, notes, and snippets.

@costa
Last active February 21, 2025 17:22
Show Gist options
  • Save costa/a72e53854fc93e14874452adbc6ea9c3 to your computer and use it in GitHub Desktop.
Save costa/a72e53854fc93e14874452adbc6ea9c3 to your computer and use it in GitHub Desktop.
Python (3.12+ish tested) logging.handlers.HTTPHandler JSON support monkey-patching
# NOTE Le monkey patch
# (https://github.com/python/cpython/blob/main/Lib/logging/handlers.py as of 2025-02-19)
def http_json_emit(self, record):
"""
Emit a record.
Send the record to the web server as a percent-encoded dictionary
"""
try:
# MONKEY import urllib.parse
import json
host = self.host
h = self.getConnection(host, self.secure)
url = self.url
# MONKEY data = urllib.parse.urlencode(self.mapLogRecord(record))
data = json.dumps(self.mapLogRecord(record))
if self.method == "GET":
if (url.find('?') >= 0):
sep = '&'
else:
sep = '?'
# MONKEY url = url + "%c%s" % (sep, data)
url = url + "%c%s" % (sep, urllib.parse.urlencode(data))
h.putrequest(self.method, url)
# support multiple hosts on one IP address...
# need to strip optional :port from host, if present
i = host.find(":")
if i >= 0:
host = host[:i]
# See issue #30904: putrequest call above already adds this header
# on Python 3.x.
# h.putheader("Host", host)
if self.method == "POST":
# MONKEY h.putheader("Content-type",
# "application/x-www-form-urlencoded")
h.putheader("Content-type", "application/json")
h.putheader("Content-length", str(len(data)))
if self.credentials:
import base64
s = ('%s:%s' % self.credentials).encode('utf-8')
s = 'Basic ' + base64.b64encode(s).strip().decode('ascii')
h.putheader('Authorization', s)
h.endheaders()
if self.method == "POST":
h.send(data.encode('utf-8'))
h.getresponse() # can't do anything with the result
except Exception:
self.handleError(record)
logging.handlers.HTTPHandler.emit = http_json_emit
def http_json_mapLogRecord(self, record):
# NOTE filtering empty values and duplicate attrs
return {k: v for k, v in record.__dict__.items() if v and k not in ['msg', 'exc_info']}
logging.handlers.HTTPHandler.mapLogRecord = http_json_mapLogRecord
# NOTE When you start monkey-patching, you just cannot stop...;)
_srcfile = os.path.normcase(http_json_emit.__code__.co_filename)
_logging_is_internal_frame = logging._is_internal_frame
def _is_internal_frame(frame):
filename = os.path.normcase(frame.f_code.co_filename)
return filename == _srcfile or _logging_is_internal_frame(frame)
logging._is_internal_frame = _is_internal_frame
@costa
Copy link
Author

costa commented Feb 21, 2025

Not the best solution, but it works, and makes kinda more sense than https://github.com/nhairs/python-json-logger -- when you're not ready to go full https://github.com/Delgan/loguru just yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment