複数アプリケーションの
プロセスとログを管理する
ための新しいツールと手法
谷津 真樹 (Masaki Yatsu)
PyCon JP
Sep 18, 2018
アジェンダ
• Jaffle: 開発支援ツールの紹介
• 開発の動機
• Pythonアプリケーションと外部プロセスの起動
/自動タスク実行
• 開発時のログ管理
• Jaffle設定の運用
• Jaffleのアーキテクチャと実装
https://jaffle.readthedocs.io/
https://github.com/yatsu/jaffle
Jaffle
• 複数のPythonアプリケーションと外部プロセスをまとめて起動・終了する
• ファイルの更新を検出してタスクの自動実行を行う (例: 自動テスト)
• ログ出力を統合し、発生時刻順に表示、フィルタリング、置換を行う
• Jupyter Kernel上でPythonコードを統合動作させるフレームワーク
(最後の実装のところで説明)
Jaffleとは
背景
• プロセスやログを管理するものは、プロダクシ
ョン環境で使えるツールやクラウド上のサービ
スは充実してきているが、手元の開発環境で手
軽に使えるものがない
• 近年の傾向として
• 連携するアプリケーションやサービスの数が増加し
てきている
• 個々のアプリケーションやサービスが出力する情報
も増加してきている
Pythonアプリケーションと
外部プロセスの起動/
自動タスク実行
事例1: TornadoとReactによるWeb開発
• Tornadoで実装されたWeb APIサーバーを立ち上げる
• 2つの外部プロセスを起動する
• “yarn start” (開発用Webサーバー)
• “jest” (JavaScriptテスト)
• .pyファイルを更新時に
• Web APIサーバー再起動
• pytest実行
• ログを統合して表示
https://jaffle.readthedocs.io/en/latest/cookbook/tornado_spa.html
Tornado (Webフレームワーク) でバックエンドWeb APIを
実装し、ReactでWebフロントエンドを開発する例
事例2: pytest自動テスト
• テストファイル test_*.py が更新されたら、そ
のテストを実行する
• 実装ファイル foo/bar.py が更新されたら、それ
に関するテスト tests/foo/test_bar.py を実行する
• pytestの実行が常にスタンバイ状態で、カレン
トディレクトリ以下のモジュールだけをリロー
ドするため、高速に動作する
• pytest実行前にキャッシュクリアは設定可能
https://jaffle.readthedocs.io/en/latest/cookbook/pytest.html
事例2: pytest自動テスト / 設定ファイル
kernel "py_kernel" {}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [{
watch_path = "pytest_example"
patterns = ["*.py"]
ignore_directories = true
code_blocks = ["pytest.handle_watchdog_event({event})"]
}]
}
}
app "pytest" {
class = "jaffle.app.pytest.PyTestRunnerApp"
kernel = "py_kernel"
options {
args = ["-s", "-v", "—color=yes"]
auto_test = ["pytest_example/tests/test_*.py"]
auto_test_map {
"pytest_example/**/*.py" = "pytest_example/tests/{}/test_{}.py"
}
}
}
ファイルシステム監視App
左側のパターンにマッチしたら右側のパターンのテストを実行
例: foo/bar.py → tests/foo/test_bar.py
パターンにマッチしたテストを実行
Jupyter Kernel (Pythonインタープリタ・プロセス) を定義
変数として参照する
実行方法:
$ jaffle start jaffle.hcl
設定ファイルがjaffle.hclだけの場合は省略可能:
$ jaffle start
上のようなjaffle.hclファイルを作成する
事例3: Sphinx自動ビルド / 設定ファイル
kernel "py_kernel" {
pass_env = ["PATH"]
}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [{
patterns = ["*/docs/*.*"]
ignore_patterns = ["*/_build/*"]
ignore_directories = true
jobs = ["sphinx”, “refresh”]
}]
}
}
job "sphinx" {
command = "sphinx-build -M html docs docs/_build"
}
job ”refresh" {
command = ”osascript browser_refresh.scpt"
}
docs/ ディレクトリ以下の .rst ファ
イルを更新すると、sphinx-build が
自動的に実行され、HTMLファイル
が生成される
Job名参照
コマンド実行はJobとして定義する
環境変数PATHをJupyter Kernelに引き継ぐ
virtualenvの場合に必要
tell application "Google Chrome" to tell the active tab of its first window
reload
end tell
(macOSの場合)
事例4: Jupyter Extension開発
https://jaffle.readthedocs.io/en/latest/cookbook/jupyter_ext.html
• .pyファイルが更新されたとき、
Jupyter Notebook Serverを再起動
• .jsファイルが更新されたとき、
“jupyter nbextension install” を実行
kernel "py_kernel" {
pass_env = ["PATH"]
}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [
{
patterns = ["*.py"]
ignore_directories = true
clear_cache = ["jupyter_myext"]
code_blocks = ["notebook.handle_watchdog_event({event})"]
},
{
patterns = ["*.js"]
ignore_directories = true
jobs = ["nbext_install"]
},
]
}
}
app "notebook" {
class = "jaffle.app.tornado.TornadoBridgeApp"
kernel = "py_kernel"
options {
app_class = "notebook.notebookapp.NotebookApp"
}
start = "notebook.start()"
}
job "nbext_install" {
command = "jupyter nbextension install jupyter_myext --user --overwrite"
}
Jupyter Notebookのサーバーサイドとフロ
ントエンドJavaScriptの両方を拡張する例
Jupyter Notebook ServerをJupyter Kernelから起動する;
Jupyter NotebookはTornado (Webフレームワーク)で実装
されており、JaffleのTornado IOLoop上で動作させること
ができる
開発時のログ管理
ログ管理の問題と解決方法
• 複数のログファイルから処
理の流れを把握するのが難
しい
• ログメッセージの数が多く、
目的のメッセージを見つけ
るのが難しい
• ひとつのログメッセージに
含まれるデータが大きく、
必要な情報を取り出すのが
難しい
すべてのログを統合して表示する
フィルタリング機能を使い、
必要のないメッセージを隠す
置換機能を使い、必要な情報だけ
取り出したり
色付けによる強調表示を行う
ログの統合
• すべてのログはまとめて発生時刻順に表示される
• ロガー名(App/プロセス単位)ごとに色がアサイン
され、識別しやすくなる
ログのフィルタリング
app "pytest" {
class = "jaffle.app.pytest.PyTestRunnerApp"
kernel = "py_kernel"
logger {
suppress_regex = [
"^platform ",
"^cachedir:",
"^rootdir:",
"^plugins:",
"collecting ...",
"^collected ",
]
}
}
pytestの出力から不要な行を隠す例
大きなデータをログ出力するときの問題
$ kubectl get services kubernetes -o json
{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"creationTimestamp": "2018-09-06T13:09:41Z",
"labels": {
"component": "apiserver",
"provider": "kubernetes"
},
"name": "kubernetes",
"namespace": "default",
"resourceVersion": "18",
"selfLink":
"/api/v1/namespaces/default/services/kubernetes",
"uid": "1dc6394b-b1d6-11e8-859a-080027be08a0"
},
"spec": {
"clusterIP": "10.96.0.1",
"ports": [
{
"name": "https",
"port": 443,
"protocol": "TCP",
"targetPort": 8443
}
],
"sessionAffinity": "ClientIP",
"sessionAffinityConfig": {
"clientIP": {
"timeoutSeconds": 10800
}
},
"type": "ClusterIP"
},
"status": {
"loadBalancer": {}
}
}
service:
{"apiVersion":"v1","kind":"Service","metadata":{"creationTimestamp":"
2018-09-
06T13:09:41Z","labels":{"component":"apiserver","provider":"kubernete
s"},"name":"kubernetes","namespace":"default","resourceVersion":"18",
"selfLink":"/api/v1/namespaces/default/services/kubernetes","uid":"1d
c6394b-b1d6-11e8-859a-
080027be08a0"},"spec":{"clusterIP":"10.96.0.1","ports":[{"name":"http
s","port":443,"protocol":"TCP","targetPort":8443}],"sessionAffinity":
"ClientIP","sessionAffinityConfig":{"clientIP":{"timeoutSeconds":1080
0}},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
すべて出力すると1行が長くなる:
labelsを見たい人はこのように出力したい:
{"labels":{"component":"apiserver","provider":"kubernetes"}
nameとclusterIPを見たい人はこのように出力したい:
service: {"name":"kubernetes”,"clusterIP":"10.96.0.1"}
見たい情報はコンテキストに依存する
ロガーを複数定義してそれぞれに出力ルールを決めるのは大変
アプリ起動時にロガーごとにレベルをセットするのも大変
ログの置換 / 値の取り出し [1/4]
app "my_app" {
logger {
replace_regex = [
{
from = "^service: (.*)$"
to = ”service ip: ${jqf('.spec.clusterIP', '1')}"
},
]
}
}
dictのパス .spec.clusterIP から値を取り出す
IPだけ取り出せる
JSONに対するjqコマンドと同等のことができる
ログの置換 / 値の取り出し [2/4]
app "my_app" {
logger {
replace_regex = [
{
from = "^service: (.*)$"
to = "ip: ${fg('blue')}${jqf('.spec.clusterIP', '1’)}${reset()}"
},
]
}
}
さらに色をつける
使える関数
• jq_all(): dict を jq した結果のリストを返す
• エイリアス: jq()
• jq_first(): dictを jq した結果の最初のアイテムを返す
• エイリアス: jqf()
• fg(): フォアグラウンドカラーを設定する
• bg(): バックグラウンドカラーを設定する
• reset(): カラーをリセットする
ログの置換 / 値の取り出し [3/4]
単純だがデバッグ時に便利な設定
app "my_app" {
logger {
replace_regex = [
{
from = "^XXX (.*)$"
to = "${fg('red')}XXX 1${reset()}"
},
]
}
}
今デバッグしているメッセージに
印をつけておき、赤で表示
ログの置換 / 値の取り出し [4/4]
• デバッグログでは大きなdictをそのまま出力させてよい
• ログを読むツールでフィルタリングを行う
• フィルタリングの設定はコンテキストごとにもつこと
ができる
• Jaffleは複数の設定をマージすることができる
(次のJaffle設定の運用で述べる)
大きなデータをログ出力で扱う方法の提案
Jaffle設定の運用
実行時の設定変更
• jaffle.hcl内で使用する値を変数として
定義することができる
• 変数は実行時に環境変数で値をセッ
トすることができる
variable "tornado_log_level" {
default = "debug"
}
variable "disable_frontend" {
default = false
}
kernel "py_kernel" {}
app "tornado_app" {
class = "jaffle.app.tornado.TornadoBridgeApp"
kernel = "py_kernel"
start = "tornado_app.start()"
logger {
level = "${var.tornado_log_level}"
}
options {
app_class = "tornado_spa_advanced.app.ExampleApp"
}
}
process "frontend" {
command = "yarn start"
tty = true
disabled = "${var.disable_frontend}"
}
変数参照
変数参照
変数定義
$ J_VAR_tornado_log_level=info 
J_VAR_disable_frontend=true 
jaffle start
実行例:
設定ファイルを毎回書き換えるのが大変なとき
複数の設定ファイルをマージして実行
チーム開発での設定ファイル構成例
project_repo/
├─ jaffle.hcl
├─ frontend.hcl
├─ backend.hcl
├─ my-jaffle.hcl
└─ src/
ベース設定 (gitで管理)
フロントエンド開発用設定 (gitで管理)
バックエンド開発用設定 (gitで管理)
自分用設定 (.gitignoreに設定)
$ jaffle start jaffle.hcl backend.hcl my-jaffle.hcl
バックエンド開発時のJaffle起動:
ひとつの設定にマージして実行する
特にログのフィルタリング・置換を多段適用するのが便利
Jaffleのアーキテクチャと
実装
アーキテクチャ [1/2]
JaffleSessionManager
JaffleKernelManager
JaffleKernelClient
JaffleStartCommand
App
Jupyter Kernel Session
ZeroMQ Socket for logging
Python Code/Response
via ZeroMQ
Log Message
Create Kernel
Session
Jupyterライブラリ群をPython実行と通信のための
フレームワークとして使用する
Python Code
アーキテクチャ [2/2]
• 複数のPythonアプリケーションをJupyter Kernel上で動
作させる
• Kernelの管理にはJupyter NotebookのKernelManager,
ContentsManager, SessionManagerを使用する
• Kernelとの通信にはjupyter-clientライブラリを使用する
• ZeroMQを使ってログメッセージを集める
• 以下の”App”を同梱する
• WatchdogApp: ファイル更新を監視し、イベント発生時にコー
ドを実行
• PyTestRunnerApp: pytest実行
• TornadoBridgeApp: Tornadoで実装されたアプリの起動・停止制
御
App間連携
• py_kernel上で、WatchdogAppと
PyTestRunnerAppはそれぞれwatchdog,
pytestという変数にアサインされる
• App同士は互いに変数名でアクセス
することができる
• 実行すべきコードをjaffle.hclに直接記
述する(左の例ではcode_blocks)
kernel "py_kernel" {}
app "watchdog" {
class = "jaffle.app.watchdog.WatchdogApp"
kernel = "py_kernel"
options {
handlers = [{
# …
code_blocks = ["pytest.handle_watchdog_event({event})"]
}]
}
}
app "pytest" {
class = "jaffle.app.pytest.PyTestRunnerApp"
kernel = "py_kernel"
options {
# …
}
}
変数名で参照できる
watchdogが変数名になる
App間に特別なプロトコルは必要ない
引数だけ知っていればよい
コードを直接書く
jaffle.hcl
独自App定義
app "my_app" {
class = "my_module.MyApp"
kernel = "py_kernel"
options {
foo = 1
}
start = "my_app.bar()"
}
from jaffle.app.base import BaseJaffleApp, capture_method_output
from tornado import gen, ioloop
class MyApp(BaseJaffleApp):
def __init__(self, app_conf_data):
super().__init__(app_conf_data)
self.foo = self.options.get('foo')
self.log.info('foo: %d', self.foo)
@capture_method_output
def bar(self):
print('bar')
ioloop.IOLoop.current().add_callback(self.async_example)
@gen.coroutine
def async_example(self):
yield self.execute_code('{var} = 1 + 2', var='foo')
yield self.execute_command('echo hello')
yield self.execute_job('my_job')
標準出力をキャプチャ
オプション取得
非同期呼び出し
my_app = my_module.MyApp(app_conf_data)
my_app.bar()
以下のように実行される
使用例 (jaffle.hcl)
my_module.py
将来計画
ログ表示機能の拡張
• 正規表現ルールをJaffle起動中に編集する機能
• リアルタイムにプレビューする機能
• インタラクティブに作成したルールをファイルに書き
出す機能
正規表現を書くのが難しい
ありがとうございました

複数アプリケーションの プロセスとログを管理する ための新しいツールと手法

  • 1.
  • 2.
    アジェンダ • Jaffle: 開発支援ツールの紹介 •開発の動機 • Pythonアプリケーションと外部プロセスの起動 /自動タスク実行 • 開発時のログ管理 • Jaffle設定の運用 • Jaffleのアーキテクチャと実装 https://jaffle.readthedocs.io/ https://github.com/yatsu/jaffle Jaffle
  • 3.
    • 複数のPythonアプリケーションと外部プロセスをまとめて起動・終了する • ファイルの更新を検出してタスクの自動実行を行う(例: 自動テスト) • ログ出力を統合し、発生時刻順に表示、フィルタリング、置換を行う • Jupyter Kernel上でPythonコードを統合動作させるフレームワーク (最後の実装のところで説明) Jaffleとは
  • 4.
    背景 • プロセスやログを管理するものは、プロダクシ ョン環境で使えるツールやクラウド上のサービ スは充実してきているが、手元の開発環境で手 軽に使えるものがない • 近年の傾向として •連携するアプリケーションやサービスの数が増加し てきている • 個々のアプリケーションやサービスが出力する情報 も増加してきている
  • 5.
  • 6.
    事例1: TornadoとReactによるWeb開発 • Tornadoで実装されたWebAPIサーバーを立ち上げる • 2つの外部プロセスを起動する • “yarn start” (開発用Webサーバー) • “jest” (JavaScriptテスト) • .pyファイルを更新時に • Web APIサーバー再起動 • pytest実行 • ログを統合して表示 https://jaffle.readthedocs.io/en/latest/cookbook/tornado_spa.html Tornado (Webフレームワーク) でバックエンドWeb APIを 実装し、ReactでWebフロントエンドを開発する例
  • 7.
    事例2: pytest自動テスト • テストファイルtest_*.py が更新されたら、そ のテストを実行する • 実装ファイル foo/bar.py が更新されたら、それ に関するテスト tests/foo/test_bar.py を実行する • pytestの実行が常にスタンバイ状態で、カレン トディレクトリ以下のモジュールだけをリロー ドするため、高速に動作する • pytest実行前にキャッシュクリアは設定可能 https://jaffle.readthedocs.io/en/latest/cookbook/pytest.html
  • 8.
    事例2: pytest自動テスト /設定ファイル kernel "py_kernel" {} app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [{ watch_path = "pytest_example" patterns = ["*.py"] ignore_directories = true code_blocks = ["pytest.handle_watchdog_event({event})"] }] } } app "pytest" { class = "jaffle.app.pytest.PyTestRunnerApp" kernel = "py_kernel" options { args = ["-s", "-v", "—color=yes"] auto_test = ["pytest_example/tests/test_*.py"] auto_test_map { "pytest_example/**/*.py" = "pytest_example/tests/{}/test_{}.py" } } } ファイルシステム監視App 左側のパターンにマッチしたら右側のパターンのテストを実行 例: foo/bar.py → tests/foo/test_bar.py パターンにマッチしたテストを実行 Jupyter Kernel (Pythonインタープリタ・プロセス) を定義 変数として参照する 実行方法: $ jaffle start jaffle.hcl 設定ファイルがjaffle.hclだけの場合は省略可能: $ jaffle start 上のようなjaffle.hclファイルを作成する
  • 9.
    事例3: Sphinx自動ビルド /設定ファイル kernel "py_kernel" { pass_env = ["PATH"] } app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [{ patterns = ["*/docs/*.*"] ignore_patterns = ["*/_build/*"] ignore_directories = true jobs = ["sphinx”, “refresh”] }] } } job "sphinx" { command = "sphinx-build -M html docs docs/_build" } job ”refresh" { command = ”osascript browser_refresh.scpt" } docs/ ディレクトリ以下の .rst ファ イルを更新すると、sphinx-build が 自動的に実行され、HTMLファイル が生成される Job名参照 コマンド実行はJobとして定義する 環境変数PATHをJupyter Kernelに引き継ぐ virtualenvの場合に必要 tell application "Google Chrome" to tell the active tab of its first window reload end tell (macOSの場合)
  • 10.
    事例4: Jupyter Extension開発 https://jaffle.readthedocs.io/en/latest/cookbook/jupyter_ext.html •.pyファイルが更新されたとき、 Jupyter Notebook Serverを再起動 • .jsファイルが更新されたとき、 “jupyter nbextension install” を実行 kernel "py_kernel" { pass_env = ["PATH"] } app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [ { patterns = ["*.py"] ignore_directories = true clear_cache = ["jupyter_myext"] code_blocks = ["notebook.handle_watchdog_event({event})"] }, { patterns = ["*.js"] ignore_directories = true jobs = ["nbext_install"] }, ] } } app "notebook" { class = "jaffle.app.tornado.TornadoBridgeApp" kernel = "py_kernel" options { app_class = "notebook.notebookapp.NotebookApp" } start = "notebook.start()" } job "nbext_install" { command = "jupyter nbextension install jupyter_myext --user --overwrite" } Jupyter Notebookのサーバーサイドとフロ ントエンドJavaScriptの両方を拡張する例 Jupyter Notebook ServerをJupyter Kernelから起動する; Jupyter NotebookはTornado (Webフレームワーク)で実装 されており、JaffleのTornado IOLoop上で動作させること ができる
  • 11.
  • 12.
    ログ管理の問題と解決方法 • 複数のログファイルから処 理の流れを把握するのが難 しい • ログメッセージの数が多く、 目的のメッセージを見つけ るのが難しい •ひとつのログメッセージに 含まれるデータが大きく、 必要な情報を取り出すのが 難しい すべてのログを統合して表示する フィルタリング機能を使い、 必要のないメッセージを隠す 置換機能を使い、必要な情報だけ 取り出したり 色付けによる強調表示を行う
  • 13.
  • 14.
    ログのフィルタリング app "pytest" { class= "jaffle.app.pytest.PyTestRunnerApp" kernel = "py_kernel" logger { suppress_regex = [ "^platform ", "^cachedir:", "^rootdir:", "^plugins:", "collecting ...", "^collected ", ] } } pytestの出力から不要な行を隠す例
  • 15.
    大きなデータをログ出力するときの問題 $ kubectl getservices kubernetes -o json { "apiVersion": "v1", "kind": "Service", "metadata": { "creationTimestamp": "2018-09-06T13:09:41Z", "labels": { "component": "apiserver", "provider": "kubernetes" }, "name": "kubernetes", "namespace": "default", "resourceVersion": "18", "selfLink": "/api/v1/namespaces/default/services/kubernetes", "uid": "1dc6394b-b1d6-11e8-859a-080027be08a0" }, "spec": { "clusterIP": "10.96.0.1", "ports": [ { "name": "https", "port": 443, "protocol": "TCP", "targetPort": 8443 } ], "sessionAffinity": "ClientIP", "sessionAffinityConfig": { "clientIP": { "timeoutSeconds": 10800 } }, "type": "ClusterIP" }, "status": { "loadBalancer": {} } } service: {"apiVersion":"v1","kind":"Service","metadata":{"creationTimestamp":" 2018-09- 06T13:09:41Z","labels":{"component":"apiserver","provider":"kubernete s"},"name":"kubernetes","namespace":"default","resourceVersion":"18", "selfLink":"/api/v1/namespaces/default/services/kubernetes","uid":"1d c6394b-b1d6-11e8-859a- 080027be08a0"},"spec":{"clusterIP":"10.96.0.1","ports":[{"name":"http s","port":443,"protocol":"TCP","targetPort":8443}],"sessionAffinity": "ClientIP","sessionAffinityConfig":{"clientIP":{"timeoutSeconds":1080 0}},"type":"ClusterIP"},"status":{"loadBalancer":{}}} すべて出力すると1行が長くなる: labelsを見たい人はこのように出力したい: {"labels":{"component":"apiserver","provider":"kubernetes"} nameとclusterIPを見たい人はこのように出力したい: service: {"name":"kubernetes”,"clusterIP":"10.96.0.1"} 見たい情報はコンテキストに依存する ロガーを複数定義してそれぞれに出力ルールを決めるのは大変 アプリ起動時にロガーごとにレベルをセットするのも大変
  • 16.
    ログの置換 / 値の取り出し[1/4] app "my_app" { logger { replace_regex = [ { from = "^service: (.*)$" to = ”service ip: ${jqf('.spec.clusterIP', '1')}" }, ] } } dictのパス .spec.clusterIP から値を取り出す IPだけ取り出せる JSONに対するjqコマンドと同等のことができる
  • 17.
    ログの置換 / 値の取り出し[2/4] app "my_app" { logger { replace_regex = [ { from = "^service: (.*)$" to = "ip: ${fg('blue')}${jqf('.spec.clusterIP', '1’)}${reset()}" }, ] } } さらに色をつける 使える関数 • jq_all(): dict を jq した結果のリストを返す • エイリアス: jq() • jq_first(): dictを jq した結果の最初のアイテムを返す • エイリアス: jqf() • fg(): フォアグラウンドカラーを設定する • bg(): バックグラウンドカラーを設定する • reset(): カラーをリセットする
  • 18.
    ログの置換 / 値の取り出し[3/4] 単純だがデバッグ時に便利な設定 app "my_app" { logger { replace_regex = [ { from = "^XXX (.*)$" to = "${fg('red')}XXX 1${reset()}" }, ] } } 今デバッグしているメッセージに 印をつけておき、赤で表示
  • 19.
    ログの置換 / 値の取り出し[4/4] • デバッグログでは大きなdictをそのまま出力させてよい • ログを読むツールでフィルタリングを行う • フィルタリングの設定はコンテキストごとにもつこと ができる • Jaffleは複数の設定をマージすることができる (次のJaffle設定の運用で述べる) 大きなデータをログ出力で扱う方法の提案
  • 20.
  • 21.
    実行時の設定変更 • jaffle.hcl内で使用する値を変数として 定義することができる • 変数は実行時に環境変数で値をセッ トすることができる variable"tornado_log_level" { default = "debug" } variable "disable_frontend" { default = false } kernel "py_kernel" {} app "tornado_app" { class = "jaffle.app.tornado.TornadoBridgeApp" kernel = "py_kernel" start = "tornado_app.start()" logger { level = "${var.tornado_log_level}" } options { app_class = "tornado_spa_advanced.app.ExampleApp" } } process "frontend" { command = "yarn start" tty = true disabled = "${var.disable_frontend}" } 変数参照 変数参照 変数定義 $ J_VAR_tornado_log_level=info J_VAR_disable_frontend=true jaffle start 実行例: 設定ファイルを毎回書き換えるのが大変なとき
  • 22.
    複数の設定ファイルをマージして実行 チーム開発での設定ファイル構成例 project_repo/ ├─ jaffle.hcl ├─ frontend.hcl ├─backend.hcl ├─ my-jaffle.hcl └─ src/ ベース設定 (gitで管理) フロントエンド開発用設定 (gitで管理) バックエンド開発用設定 (gitで管理) 自分用設定 (.gitignoreに設定) $ jaffle start jaffle.hcl backend.hcl my-jaffle.hcl バックエンド開発時のJaffle起動: ひとつの設定にマージして実行する 特にログのフィルタリング・置換を多段適用するのが便利
  • 23.
  • 24.
    アーキテクチャ [1/2] JaffleSessionManager JaffleKernelManager JaffleKernelClient JaffleStartCommand App Jupyter KernelSession ZeroMQ Socket for logging Python Code/Response via ZeroMQ Log Message Create Kernel Session Jupyterライブラリ群をPython実行と通信のための フレームワークとして使用する Python Code
  • 25.
    アーキテクチャ [2/2] • 複数のPythonアプリケーションをJupyterKernel上で動 作させる • Kernelの管理にはJupyter NotebookのKernelManager, ContentsManager, SessionManagerを使用する • Kernelとの通信にはjupyter-clientライブラリを使用する • ZeroMQを使ってログメッセージを集める • 以下の”App”を同梱する • WatchdogApp: ファイル更新を監視し、イベント発生時にコー ドを実行 • PyTestRunnerApp: pytest実行 • TornadoBridgeApp: Tornadoで実装されたアプリの起動・停止制 御
  • 26.
    App間連携 • py_kernel上で、WatchdogAppと PyTestRunnerAppはそれぞれwatchdog, pytestという変数にアサインされる • App同士は互いに変数名でアクセス することができる •実行すべきコードをjaffle.hclに直接記 述する(左の例ではcode_blocks) kernel "py_kernel" {} app "watchdog" { class = "jaffle.app.watchdog.WatchdogApp" kernel = "py_kernel" options { handlers = [{ # … code_blocks = ["pytest.handle_watchdog_event({event})"] }] } } app "pytest" { class = "jaffle.app.pytest.PyTestRunnerApp" kernel = "py_kernel" options { # … } } 変数名で参照できる watchdogが変数名になる App間に特別なプロトコルは必要ない 引数だけ知っていればよい コードを直接書く jaffle.hcl
  • 27.
    独自App定義 app "my_app" { class= "my_module.MyApp" kernel = "py_kernel" options { foo = 1 } start = "my_app.bar()" } from jaffle.app.base import BaseJaffleApp, capture_method_output from tornado import gen, ioloop class MyApp(BaseJaffleApp): def __init__(self, app_conf_data): super().__init__(app_conf_data) self.foo = self.options.get('foo') self.log.info('foo: %d', self.foo) @capture_method_output def bar(self): print('bar') ioloop.IOLoop.current().add_callback(self.async_example) @gen.coroutine def async_example(self): yield self.execute_code('{var} = 1 + 2', var='foo') yield self.execute_command('echo hello') yield self.execute_job('my_job') 標準出力をキャプチャ オプション取得 非同期呼び出し my_app = my_module.MyApp(app_conf_data) my_app.bar() 以下のように実行される 使用例 (jaffle.hcl) my_module.py
  • 28.
  • 29.
    ログ表示機能の拡張 • 正規表現ルールをJaffle起動中に編集する機能 • リアルタイムにプレビューする機能 •インタラクティブに作成したルールをファイルに書き 出す機能 正規表現を書くのが難しい
  • 30.