Skip to content

Instantly share code, notes, and snippets.

@dgelessus
Created July 1, 2018 17:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dgelessus/018681c297dfae22a06a3bc315d9e6e3 to your computer and use it in GitHub Desktop.
Save dgelessus/018681c297dfae22a06a3bc315d9e6e3 to your computer and use it in GitHub Desktop.
Helper module to log Python strings using NSLog
"""Allows logging Python :class:`str`s to the system log using Foundation's ``NSLog`` function.
This module is meant to help with debugging the loading process of Rubicon on systems like iOS, where sometimes only
NSLog output (but not stdout/stderr) is available to the developer.
"""
import ctypes
import io
import sys
__all__ = [
'nslog',
'NSLogWriter',
]
# Name of the UTF-16 encoding with the system byte order.
if sys.byteorder == 'little':
UTF16_NATIVE = 'utf-16-le'
elif sys.byteorder == 'big':
UTF16_NATIVE = 'utf-16-be'
else:
raise AssertionError('Unknown byte order: ' + sys.byteorder)
# Note: Many of the following definitions are duplicated in other rubicon.objc submodules.
# However because this module is used to debug the early startup process of Rubicon, we can't use any of the other
# definitions.
class CFTypeRef(ctypes.c_void_p):
pass
CFIndex = ctypes.c_long
UniChar = ctypes.c_uint16
CoreFoundation = ctypes.CDLL('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
Foundation = ctypes.CDLL('/System/Library/Frameworks/Foundation.framework/Foundation')
# void CFRelease(CFTypeRef arg)
CoreFoundation.CFRelease.restype = None
CoreFoundation.CFRelease.argtypes = [CFTypeRef]
# CFStringRef CFStringCreateWithCharacters(CFAllocatorRef alloc, const UniChar *chars, CFIndex numChars)
CoreFoundation.CFStringCreateWithCharacters.restype = CFTypeRef
CoreFoundation.CFStringCreateWithCharacters.argtypes = [CFTypeRef, ctypes.POINTER(UniChar), CFIndex]
# void NSLog(NSString *format, ...)
Foundation.NSLog.restype = None
Foundation.NSLog.argtypes = [CFTypeRef]
def _cfstr(s):
"""Create a ``CFString`` from the given Python :class:`str`."""
encoded = s.encode(UTF16_NATIVE)
assert len(encoded) % 2 == 0
arr = (UniChar * (len(encoded)//2)).from_buffer_copy(encoded)
cfstring = CoreFoundation.CFStringCreateWithCharacters(None, arr, len(arr))
assert cfstring is not None
return cfstring
# Format string for a single object.
FORMAT = _cfstr('%@')
def nslog(s):
"""Log the given Python :class:`str` to the system log."""
cfstring = _cfstr(s)
Foundation.NSLog(FORMAT, cfstring)
CoreFoundation.CFRelease(cfstring)
class NSLogWriter(io.TextIOBase):
"""An output-only text stream that writes to the system log."""
def write(self, s):
nslog(s[:-1] if s.endswith('\n') else s)
return len(s)
@property
def encoding(self):
return UTF16_NATIVE
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment