Skip to content

Commit a22de63

Browse files
committed
Fix logging.rst and cLogging.c. Add setuptools to requirements.txt
1 parent b6abf1a commit a22de63

File tree

4 files changed

+234
-56
lines changed

4 files changed

+234
-56
lines changed

Diff for: doc/sphinx/source/logging.rst

+194-40
Original file line numberDiff line numberDiff line change
@@ -28,72 +28,130 @@ Many thanks to `nnathan <https://github.com/nnathan>`_ for this.
2828
We import the module and define C equivalent logging functions that are
2929
compatible with the `*printf` family.
3030

31+
Logging C Declarations
32+
----------------------
33+
34+
First define the log levels, ``#define`` is used so the that switch case does not issue a compile time non-const error:
35+
3136
.. code-block:: c
3237
38+
#define PPY_SSIZE_T_CLEAN
3339
#include <Python.h>
40+
/* For va_start, va_end */
3441
#include <stdarg.h>
3542
3643
/* logging levels defined by logging module
37-
* NOTE: In Python logging FATAL = CRITICAL */
38-
enum { LOGGING_INFO, LOGGING_WARNING, LOGGING_ERROR, LOGGING_FATAL, LOGGING_DEBUG, LOGGING_EXCEPTION };
44+
* From: https://docs.python.org/3/library/logging.html#logging-levels */
45+
#define LOGGING_DEBUG 10
46+
#define LOGGING_INFO 20
47+
#define LOGGING_WARNING 30
48+
#define LOGGING_ERROR 40
49+
#define LOGGING_CRITICAL 50
50+
#define LOGGING_EXCEPTION 60
51+
52+
Logging C Globals
53+
----------------------
54+
55+
Then two globals, the first is the imported logging module, the next the current logger:
3956

40-
/* module globals */
41-
static PyObject *g_logging_import = NULL;
57+
.. code-block:: c
58+
59+
/* This modules globals */
60+
static PyObject *g_logging_module = NULL; /* Initialise by PyInit_cLogging() below. */
4261
static PyObject *g_logger = NULL;
4362
44-
/* Get a logger object from the logging module. */
63+
Logging C Functions
64+
----------------------
65+
66+
Now a function to get a logger object from the logging module:
67+
68+
.. code-block:: c
69+
4570
static PyObject *py_get_logger(char *logger_name) {
71+
assert(g_logging_module);
4672
PyObject *logger = NULL;
4773
48-
logger = PyObject_CallMethod(g_logging_import, "getLogger", "s", logger_name);
74+
logger = PyObject_CallMethod(g_logging_module, "getLogger", "s", logger_name);
4975
if (logger == NULL) {
5076
const char *err_msg = "failed to call logging.getLogger";
5177
PyErr_SetString(PyExc_RuntimeError, err_msg);
5278
}
79+
/*
80+
fprintf(stdout, "%s()#%d logger=0x%p\n", __FUNCTION__, __LINE__, (void *)logger);
81+
*/
5382
return logger;
5483
}
5584
56-
/* main interface to logging function */
85+
Now the main interface to logging function:
86+
87+
.. code-block:: c
88+
5789
static PyObject *
5890
py_log_msg(int log_level, char *printf_fmt, ...) {
91+
assert(g_logger);
92+
assert(!PyErr_Occurred());
5993
PyObject *log_msg = NULL;
6094
PyObject *ret = NULL;
6195
va_list fmt_args;
6296
6397
va_start(fmt_args, printf_fmt);
6498
log_msg = PyUnicode_FromFormatV(printf_fmt, fmt_args);
6599
va_end(fmt_args);
100+
66101
if (log_msg == NULL) {
67-
/* fail silently. */
68-
return ret;
69-
}
70-
/* call function depending on loglevel */
71-
switch (log_level) {
72-
case LOGGING_INFO:
73-
ret = PyObject_CallMethod(g_logger, "info", "O", log_msg);
74-
break;
75-
case LOGGING_WARNING:
76-
ret = PyObject_CallMethod(g_logger, "warning", "O", log_msg);
77-
break;
78-
case LOGGING_ERROR:
79-
ret = PyObject_CallMethod(g_logger, "error", "O", log_msg);
80-
break;
81-
case LOGGING_FATAL:
82-
ret = PyObject_CallMethod(g_logger, "fatal", "O", log_msg);
83-
break;
84-
case LOGGING_DEBUG:
85-
ret = PyObject_CallMethod(g_logger, "debug", "O", log_msg);
86-
break;
87-
case LOGGING_EXCEPTION:
88-
ret = PyObject_CallMethod(g_logger, "exception", "O", log_msg);
89-
break;
90-
default:
91-
break;
102+
/* fail. */
103+
ret = PyObject_CallMethod(
104+
g_logger,
105+
"critical",
106+
"O", "Unable to create log message."
107+
);
108+
} else {
109+
/* call function depending on loglevel */
110+
switch (log_level) {
111+
case LOGGING_DEBUG:
112+
ret = PyObject_CallMethod(g_logger, "debug", "O", log_msg);
113+
break;
114+
case LOGGING_INFO:
115+
ret = PyObject_CallMethod(g_logger, "info", "O", log_msg);
116+
break;
117+
case LOGGING_WARNING:
118+
ret = PyObject_CallMethod(g_logger, "warning", "O", log_msg);
119+
break;
120+
case LOGGING_ERROR:
121+
ret = PyObject_CallMethod(g_logger, "error", "O", log_msg);
122+
break;
123+
case LOGGING_CRITICAL:
124+
ret = PyObject_CallMethod(g_logger, "critical", "O", log_msg);
125+
break;
126+
default:
127+
ret = PyObject_CallMethod(g_logger, "critical", "O", log_msg);
128+
break;
129+
}
130+
assert(!PyErr_Occurred());
92131
}
93132
Py_DECREF(log_msg);
94133
return ret;
95134
}
96135
136+
A function to set a log level:
137+
138+
.. code-block:: c
139+
140+
static PyObject *
141+
py_log_set_level(PyObject *Py_UNUSED(module), PyObject *args) {
142+
assert(g_logger);
143+
PyObject *py_log_level;
144+
145+
if (!PyArg_ParseTuple(args, "O", &py_log_level)) {
146+
return NULL;
147+
}
148+
return PyObject_CallMethod(g_logger, "setLevel", "O", py_log_level);
149+
}
150+
151+
And the main function to log a message:
152+
153+
.. code-block:: c
154+
97155
static PyObject *
98156
py_log_message(PyObject *Py_UNUSED(module), PyObject *args) {
99157
int log_level;
@@ -103,19 +161,37 @@ compatible with the `*printf` family.
103161
return NULL;
104162
}
105163
return py_log_msg(log_level, "%s", message);
106-
// Py_RETURN_NONE;
107164
}
108165
166+
cLogging Module
167+
----------------------
168+
169+
Setup the module functions:
170+
171+
.. code-block:: c
172+
109173
static PyMethodDef logging_methods[] = {
110-
{
111-
"log",
112-
(PyCFunction) py_log_message,
113-
METH_VARARGS,
114-
"Log a message."
115-
},
116-
{NULL, NULL, 0, NULL} /* Sentinel */
174+
/* ... */
175+
{
176+
"py_log_set_level",
177+
(PyCFunction) py_log_set_level,
178+
METH_VARARGS,
179+
"Set the logging level."
180+
},
181+
{
182+
"log",
183+
(PyCFunction) py_log_message,
184+
METH_VARARGS,
185+
"Log a message."
186+
},
187+
/* ... */
188+
{NULL, NULL, 0, NULL} /* Sentinel */
117189
};
118190
191+
The module definition:
192+
193+
.. code-block:: c
194+
119195
static PyModuleDef cLogging = {
120196
PyModuleDef_HEAD_INIT,
121197
.m_name = "cLogging",
@@ -124,6 +200,10 @@ compatible with the `*printf` family.
124200
.m_methods = logging_methods,
125201
};
126202
203+
The module initialisation, this is where the logging module is imported with ``PyImport_ImportModule()``:
204+
205+
.. code-block:: c
206+
127207
PyMODINIT_FUNC PyInit_cLogging(void) {
128208
PyObject *m = PyModule_Create(&cLogging);
129209
if (! m) {
@@ -173,12 +253,86 @@ compatible with the `*printf` family.
173253
return m;
174254
}
175255
256+
Create in the ``setup.py``:
257+
258+
.. code-block:: python
259+
260+
Extension(name=f"{PACKAGE_NAME}.Logging.cLogging",
261+
include_dirs=[],
262+
sources=["src/cpy/Logging/cLogging.c", ],
263+
extra_compile_args=extra_compile_args_c,
264+
language='c',
265+
),
266+
267+
And run ``python setup.py develop``.
268+
269+
Using and Testing
270+
-----------------
271+
272+
Using From C
273+
^^^^^^^^^^^^^
274+
176275
To simply use the interface defined in the above function, use it like the `printf` family of functions:
177276

178277
.. code-block:: c
179278
180279
py_log_msg(WARNING, "error code: %d", 10);
181280
281+
Using From Python
282+
^^^^^^^^^^^^^^^^^
283+
284+
.. code-block:: python
285+
286+
from cPyExtPatt.Logging import cLogging
287+
288+
cLogging.log(cLogging.ERROR, "Test log message")
289+
290+
There are various tests in ``tests/unit/test_c_logging.py``.
291+
As pytest swallows logging messages there is a ``main()`` function in that script so you can run it from the command
292+
line:
293+
294+
.. code-block:: python
295+
296+
def main():
297+
logger.setLevel(logging.DEBUG)
298+
logger.info('main')
299+
logger.warning('Test warning message XXXX')
300+
logger.debug('Test debug message XXXX')
301+
logger.info('_test_logging')
302+
test_logging()
303+
print()
304+
print(cLogging)
305+
print(dir(cLogging))
306+
print()
307+
logger.info('cLogging.log():')
308+
cLogging.py_log_set_level(10)
309+
cLogging.log(cLogging.ERROR, "cLogging.log(): Test log message")
310+
311+
return 0
312+
313+
314+
if __name__ == '__main__':
315+
exit(main())
316+
317+
Here is an example output:
318+
319+
.. code-block:: bash
320+
321+
$python tests/unit/test_c_logging.py
322+
2025-03-07 11:49:23,994 7064 INFO main
323+
2025-03-07 11:49:23,994 7064 WARNING Test warning message XXXX
324+
2025-03-07 11:49:23,994 7064 DEBUG Test debug message XXXX
325+
2025-03-07 11:49:23,994 7064 INFO _test_logging
326+
2025-03-07 11:49:23,994 7064 WARNING Test warning message XXXX
327+
2025-03-07 11:49:23,994 7064 DEBUG Test debug message XXXX
328+
329+
<module 'cPyExtPatt.Logging.cLogging' from 'PythonExtensionPatterns/cPyExtPatt/Logging/cLogging.cpython-313-darwin.so'>
330+
['CRITICAL', 'DEBUG', 'ERROR', 'EXCEPTION', 'INFO', 'WARNING', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'c_file_line_function', 'log', 'py_file_line_function', 'py_log_set_level']
331+
332+
2025-03-07 11:49:23,994 7064 INFO cLogging.log():
333+
2025-03-07 11:49:23,994 7064 ERROR cLogging.log(): Test log message
334+
335+
182336
.. _PyEval_GetFrame(): https://docs.python.org/3/c-api/reflection.html#c.PyEval_GetFrame
183337
.. _PyFrameObject: https://docs.python.org/3/c-api/frame.html#c.PyFrameObject
184338
.. _Frame API: https://docs.python.org/3/c-api/frame.html

Diff for: requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ Sphinx>=7.4
22
psutil>=6.0
33
pymemtrace>=0.2
44
pytest>=8.3
5+
setuptools

Diff for: src/cpy/Logging/cLogging.c

+7-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
#define LOGGING_EXCEPTION 60
1919

2020
/* This modules globals */
21-
static PyObject *g_logging_module = NULL;
21+
static PyObject *g_logging_module = NULL; /* Initialise by PyInit_cLogging() below. */
2222
static PyObject *g_logger = NULL;
2323

2424
/* Get a logger object from the logging module. */
@@ -31,7 +31,9 @@ static PyObject *py_get_logger(char *logger_name) {
3131
const char *err_msg = "failed to call logging.getLogger";
3232
PyErr_SetString(PyExc_RuntimeError, err_msg);
3333
}
34+
/*
3435
fprintf(stdout, "%s()#%d logger=0x%p\n", __FUNCTION__, __LINE__, (void *)logger);
36+
*/
3537
return logger;
3638
}
3739

@@ -43,18 +45,18 @@ py_log_msg(int log_level, char *printf_fmt, ...) {
4345
PyObject *log_msg = NULL;
4446
PyObject *ret = NULL;
4547
va_list fmt_args;
46-
48+
/*
4749
fprintf(stdout, "%s()#%d g_logger=0x%p\n", __FUNCTION__, __LINE__, (void *)g_logger);
4850
fprintf(stdout, "%s()#%d log_level=%d print_fmt=\"%s\"\n", __FUNCTION__, __LINE__, log_level, printf_fmt);
49-
51+
*/
5052
va_start(fmt_args, printf_fmt);
5153
log_msg = PyUnicode_FromFormatV(printf_fmt, fmt_args);
5254
va_end(fmt_args);
53-
55+
/*
5456
fprintf(stdout, "%s()#%d log_message: \"", __FUNCTION__, __LINE__);
5557
PyObject_Print(log_msg, stdout, Py_PRINT_RAW);
5658
fprintf(stdout, "\"\n");
57-
59+
*/
5860
if (log_msg == NULL) {
5961
/* fail. */
6062
ret = PyObject_CallMethod(
@@ -86,7 +88,6 @@ py_log_msg(int log_level, char *printf_fmt, ...) {
8688
}
8789
assert(!PyErr_Occurred());
8890
}
89-
// PyObject_CallMethod(g_logger, "flush", "");
9091
Py_DECREF(log_msg);
9192
return ret;
9293
}

0 commit comments

Comments
 (0)