Skip to content

Commit 0323e5f

Browse files
committed
Add is_supported() - install/import on incompatible macOS versions
1 parent cb2024e commit 0323e5f

File tree

6 files changed

+58
-14
lines changed

6 files changed

+58
-14
lines changed

README.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Pyoslog
2-
Pyoslog is a simple extension to send messages to the macOS [unified logging system](https://developer.apple.com/documentation/os/os_log) from Python.
2+
Pyoslog is a simple Python module that allows you to send messages to the macOS [unified logging system](https://developer.apple.com/documentation/os/os_log).
33

44
```python
55
from pyoslog import os_log, OS_LOG_DEFAULT
@@ -8,19 +8,26 @@ os_log(OS_LOG_DEFAULT, 'Hello from Python!')
88

99

1010
## Installation
11+
Pyoslog requires macOS 10.12 or later.
12+
Install using `pip`:
13+
1114
```shell
1215
python -m pip install pyoslog
1316
```
1417

15-
Pyoslog requires macOS 10.12 or later.
18+
The module will install and import without error on earlier macOS versions.
19+
Use `pyoslog.is_supported()` if you need to support old macOS versions and want to know at runtime whether to use pyoslog.
20+
Please note that if `is_supported()` returns `False` then none of the module's other methods or constants will exist.
1621

1722

1823
## Usage
1924
Pyoslog currently provides the methods [`os_log_create`](https://developer.apple.com/documentation/os/1643744-os_log_create), [`os_log_with_type`](https://developer.apple.com/documentation/os/os_log_with_type) and [`os_log`](https://developer.apple.com/documentation/os/os_log), each with the same signatures as their native versions.
2025

21-
The module also offers a helper method – `log` – that by default posts a message of type `OS_LOG_TYPE_DEFAULT` to `OS_LOG_DEFAULT`. For example, the shortcut `log('message')` is equivalent to `os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEFAULT, 'message')`.
26+
The module also offers a helper method – `log` – that by default posts a message of type `OS_LOG_TYPE_DEFAULT` to `OS_LOG_DEFAULT`.
27+
For example, the shortcut `log('message')` is equivalent to `os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEFAULT, 'message')`.
2228

2329
The `Handler` class is designed for use with Python's inbuilt [logging](https://docs.python.org/3/library/logging.html) module.
30+
It works as a drop-in replacement for other Handler varieties.
2431

2532
### Labelling subsystem and category
2633
Create a log object using `os_log_create` and pass it to any of the log methods to add your own subsystem and category labels:
@@ -45,15 +52,20 @@ logger.addHandler(handler)
4552
logger.debug('message')
4653
```
4754

55+
To configure the Handler's output type, use `handler.setLevel` with a level from the logging module.
56+
These are mapped internally to the `OS_LOG_TYPE` values – for example, `handler.setLevel(logging.DEBUG)` will configure the Handler to output messages of type `OS_LOG_TYPE_DEBUG`.
57+
4858

4959
### Receiving log messages
50-
Logs can be viewed using Console.app or the `log` command. For example, messages sent using the default configuration can be monitored using:
60+
Logs can be viewed using Console.app or the `log` command.
61+
For example, messages sent using the default configuration can be streamed using:
5162

5263
```shell
5364
log stream --predicate 'processImagePath CONTAINS "Python"'
5465
```
5566

56-
Messages sent using custom configurations can be filtered more precisely. For example, to receive messages from the labelled subsystem used in the example above:
67+
Messages sent using custom configurations can be filtered more precisely.
68+
For example, to receive messages from the labelled subsystem used in the example above:
5769

5870
```shell
5971
log stream --predicate 'subsystem == "ac.robinson.pyoslog"' --level=debug
@@ -62,14 +74,21 @@ log stream --predicate 'subsystem == "ac.robinson.pyoslog"' --level=debug
6274
See `man log` for further details about the available options and filters.
6375

6476

77+
### Handling cleanup
78+
When labelling subsystem and category using the native C methods there is a requirement to free the log object after use (using `os_release`).
79+
The pyoslog module handles this for you – there is no need to `del` or release these objects.
80+
81+
6582
## Alternatives
66-
At the time this module was created there were no alternatives available on [PyPi](https://pypi.org/). There are, however, other options available if this is not seen as a constraint:
83+
At the time this module was created there were no alternatives available on [PyPi](https://pypi.org/search/?q=macos+unified+logging&c=Operating+System+%3A%3A+MacOS).
84+
There are, however, other options available if this is not seen as a constraint:
6785

6886
- [apple_os_log_py](https://github.com/cedar101/apple_os_log_py)
6987
- [pymacoslog](https://github.com/douglas-carmichael/pymacoslog)
7088
- [loggy](https://github.com/pointy-tools/loggy)
7189

72-
Note that the [pyobjc](https://pyobjc.readthedocs.io/) module [OSLog](https://pypi.org/project/pyobjc-framework-OSLog/) is for _reading_ from the unified logging system rather than writing to it. A `log.h` binding is on that project's [roadmap](https://github.com/ronaldoussoren/pyobjc/issues/377), but not yet implemented.
90+
Note that the [pyobjc](https://pyobjc.readthedocs.io/) module [OSLog](https://pypi.org/project/pyobjc-framework-OSLog/) is for _reading_ from the unified logging system rather than writing to it.
91+
A `log.h` binding is on that project's [roadmap](https://github.com/ronaldoussoren/pyobjc/issues/377), but not yet implemented.
7392

7493

7594
## License

pyoslog/__init__.py

100644100755
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
""""Send messages to the macOS unified logging system. See: https://developer.apple.com/documentation/os/os_log"""
2-
from .core import *
3-
from .handler import *
2+
from .compatibility import is_supported
3+
4+
if is_supported():
5+
from .core import *
6+
from .handler import *
7+
8+
del compatibility

pyoslog/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__title__ = 'pyoslog'
2-
__version__ = '0.2.0'
2+
__version__ = '0.3.0'
33
__description__ = 'Send messages to the macOS unified logging system'
44
__author__ = 'Simon Robinson'
55
__author_email__ = '[email protected]'

pyoslog/compatibility.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import platform
2+
import sys
3+
4+
5+
def is_supported():
6+
"""Unified logging is only present in macOS 10.12 and later, but it is nicer to not have to check OS type or version
7+
strings when installing or importing the module - use this method at runtime to check whether it is supported. It is
8+
important to note that if is_supported() is False then none of the module's other methods/constants will exist."""
9+
return sys.platform == 'darwin' and float('.'.join(platform.mac_ver()[0].split('.')[:2])) >= 10.12

pyoslog/core.py

100644100755
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def os_log_create(subsystem, category):
1616

1717
def os_log_with_type(log_object, log_type, *message):
1818
"""Sends a message at a specific logging level, such as default, info, debug, error, or fault, to the logging
19-
system. See: https://developer.apple.com/documentation/os/os_log_with_type """
19+
system. See: https://developer.apple.com/documentation/os/os_log_with_type"""
2020
return _pyoslog.os_log_with_type(log_object, log_type, ' '.join(map(str, message)))
2121

2222

setup.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1+
import importlib.util
12
import os
3+
24
from setuptools import setup, Extension
35

46
NAME = 'pyoslog'
57

68
about = {}
7-
working_directory = os.path.abspath(os.path.dirname(__file__))
8-
with open(os.path.join(working_directory, NAME, '__version__.py')) as version_file:
9+
working_directory = os.path.join(os.path.abspath(os.path.dirname(__file__)), NAME)
10+
with open(os.path.join(working_directory, '__version__.py')) as version_file:
911
exec(version_file.read(), about)
1012

1113
with open('README.md') as readme_file:
1214
readme = readme_file.read()
1315

16+
# see compatibility.py - allow installation on older versions but provide is_supported() at runtime
17+
spec = importlib.util.spec_from_file_location('compatibility', os.path.join(working_directory, 'compatibility.py'))
18+
compatibility = importlib.util.module_from_spec(spec)
19+
spec.loader.exec_module(compatibility)
20+
ext_modules = []
21+
# noinspection PyUnresolvedReferences
22+
if compatibility.is_supported():
23+
ext_modules.append(Extension('_' + NAME, ['%s/_%s.c' % (NAME, NAME)]))
24+
1425
# https://setuptools.pypa.io/en/latest/references/keywords.html
1526
setup(
1627
name=NAME,
@@ -24,7 +35,7 @@
2435

2536
platforms=['darwin'],
2637
packages=[NAME],
27-
ext_modules=[Extension('_' + NAME, ['%s/_%s.c' % (NAME, NAME)])],
38+
ext_modules=ext_modules,
2839

2940
package_data={'': ['LICENSE']},
3041
include_package_data=True,

0 commit comments

Comments
 (0)