@@ -28,72 +28,130 @@ Many thanks to `nnathan <https://github.com/nnathan>`_ for this.
28
28
We import the module and define C equivalent logging functions that are
29
29
compatible with the `*printf ` family.
30
30
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
+
31
36
.. code-block :: c
32
37
38
+ #define PPY_SSIZE_T_CLEAN
33
39
#include <Python.h>
40
+ /* For va_start, va_end */
34
41
#include <stdarg.h>
35
42
36
43
/* 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:
39
56
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. */
42
61
static PyObject *g_logger = NULL;
43
62
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
+
45
70
static PyObject *py_get_logger(char *logger_name) {
71
+ assert(g_logging_module);
46
72
PyObject *logger = NULL;
47
73
48
- logger = PyObject_CallMethod(g_logging_import , "getLogger", "s", logger_name);
74
+ logger = PyObject_CallMethod(g_logging_module , "getLogger", "s", logger_name);
49
75
if (logger == NULL) {
50
76
const char *err_msg = "failed to call logging.getLogger";
51
77
PyErr_SetString(PyExc_RuntimeError, err_msg);
52
78
}
79
+ /*
80
+ fprintf(stdout, "%s()#%d logger=0x%p\n", __FUNCTION__, __LINE__, (void *)logger);
81
+ */
53
82
return logger;
54
83
}
55
84
56
- /* main interface to logging function */
85
+ Now the main interface to logging function:
86
+
87
+ .. code-block :: c
88
+
57
89
static PyObject *
58
90
py_log_msg(int log_level, char *printf_fmt, ...) {
91
+ assert(g_logger);
92
+ assert(!PyErr_Occurred());
59
93
PyObject *log_msg = NULL;
60
94
PyObject *ret = NULL;
61
95
va_list fmt_args;
62
96
63
97
va_start(fmt_args, printf_fmt);
64
98
log_msg = PyUnicode_FromFormatV(printf_fmt, fmt_args);
65
99
va_end(fmt_args);
100
+
66
101
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());
92
131
}
93
132
Py_DECREF(log_msg);
94
133
return ret;
95
134
}
96
135
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
+
97
155
static PyObject *
98
156
py_log_message(PyObject *Py_UNUSED(module), PyObject *args) {
99
157
int log_level;
@@ -103,19 +161,37 @@ compatible with the `*printf` family.
103
161
return NULL;
104
162
}
105
163
return py_log_msg(log_level, "%s", message);
106
- // Py_RETURN_NONE;
107
164
}
108
165
166
+ cLogging Module
167
+ ----------------------
168
+
169
+ Setup the module functions:
170
+
171
+ .. code-block :: c
172
+
109
173
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 */
117
189
};
118
190
191
+ The module definition:
192
+
193
+ .. code-block :: c
194
+
119
195
static PyModuleDef cLogging = {
120
196
PyModuleDef_HEAD_INIT,
121
197
.m_name = "cLogging",
@@ -124,6 +200,10 @@ compatible with the `*printf` family.
124
200
.m_methods = logging_methods,
125
201
};
126
202
203
+ The module initialisation, this is where the logging module is imported with ``PyImport_ImportModule() ``:
204
+
205
+ .. code-block :: c
206
+
127
207
PyMODINIT_FUNC PyInit_cLogging(void) {
128
208
PyObject *m = PyModule_Create(&cLogging);
129
209
if (! m) {
@@ -173,12 +253,86 @@ compatible with the `*printf` family.
173
253
return m;
174
254
}
175
255
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
+
176
275
To simply use the interface defined in the above function, use it like the `printf ` family of functions:
177
276
178
277
.. code-block :: c
179
278
180
279
py_log_msg(WARNING, "error code: %d", 10);
181
280
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
+
182
336
.. _PyEval_GetFrame() : https://docs.python.org/3/c-api/reflection.html#c.PyEval_GetFrame
183
337
.. _PyFrameObject : https://docs.python.org/3/c-api/frame.html#c.PyFrameObject
184
338
.. _Frame API : https://docs.python.org/3/c-api/frame.html
0 commit comments