[英]Erlport/Python STDOUT capture to Elixir
I'm trying to pipe STDOUT from Python/Erlport back to Elixir.我正在尝试将 STDOUT 从 Python/Erlport 通过管道传输回 Elixir。 I've got
:python
calls working fine, I just want to send the STDOUT stuff from Python back to Elixir for logging but I can't wrap my head around how to achieve that.我有
:python
调用工作正常,我只想将 Python 中的 STDOUT 内容发送回 Elixir 进行日志记录,但我无法理解如何实现这一点。 I know it'spossible even though I'm using Python 2.7.我知道即使我使用的是 Python 2.7,这也是可能的。
I've got a Genserver wrapper around the :python
module so that my call works like so:我在
:python
模块周围有一个 Genserver 包装器,以便我的调用工作如下:
pid = Python.start()
Python.call(pid, :bridge, :register_handler, [self()])
Python.call just looks like this: Python.call 看起来像这样:
def call(pid, module, function, args \\ []) do
:python.call(pid, module, function, args)
end
Anything from :bridge
(ie, bridge.py
) is lost to STDOUT unless I explicitly return something (obviously halting the function).任何来自
:bridge
(即bridge.py
)的东西都会丢失到 STDOUT 除非我明确返回某些东西(显然是停止函数)。 What can I do to capture STDOUT?我可以做什么来捕获标准输出?
My idea was to call something like Python.call(pid, :builtins, :print, [self()])
but that results in a bunch of errors and I really don't know if that's the right direction at all.我的想法是调用
Python.call(pid, :builtins, :print, [self()])
但这会导致一堆错误,我真的不知道这是否是正确的方向。
I actually want to pipe it into a Phoenix channel, but that's the easy part (I hope).我实际上想把它传送到凤凰频道,但这是简单的部分(我希望)。 Any advice?
有什么建议吗? Thanks.
谢谢。
My idea was to call something like
Python.call(pid, :builtins, :print, [self()])
but that results in a bunch of errors and I really don't know if that's the right direction at all.我的想法是调用
Python.call(pid, :builtins, :print, [self()])
但这会导致一堆错误,我真的不知道这是否是正确的方向。
self()
is not where the output goes--rather self()
is the argument for print
, ie what python will print out. self()
不是输出去的地方——而是self()
是print
的参数,即 python 将打印出来的内容。
I think erlport
can only handle MFA calls(module, function, argument), and because print
isn't a function in python 2.7
, I think you need to wrap a function around print
, eg:我认为
erlport
只能处理 MFA 调用(模块、函数、参数),并且因为print
不是python 2.7
的函数,所以我认为您需要在print
周围包装一个函数,例如:
myprint.py:我的打印文件:
def print_this(str):
print str
I just want to send the STDOUT stuff from Python back to Elixir for logging but I can't wrap my head around how to achieve that.
我只是想将 Python 中的 STDOUT 内容发送回 Elixir 进行日志记录,但我无法理解如何实现这一点。 I know it's possible even though I'm using Python 2.7
我知道即使我使用的是 Python 2.7 也是可能的
Theerlport docs say: erlport文档说:
As a convenient feature ErlPort also supports redirection of Python`s STDOUT to Erlang...
作为一个方便的特性,ErlPort 还支持将 Python 的 STDOUT 重定向到 Erlang...
That appears to be the default setup, so you don't have to do anything to get python stdout to be redirected to elixir stdout.这似乎是默认设置,因此您无需执行任何操作即可将 python 标准输出重定向到 elixir 标准输出。 The question then becomes: "How do you log elixir stdout to a file?"
那么问题就变成了:“如何将 elixir stdout 记录到文件中?”
I'm able to log elixir stdout
to a file like this:我可以将 elixir
stdout
记录到这样的文件中:
friends.ex:朋友.ex:
defmodule Friends do
use Export.Python
def go do
#Get path to logfile:
priv_path = :code.priv_dir(:friends)
logfile_path = Path.join([priv_path, "log", "mylog.log"])
#Redirect stdout:
{:ok, io_pid} = File.open(logfile_path, [:append])
Process.group_leader(self(), io_pid)
#Send output to stdout:
IO.puts "Am I in the log file??!"
python_path = Path.expand("lib/python")
{:ok, py} = Python.start(
python: "python2.7",
python_path: python_path
)
Python.call(py, "myprint", "print_this", ["hello world!"])
Python.call(py, "myprint", "print_this", ["goodbye..."])
Python.stop(py)
end
end
This is my directory structure:这是我的目录结构:
friends
/lib
/friends
/python
myprint.py
friends.ex
/priv
/log
mylog.log
In iex:在 iex 中:
~/elixir_programs/friends$ iex -S mix
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Compiling 1 file (.ex)
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Friends.go
iex(2)>
In the log file:在日志文件中:
Am I in the log file??!
hello world!
goodbye...
[33m[36m:ok[0m[33m[0m
(I don't know what that junk is on the last line. Edit: Hmmm...it's the atom :ok
surrounded by some other stuff.) (我不知道最后一行上的垃圾是什么。编辑:嗯......它是原子
:ok
被其他一些东西包围。)
If I comment out everything inside go()
above the python_path line, then I get:如果我在 python_path 行上方注释掉
go()
所有内容,那么我会得到:
iex(1)> Friends.go
hello world!
goodbye...
:ok
In erlang/elixir file I/O is handled by starting a process to which requests are sent to either write to the file or read the file.在 erlang/elixir 文件 I/O 是通过启动一个进程来处理的,该进程将请求发送到写入文件或读取文件。 I think stdout gets sent to whatever process is the
group_leader
, and if the process handling file I/O is the group_leader, then stdout gets sent to the file.我认为 stdout 会被发送到
group_leader
任何进程,如果处理文件 I/O 的进程是 group_leader,那么 stdout 会被发送到文件。
I don't know if messing with the group_leader
will screw things up when you are using a GenServer
.我不知道当您使用
GenServer
时,弄乱group_leader
是否会搞砸。 There is a warning in the erlang docs : erlang 文档中有一个警告:
The group leader should be rarely changed in applications with a supervision tree, because OTP assumes the group leader of their processes is their application master.
在具有监督树的应用程序中,组长应该很少更改,因为 OTP 假定其进程的组长是它们的应用程序主人。
. .
For anyone else getting stuck with this kinda thing: since I've got a GenServer around the :python
instance, I've just leveraged handle_info
:对于其他被这种事情卡住的人:因为我在
:python
实例周围有一个 GenServer,我只是利用了handle_info
:
def handle_info({:python, message}, session) do
message |> String.split("\n", trim: true)
SomeWeb.Endpoint.broadcast("log", "update", %{body: message})
{:stop, :normal, session}
end
Detail细节
To more fully outline my solution as @7stud advised, I'll include the wider approach based on erlport
and this great post .为了按照@7stud 的建议更全面地概述我的解决方案,我将包括基于
erlport
的更广泛的方法和这篇很棒的文章。 Accordingly, I've got a Python
module that looks like this:因此,我有一个看起来像这样的
Python
模块:
defmodule App.Python do
@doc """
Python instance pointing to priv/python.
"""
def start() do
path = [
:code.priv_dir(:prefect),
"python"
]|> Path.join()
{:ok, pid} = :python.start([
{:python_path, to_charlist(path)}
])
pid
end
def call(pid, module, function, args \\ []) do
:python.call(pid, module, function, args)
end
def cast(pid, message) do
:python.cast(pid, message)
end
def stop(pid) do
:python.stop(pid)
end
end
It's called from a GenServer that handles its spawning and termination:它是从处理其生成和终止的 GenServer 调用的:
defmodule App.PythonServer do
@doc """
Receives async. messages from Python instance.
"""
use GenServer
alias App.Python
def start_link() do
GenServer.start_link(__MODULE__, [])
end
def init(_args) do
pid = Python.start()
Python.call(pid, :bridge, :register_handler, [self()])
App.Application.broadcast_change
{:ok, pid}
end
def cast_draw(id) do
{:ok, pid} = start_link()
GenServer.cast(pid, {:id, id})
end
def call_draw(id) do
{:ok, pid} = start_link()
GenServer.call(pid, {:id, id}, 10_000)
end
def handle_call({:id, id}, _from, session) do
result = Python.call(session, :bridge, :draw, [id])
{:reply, result, session}
end
def handle_cast({:id, id}, session) do
Python.cast(session, id)
{:noreply, session}
end
def handle_info({:python, message}, session) do
msg = message |> format_response
{:ok, time} = Timex.now |> Timex.format("{h12}:{m}{am} {D}/{M}/{YYYY}")
AppWeb.Endpoint.broadcast("log", "update", %{time: time, body: msg, process: message})
{:stop, :normal, session}
end
def terminate(_reason, session) do
Python.stop(session)
App.Application.broadcast_change
:ok
end
defp format_response(message) do
if String.contains? message, "[result] Sent" do
message |> String.split("\n", trim: true) |> Enum.at(-2)
else
message |> String.split("\n", trim: true) |> Enum.take(-12) |> Enum.join("\n")
end
end
end
You can see at the end if STDOUT doesn't return a certain string from bridge.py
(or any other Python module) it'll return a stacktrace.您可以在最后看到如果 STDOUT 没有从
bridge.py
(或任何其他 Python 模块)返回某个字符串, bridge.py
返回一个bridge.py
。 Speaking of, bridge.py
looks like this:说起来,
bridge.py
看起来像这样:
import os
import sys
import subprocess
from erlport.erlang import set_message_handler, cast
from erlport.erlterms import Atom
message_handler = None # reference to the elixir process to send
cmd = "xvfb-run -a python"
py = os.path.join("/home/ubuntu/app/priv/python/export.py")
def cast_message(pid, message):
cast(pid, message)
def register_handler(pid):
global message_handler
message_handler = pid
def handle_message(id):
try:
result = draw(id)
print result
if message_handler:
cast_message(message_handler, (Atom('python'), result))
except Exception, error:
print error
if message_handler:
cast_message(message_handler, (Atom('python'), error))
pass
def draw(id):
proc = subprocess.check_output(
"{0} {1} {2}".format(cmd, py, id), stderr = subprocess.STDOUT, shell = True
)
return proc
set_message_handler(handle_message)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.