python-3.x - C 扩展 - 如何将 printf 重定向到 python 记录器?
问题描述
我有一个简单的 C 扩展(参见下面的示例),有时使用 printf 函数打印。我正在寻找一种方法来包装从 C 扩展对函数的调用,以便所有这些 printfs 都将被重定向到我的 python 记录器。
你好ç:
#include <Python.h>
static PyObject* hello(PyObject* self)
{
printf("example print from a C code\n");
return Py_BuildValue("");
}
static char helloworld_docs[] =
"helloworld(): Any message you want to put here!!\n";
static PyMethodDef helloworld_funcs[] = {
{"hello", (PyCFunction)hello,
METH_NOARGS, helloworld_docs},
{NULL}
};
static struct PyModuleDef cModPyDem =
{
PyModuleDef_HEAD_INIT,
"helloworld",
"Extension module example!",
-1,
helloworld_funcs
};
PyMODINIT_FUNC PyInit_helloworld(void)
{
return PyModule_Create(&cModPyDem);
};
设置.py:
from distutils.core import setup, Extension
setup(name = 'helloworld', version = '1.0', \
ext_modules = [Extension('helloworld', ['hello.c'])])
使用第一次运行
python3 setup.py 安装
接着:
import helloworld
helloworld.hello()
我希望能够做这样的事情:
with redirect_to_logger(my_logger)
helloworld.hello()
编辑:我看到了一些帖子,展示了如何使 C 中的打印静音,但我无法从中弄清楚如何在 python 中捕获打印。此类帖子的示例:Redirect stdout from python for C calls
我认为这个问题没有引起太大的关注,因为我可能问的太多了,所以我不再关心日志记录......如何在 python 中捕获 C 打印?到列表或其他任何东西。
编辑 所以我能够在某种程度上实现我想要的工作代码 - 将 c printf 重定向到 python 记录器:
import select
import threading
import time
import logging
import re
from contextlib import contextmanager
from wurlitzer import pipes
from helloworld import hello
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
class CPrintsHandler(threading.Thread):
def __init__(self, std, poll_std, err, poll_err, logger):
super(CPrintsHandler, self).__init__()
self.std = std
self.poll_std = poll_std
self.err = err
self.poll_err = poll_err
self.logger = logger
self.stop_event = threading.Event()
def stop(self):
self.stop_event.set()
def run(self):
while not self.stop_event.is_set():
# How can I poll both std and err at the same time?
if self.poll_std.poll(1):
line = self.std.readline()
if line:
self.logger.debug(line.strip())
if self.poll_err.poll(1):
line = self.err.readline()
if line:
self.logger.debug(line.strip())
@contextmanager
def redirect_to_logger(some_logger):
handler = None
try:
with pipes() as (std, err):
poll_std = select.poll()
poll_std.register(std, select.POLLIN)
poll_err = select.poll()
poll_err.register(err, select.POLLIN)
handler = CPrintsHandler(std, poll_std, err, poll_err, some_logger)
handler.start()
yield
finally:
if handler:
time.sleep(0.1) # why do I have to sleep here for the foo prints to finish?
handler.stop()
handler.join()
def foo():
logger.debug('logger print from foo()')
hello()
def main():
with redirect_to_logger(logger):
# I don't want the logs from here to be redirected as well, only printf.
logger.debug('logger print from main()')
foo()
main()
但我有几个问题:
python 日志也被 CPrintsHandler 重定向和捕获。有没有办法避免这种情况?
打印的顺序不完全正确:
python3 redirect_c_example_for_stackoverflow.py
2020-08-18 19:50:47,732 - 根 - 调试 - 从 C 代码打印的示例
2020-08-18 19:50:47,733 - 根 - 调试 - 2020-08-18 19:50:47,731 - 根 - 调试 - 来自 main() 的记录器打印
2020-08-18 19:50:47,733 - 根 - 调试 - 2020-08-18 19:50:47,731 - 根 - 调试 - 来自 foo() 的记录器打印
此外,记录器打印全部出错,也许我轮询它们的方式导致了这个顺序。
- 我对python中的select不太熟悉,不确定是否有办法同时轮询std和err并打印先有东西的那个。
解决方案
在 Linux 上,您可以使用wurlitzer
which 来捕获来自 的输出fprint
,例如:
from wurlitzer import pipes
with pipes() as (out, err):
helloworld.hello()
out.read()
#'example print from a C code\n'
wurlitzer
基于Eli Bendersky 的这篇文章,如果您不喜欢依赖第三方库,可以使用该代码。
遗憾的是,wurlitzer
文章中的代码仅适用于 Linux(以及可能的 MacOS)。
这是一个使用 Eli 的方法作为 Cython 扩展的 Windows 原型(可以从我的 github安装原型的改进版本)(如果需要,可能可以翻译成ctypes
):
%%cython
import io
import os
cdef extern from *:
"""
#include <windows.h>
#include <io.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
int open_temp_file() {
TCHAR lpTempPathBuffer[MAX_PATH+1];//path+NULL
// Gets the temp path env string (no guarantee it's a valid path).
DWORD dwRetVal = GetTempPath(MAX_PATH, // length of the buffer
lpTempPathBuffer); // buffer for path
if(dwRetVal > MAX_PATH || (dwRetVal == 0))
{
return -1;
}
// Generates a temporary file name.
TCHAR szTempFileName[MAX_PATH + 1];//path+NULL
DWORD uRetVal = GetTempFileName(lpTempPathBuffer, // directory for tmp files
TEXT("tmp"), // temp file name prefix
0, // create unique name
szTempFileName); // buffer for name
if (uRetVal == 0)
{
return -1;
}
HANDLE tFile = CreateFile((LPTSTR)szTempFileName, // file name
GENERIC_READ | GENERIC_WRITE, // first we write than we read
0, // do not share
NULL, // default security
CREATE_ALWAYS, // overwrite existing
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, // "temporary" temporary file, see https://docs.microsoft.com/en-us/archive/blogs/larryosterman/its-only-temporary
NULL); // no template
if (tFile == INVALID_HANDLE_VALUE) {
return -1;
}
return _open_osfhandle((intptr_t)tFile, _O_APPEND | _O_TEXT);
}
int replace_stdout(int temp_fileno)
{
fflush(stdout);
int old;
int cstdout = _fileno(stdout);
old = _dup(cstdout); // "old" now refers to "stdout"
if (old == -1)
{
return -1;
}
if (-1 == _dup2(temp_fileno, cstdout))
{
return -1;
}
return old;
}
int restore_stdout(int old_stdout){
fflush(stdout);
// Restore original stdout
int cstdout = _fileno(stdout);
return _dup2(old_stdout, cstdout);
}
void rewind_fd(int fd) {
_lseek(fd, 0L, SEEK_SET);
}
"""
int open_temp_file()
int replace_stdout(int temp_fileno)
int restore_stdout(int old_stdout)
void rewind_fd(int fd)
void close_fd "_close" (int fd)
cdef class CStdOutCapture():
cdef int tmpfile_fd
cdef int old_stdout_fd
def start(self): #start capturing
self.tmpfile_fd = open_temp_file()
self.old_stdout_fd = replace_stdout(self.tmpfile_fd)
def stop(self): # stops capturing, frees resources and returns the content
restore_stdout(self.old_stdout_fd)
rewind_fd(self.tmpfile_fd) # need to read from the beginning
buffer = io.TextIOWrapper(os.fdopen(self.tmpfile_fd, 'rb'))
result = buffer.read()
close_fd(self.tmpfile_fd)
return result
现在:
b = CStdOutCapture()
b.start()
helloworld.hello()
out = b.stop()
print("HERE WE GO:", out)
# HERE WE GO: example print from a C code
推荐阅读
- typescript - 全局注册Can组件时casl + vue上的Vue错误
- c# - 页面(视图)之间的 C# WPF 导航
- sql - 将特殊字符 SQL 编码为 R 并返回
- c# - 是否可以使用控制台应用程序在 .net framework 4.5 中实现相扑逻辑?如果是,我们如何实施
- loops - 通过python中的一组行迭代一个函数
- php - 适用于 PHP 客户端的 Azure 服务总线队列 ReceiveBatch
- c# - 我是使用 C# 创建 API 的新手,遇到错误“严重性代码描述项目文件行抑制状态警告 CS0108”
- php - Wordpress 类别存档页面显示注意:未定义的偏移量:错误
- performance - JMeter 查看结果树侦听器显示重复的 https 请求
- python - Python检查句子中是否存在短语