Erlport/Python STDOUT 捕获到 Elixir
Erlport/Python STDOUT capture to Elixir
我正在尝试将 STDOUT 从 Python/Erlport 传送回 Elixir。我有 :python
调用工作正常,我只是想将 Python 中的 STDOUT 内容发送回 Elixir 进行日志记录,但我不知道如何实现这一点。我知道它是 possible,即使我使用的是 Python 2.7.
我在 :python
模块周围有一个 Genserver 包装器,这样我的调用就可以像这样工作:
pid = Python.start()
Python.call(pid, :bridge, :register_handler, [self()])
Python.call 看起来像这样:
def call(pid, module, function, args \ []) do
:python.call(pid, module, function, args)
end
来自 :bridge
(即 bridge.py
)的任何内容都会丢失到 STDOUT,除非我明确地 return 某些内容(显然停止了该功能)。我可以做些什么来捕获 STDOUT?
我的想法是调用 Python.call(pid, :builtins, :print, [self()])
之类的东西,但这会导致一堆错误,我真的不知道这是否是正确的方向。
我实际上想将其通过管道传输到 Phoenix 频道,但这是简单的部分(我希望如此)。有什么建议吗?谢谢。
对于其他遇到这种事情的人:因为我在 :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
详情
为了按照 @7stud 的建议更全面地概述我的解决方案,我将包括基于 erlport
和 this great post 的更广泛的方法。因此,我有一个看起来像这样的 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
它是从处理其生成和终止的 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
你可以在最后看到如果 STDOUT 没有 return 来自 bridge.py
(或任何其他 Python 模块)的特定字符串,它将 return堆栈跟踪。说起来,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)
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.
self()
不是输出的地方——self()
是 print
的参数,即 python 将打印出的内容。
我认为 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. I know
it's possible even though I'm using Python 2.7
erlport 文档说:
As a convenient feature ErlPort also supports redirection of Python`s
STDOUT to Erlang...
这似乎是默认设置,因此您无需执行任何操作即可将 python 标准输出重定向到 elixir 标准输出。那么问题就变成了:"How do you log elixir stdout to a file?"
我可以将长生不老药 stdout
记录到这样的文件中:
friends.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
这是我的目录结构:
friends
/lib
/friends
/python
myprint.py
friends.ex
/priv
/log
mylog.log
在 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)>
在日志文件中:
Am I in the log file??!
hello world!
goodbye...
[33m[36m:ok[0m[33m[0m
(我不知道最后一行的垃圾是什么。编辑:嗯...这是被其他东西包围的原子 :ok
。)
如果我注释掉 python_path 行上方 go()
内的所有内容,那么我得到:
iex(1)> Friends.go
hello world!
goodbye...
:ok
在 erlang/elixir 中,文件 I/O 是通过启动一个进程来处理的,请求被发送到该进程以写入文件或读取文件。我认为 stdout 被发送到 group_leader
的任何进程,如果进程处理文件 I/O 是 group_leader,那么 stdout 被发送到文件。
我不知道在使用 GenServer
时乱用 group_leader
是否会把事情搞砸。 erlang docs中有一个警告:
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.
.
我正在尝试将 STDOUT 从 Python/Erlport 传送回 Elixir。我有 :python
调用工作正常,我只是想将 Python 中的 STDOUT 内容发送回 Elixir 进行日志记录,但我不知道如何实现这一点。我知道它是 possible,即使我使用的是 Python 2.7.
我在 :python
模块周围有一个 Genserver 包装器,这样我的调用就可以像这样工作:
pid = Python.start()
Python.call(pid, :bridge, :register_handler, [self()])
Python.call 看起来像这样:
def call(pid, module, function, args \ []) do
:python.call(pid, module, function, args)
end
来自 :bridge
(即 bridge.py
)的任何内容都会丢失到 STDOUT,除非我明确地 return 某些内容(显然停止了该功能)。我可以做些什么来捕获 STDOUT?
我的想法是调用 Python.call(pid, :builtins, :print, [self()])
之类的东西,但这会导致一堆错误,我真的不知道这是否是正确的方向。
我实际上想将其通过管道传输到 Phoenix 频道,但这是简单的部分(我希望如此)。有什么建议吗?谢谢。
对于其他遇到这种事情的人:因为我在 :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
详情
为了按照 @7stud 的建议更全面地概述我的解决方案,我将包括基于 erlport
和 this great post 的更广泛的方法。因此,我有一个看起来像这样的 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
它是从处理其生成和终止的 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
你可以在最后看到如果 STDOUT 没有 return 来自 bridge.py
(或任何其他 Python 模块)的特定字符串,它将 return堆栈跟踪。说起来,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)
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.
self()
不是输出的地方——self()
是 print
的参数,即 python 将打印出的内容。
我认为 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. I know it's possible even though I'm using Python 2.7
erlport 文档说:
As a convenient feature ErlPort also supports redirection of Python`s STDOUT to Erlang...
这似乎是默认设置,因此您无需执行任何操作即可将 python 标准输出重定向到 elixir 标准输出。那么问题就变成了:"How do you log elixir stdout to a file?"
我可以将长生不老药 stdout
记录到这样的文件中:
friends.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
这是我的目录结构:
friends
/lib
/friends
/python
myprint.py
friends.ex
/priv
/log
mylog.log
在 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)>
在日志文件中:
Am I in the log file??!
hello world!
goodbye...
[33m[36m:ok[0m[33m[0m
(我不知道最后一行的垃圾是什么。编辑:嗯...这是被其他东西包围的原子 :ok
。)
如果我注释掉 python_path 行上方 go()
内的所有内容,那么我得到:
iex(1)> Friends.go
hello world!
goodbye...
:ok
在 erlang/elixir 中,文件 I/O 是通过启动一个进程来处理的,请求被发送到该进程以写入文件或读取文件。我认为 stdout 被发送到 group_leader
的任何进程,如果进程处理文件 I/O 是 group_leader,那么 stdout 被发送到文件。
我不知道在使用 GenServer
时乱用 group_leader
是否会把事情搞砸。 erlang docs中有一个警告:
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.
.