Skip to content

Commit 9fb4147

Browse files
committed
Initial import - version 0.1.0
1 parent d013e26 commit 9fb4147

File tree

7 files changed

+370
-0
lines changed

7 files changed

+370
-0
lines changed

.gitignore

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
pip-wheel-metadata/
24+
share/python-wheels/
25+
*.egg-info/
26+
.installed.cfg
27+
*.egg
28+
MANIFEST
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
*.py,cover
51+
.hypothesis/
52+
.pytest_cache/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
target/
76+
77+
# Jupyter Notebook
78+
.ipynb_checkpoints
79+
80+
# IPython
81+
profile_default/
82+
ipython_config.py
83+
84+
# pyenv
85+
.python-version
86+
87+
# pipenv
88+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
90+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
91+
# install all needed dependencies.
92+
#Pipfile.lock
93+
94+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
95+
__pypackages__/
96+
97+
# Celery stuff
98+
celerybeat-schedule
99+
celerybeat.pid
100+
101+
# SageMath parsed files
102+
*.sage.py
103+
104+
# Environments
105+
.env
106+
.venv
107+
env/
108+
venv/
109+
ENV/
110+
env.bak/
111+
venv.bak/
112+
113+
# Spyder project settings
114+
.spyderproject
115+
.spyproject
116+
117+
# Rope project settings
118+
.ropeproject
119+
120+
# mkdocs documentation
121+
/site
122+
123+
# mypy
124+
.mypy_cache/
125+
.dmypy.json
126+
dmypy.json
127+
128+
# Pyre type checker
129+
.pyre/
130+
131+
# Custom for this repository
132+
versions
133+
build.sh
134+
.idea
135+
*.iml

MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include README.md LICENSE

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# 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.
3+
4+
```python
5+
from pyoslog import os_log, OS_LOG_DEFAULT
6+
os_log(OS_LOG_DEFAULT, 'Hello from Python!')
7+
```
8+
9+
## Installation
10+
`python -m pip install pyoslog`
11+
12+
13+
## Usage
14+
Pyoslog currently provides the methods [`os_log_create`](https://developer.apple.com/documentation/os/1643744-os_log_create), [`os_log_wih_type`](https://developer.apple.com/documentation/os/os_log_with_type) and [`os_log`](https://developer.apple.com/documentation/os/os_log) with the same signatures as their native versions.
15+
16+
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 `pyoslog.log('message')` is equivalent to `pyoslog.os_log_with_type(pyoslog.OS_LOG_DEFAULT, pyoslog.OS_LOG_TYPE_DEFAULT, 'message')`.
17+
18+
19+
### Labelling subsystem and category
20+
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:
21+
22+
```python
23+
import pyoslog
24+
log = pyoslog.os_log_create('ac.robinson.pyoslog', 'custom-category')
25+
pyoslog.os_log_with_type(log, pyoslog.OS_LOG_TYPE_DEBUG, 'Message to log object', log, 'of type', pyoslog.OS_LOG_TYPE_DEBUG)
26+
```
27+
28+
### Receiving log messages
29+
Logs can be viewed using Console.app or the `log` command. For example, messages sent using the default configuration can be monitored using:
30+
31+
`log stream --predicate 'processImagePath CONTAINS "Python"'`
32+
33+
Messages sent using custom configurations can be filtered more precisely. For example, to receive messages from the labelled subsystem example above:
34+
35+
`log stream --predicate 'subsystem == "ac.robinson.pyoslog"' --level=debug`
36+
37+
38+
## License
39+
[Apache 2.0](LICENSE)

pyoslog/__init__.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
""""Send messages to the macOS unified logging system. See: https://developer.apple.com/documentation/os/os_log"""
2+
import _pyoslog
3+
4+
OS_LOG_DEFAULT = _pyoslog.OS_LOG_DEFAULT
5+
6+
OS_LOG_TYPE_DEFAULT = _pyoslog.OS_LOG_TYPE_DEFAULT
7+
OS_LOG_TYPE_INFO = _pyoslog.OS_LOG_TYPE_INFO
8+
OS_LOG_TYPE_DEBUG = _pyoslog.OS_LOG_TYPE_DEBUG
9+
OS_LOG_TYPE_ERROR = _pyoslog.OS_LOG_TYPE_ERROR
10+
OS_LOG_TYPE_FAULT = _pyoslog.OS_LOG_TYPE_FAULT
11+
12+
13+
def os_log_create(subsystem, category):
14+
"""Creates a custom log object. See: https://developer.apple.com/documentation/os/1643744-os_log_create"""
15+
return _pyoslog.os_log_create(subsystem, category)
16+
17+
18+
def os_log_with_type(log_object, log_type, *message):
19+
"""Sends a message at a specific logging level, such as default, info, debug, error, or fault, to the logging
20+
system. See: https://developer.apple.com/documentation/os/os_log_with_type """
21+
return _pyoslog.os_log_with_type(log_object, log_type, ' '.join(map(str, message)))
22+
23+
24+
def os_log(log_object, *message):
25+
"""Sends a default-level message to the logging system. See: https://developer.apple.com/documentation/os/os_log"""
26+
return os_log_with_type(log_object, OS_LOG_TYPE_DEFAULT, *message)
27+
28+
29+
def log(*message, log_object=OS_LOG_DEFAULT, log_type=OS_LOG_TYPE_DEFAULT):
30+
"""Equivalent to os_log_with_type(log_object, log_type, *message) with the default log object and type"""
31+
return os_log_with_type(log_object, log_type, *message)

pyoslog/__version__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
__title__ = 'pyoslog'
2+
__version__ = '0.1.0'
3+
__description__ = 'Send messages to the macOS unified logging system'
4+
__author__ = 'Simon Robinson'
5+
__author_email__ = '[email protected]'
6+
__url__ = 'https://github.com/simonrob/pyoslog'
7+
__copyright__ = 'Copyright (c) 2022 Simon Robinson'
8+
__license__ = 'Apache 2.0'

pyoslog/_pyoslog.c

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#include <os/log.h>
2+
#include <os/object.h>
3+
4+
#include "Python.h"
5+
6+
PyDoc_STRVAR(_pyoslog_doc,
7+
"Send messages to the macOS unified logging system. See: "
8+
"https://developer.apple.com/documentation/os/os_log");
9+
10+
/* -------------------------------------------------------------------------- */
11+
12+
PyDoc_STRVAR(os_log_with_type_doc,
13+
"Sends a message at a specific logging level, such as default, "
14+
"info, debug, error, or fault, to the logging system. See: "
15+
"https://developer.apple.com/documentation/os/os_log_with_type");
16+
17+
static PyObject *py_os_log_with_type(PyObject *self, PyObject *args) {
18+
PyObject *py_log;
19+
const uint8_t *type;
20+
const char *log_message;
21+
22+
if (!PyArg_ParseTuple(args, "Obs", &py_log, &type, &log_message)) {
23+
return NULL;
24+
}
25+
26+
os_log_t *log;
27+
int use_default = 0;
28+
if (PyCapsule_IsValid(py_log, "os_log_t")) {
29+
if (!(log = (os_log_t *)PyCapsule_GetPointer(py_log, "os_log_t"))) {
30+
return NULL;
31+
}
32+
} else {
33+
// a hack - treat capsule errors as selecting the default log,
34+
// meaning we don't have to provide a constant os_log_t object
35+
use_default = 1;
36+
}
37+
38+
// TODO: can we support custom formats here? (must be constant strings)
39+
os_log_with_type(use_default ? OS_LOG_DEFAULT : *log, (os_log_type_t)type,
40+
"%{public}s", log_message);
41+
Py_RETURN_NONE;
42+
}
43+
44+
static void os_log_release(PyObject *args) {
45+
os_log_t *log;
46+
if (!(log = (os_log_t *)PyCapsule_GetPointer(args, "os_log_t"))) {
47+
return;
48+
}
49+
50+
os_release(*log);
51+
PyMem_Free(log);
52+
}
53+
54+
PyDoc_STRVAR(
55+
os_log_create_doc,
56+
"Creates a custom log object. See: "
57+
"https://developer.apple.com/documentation/os/1643744-os_log_create");
58+
59+
static PyObject *py_os_log_create(PyObject *self, PyObject *args) {
60+
const char *subsystem;
61+
const char *category;
62+
if (!PyArg_ParseTuple(args, "ss", &subsystem, &category)) {
63+
return NULL;
64+
}
65+
66+
os_log_t *log = (os_log_t *)PyMem_Malloc(sizeof(os_log_t *));
67+
if (log == NULL) {
68+
return NULL;
69+
}
70+
71+
*log = os_log_create(subsystem, category);
72+
return PyCapsule_New(log, "os_log_t", os_log_release);
73+
}
74+
75+
// TODO: are there any additional methods worth implementing?
76+
// https://opensource.apple.com/source/xnu/xnu-3789.21.4/libkern/os/log.h.auto.html
77+
static PyMethodDef module_methods[] = {
78+
{.ml_name = "os_log_with_type",
79+
.ml_meth = (PyCFunction)py_os_log_with_type,
80+
.ml_flags = METH_VARARGS,
81+
.ml_doc = os_log_with_type_doc},
82+
{.ml_name = "os_log_create",
83+
.ml_meth = (PyCFunction)py_os_log_create,
84+
.ml_flags = METH_VARARGS,
85+
.ml_doc = os_log_create_doc},
86+
{.ml_name = NULL} /* sentinel */
87+
};
88+
89+
static struct PyModuleDef module_definition = {.m_base = PyModuleDef_HEAD_INIT,
90+
.m_name = "_pyoslog",
91+
.m_doc = _pyoslog_doc,
92+
.m_size = -1,
93+
.m_methods = module_methods,
94+
/* m_reload = */ NULL,
95+
.m_traverse = NULL,
96+
.m_clear = NULL,
97+
.m_free = NULL};
98+
99+
PyObject *PyInit__pyoslog(void) {
100+
PyObject *module = PyModule_Create(&module_definition);
101+
if (module == NULL) {
102+
return NULL;
103+
}
104+
105+
// the default log type (no named subsystem or facility)
106+
// see note above for why we don't use the actual value
107+
PyModule_AddIntConstant(module, "OS_LOG_DEFAULT", 0);
108+
109+
// standard log types
110+
PyModule_AddIntConstant(module, "OS_LOG_TYPE_DEFAULT", OS_LOG_TYPE_DEFAULT);
111+
PyModule_AddIntConstant(module, "OS_LOG_TYPE_INFO", OS_LOG_TYPE_DEFAULT);
112+
PyModule_AddIntConstant(module, "OS_LOG_TYPE_DEBUG", OS_LOG_TYPE_DEBUG);
113+
PyModule_AddIntConstant(module, "OS_LOG_TYPE_ERROR", OS_LOG_TYPE_ERROR);
114+
PyModule_AddIntConstant(module, "OS_LOG_TYPE_FAULT", OS_LOG_TYPE_FAULT);
115+
116+
return module;
117+
}

setup.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import os
2+
from setuptools import setup, Extension
3+
4+
NAME = 'pyoslog'
5+
6+
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+
exec(version_file.read(), about)
10+
11+
with open('README.md') as readme_file:
12+
readme = readme_file.read()
13+
14+
# https://setuptools.pypa.io/en/latest/references/keywords.html
15+
setup(
16+
name=NAME,
17+
version=about['__version__'],
18+
description=about['__description__'],
19+
long_description=readme,
20+
long_description_content_type='text/markdown',
21+
author=about['__author__'],
22+
author_email=about['__author_email__'],
23+
url=about['__url__'],
24+
25+
platforms=['darwin'],
26+
ext_modules=[Extension('_' + NAME, ['%s/_%s.c' % (NAME, NAME)])],
27+
28+
package_data={'': ['LICENSE']},
29+
include_package_data=True,
30+
31+
license=about['__license__'],
32+
classifiers=[
33+
'Development Status :: 4 - Beta',
34+
'Operating System :: MacOS',
35+
'Intended Audience :: Developers',
36+
'License :: OSI Approved :: Apache Software License',
37+
'Programming Language :: Python'
38+
]
39+
)

0 commit comments

Comments
 (0)