Hatena::ブログ(Diary)

Memo

2015-08-23

[] Golang でのログローテション

前に試して、メモするの忘れてた。


Golang のロギングライブラリで、logrus を使ってるけど、これにはローテート機能が無い。

README には logrotate(8) でやれやとあるけど、ポータビリティに欠けるので、Golang にやらせたい。


lumberjack を使えばいけそうだけど、上手い事行かなかったので、@lestrrat さんの go-file-rotatelogs を使った。

lestrrat/go-file-rotatelogs ? GitHub

func setupLogger(logLevel string) *logrus.Logger {
    level, err := logrus.ParseLevel(logLevel)
    if err != nil {
        log.Fatalf("Log level error %v", err)
    }

    path, err := filepath.Abs("logs/app.log.%Y%m%d")
    if err != nil {
        log.Fatalf("Log level error %v", err)
    }
    rl := rotatelogs.NewRotateLogs(path)

    rl.LinkName = "logs/app.log"
    rl.RotationTime = 3600 * time.Second
    rl.Offset = (9 * 60 * 60) * time.Second // Time zone `9 * 60 * 60` is Asia/Tokyo.

    out := io.MultiWriter(os.Stdout, rl)
    logger := logrus.Logger{
        Formatter: &logrus.JSONFormatter{},
        Level:     level,
        Out:       out,
    }
    logger.Info("Setup log finished.")

    return &logger
}

こんな感じで書く。

最初 `rl.Offset` が良くわかんなかったので、元ネタの Perl5 のドキュメントを読んで何を設定すれば良いのか分かった。


@lestrrat さん製のライブラリなので安心して使える。

便利!

2015-07-25

[] Kotoha の Vim plugin を作った

アニメの名言を簡単に引用できるChrome extension『Kotoha』作りました - Konifar's WIP



って @mattn さんが書いてたのでついカッとなってやった。

heavenshell/vim-kotoha ? GitHub

使い方

:set omnifunc=kotoha#complete
:KotohaQuery -query=仕事

と入れるとキーワード検索をしてオムニ補完が出来る。

f:id:heavenshell:20150725234923p:image


:set omnifunc=kotoha#complete
:KotohaTag -query=SHIROBAKO

と入れるとタグ検索をしてオムニ補完が出来る。

f:id:heavenshell:20150725234924p:image


便利。

2015-07-16

[] Flask でアプリケーションを作る際のメモ(2015 年版)

Flask でアプリケーションを作る際のメモ - Memo の 2015 年版

ディレクトリ構造

.
├── __init__.py
├── app.py
├── configs
│      ├── __init__.py
│      └── settings.py
├── errors.py
├── extensions
│      ├── __init__.py
│      ├── injector.py
│      └── permission.py
├── forms
├── i18n
├── models
│      ├── __init__.py
│      ├── db.py
│      ├── entities
│      └── mappers
├── static
│      ├── css
│      ├── fonts
│      ├── img
│      └── js
├── templates
│      ├── admin
│      └── frontend
├── tests
│      ├── __init__.py
│      ├── factories
│      ├── fixtures
│      └── helper.py
├── utils
│      ├── __init__.py
│      ├── compat.py
│      ├── decorators.py
│      ├── method_rewrite.py
│      └── pagination.py
└── views
    ├── __init__.py
    ├── admin
    └── frontend
app.pyアプリケーションファクトリ
configs設定ファイル
errors.pyアプリケーションが使う例外関連
extensionsFlask の拡張ライブラリ
injector.pyDI 関係(Flask-Injecotr)
permission.py権限管理(Flask-Principal)
formsWTForms
i18n国際化関連の翻訳ファイル類
modelsモデル類(直下はドメインロジック)
db.pySQLALchemy のセッション生成等
entitiesSQLAlchemy のテーブル類
mappersBPMapper(SQLAlchemy のオブジェクトJSON に変換)
testsテスト類
factoriesfactory_boy 関連
fixturesMock類(外部 Web サービスの JSON 等)
helper.pyテスト用ヘルパー
utilsユーティリティ
compat.pyPython2, 3 の互換関連(Werkzuig からのコピー)
decorators.pyデコレータ
method_rewrite.pyHTTP の PUT などを上書き
pagination.pyPagenator
viewsView 類

アプリケーションの構造は基本的に変らない。

Injector を使いまくってるので、それが増えた。

あとは factory_boy を使うようになったりとか、Python3 に移行したとかが大きな違い。

アプリケーションの起動

app.py は各種 Flask の拡張の初期化や、views(Blueprint)の登録、エラーハンドリングなどを行う。

アプリケーションの起動は Flask-Script 経由で app.py でアプリケーションオブジェクトを生成して起動する。

テストも app.py のアプリケーションオブジェクトを生成して実行する。

セッション周りは元々 Flask-KVSession を使っていて、しばらく Python3 に対応しなかったので、Redis のインタフェースを適当に書いたけど、Flask-KVSession が Python3 に対応したので、元に戻った。

DB 周り

db.py は元々 meta.py とか(Pylons の名残)にしてたけど、直感的じゃないので、db.py にした。

Flask-SQLAlchemy ではなく SQLALchemy を直接使ってるのは、主にマルチデータベース関連のため*1

マイグレーションには Alembic 使ってる。

View と Model の関係

SQLAlchemy のテーブル類は entities というくくりに入れる。

モデルのロジック類は models 直下に入れる。

View からは models 直下のロジックを使う(entities を直接は触らない)。

Flask-Intector が models 以下のロジックを DI で注入してインスタンス化してるので、View から呼び出してテンプレートエンジンにアサインするか、JSON 化して応答する。


リクエストのバリデーションは WTForms か JSON なら jsonschema を使う。

これは View に書いてハンドリングしている。

昔はロジックだからと models のロジックに入れていたが WTForms に依存が出てくるのでやめた。

テスト

factory_boy を使うようになった。

データベースは毎回 setUpClass 内で初期化をしている。

fixtures には Web サービスがあるのなら、それが応答するレスポンスをダミーデータとして mock から使う。

ヘルパーには例えば、ログイン画面の csrf トークンの取得など便利関数があるので、それを使う。


とりあずは小規模なアプリケーションならこれで困っていない。

*1:Flask-SQLAlchemy も対応しているが、意図通り動かなかったため

2015-06-12

[] Splinter と PhantomJS を使って confirm() をフックする方法

Splinter — Splinter 0.7.3 documentation

Splinter と PhantomJS を使うと E2E なテストを実現できる。

概ねこれで問題ないけど、サイトで JavaScript が window.confirm() を使って確認ダイアログを出している。

Splinter には get_alert() というインターフェイスがあるので、これを使えば良いかと思ったが、PhantomJS がドライバーの場合使えない。

splinter/phantomjs.py at master ? cobrateam/splinter ? GitHub


クリックした結果を取れればいいので、window.confirm を書き換えたら良い。

browser.execute_script('window.confirm = function () {return true;}')

これをページがロードされた直後にしてやると、window.confirm が上書きされ、無事に常に true を受け取るようになる。

2015-05-16

[][] alembic でインデックスが 767 バイト以上の対策

MySQL5.6 で alembic を使った場合に、

"Warning: Specified key was too long; max key length is 767 bytes" が出る場合の対策メモ。

$ alembic upgrade head
INFO  [alembic.migration] Context impl MySQLImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade  -> 202b6777d6c, Create init table.
/opt/python3.4/site-packages/pymysql
/cursors.py:134: Warning: Specified key was too long; max key length is 767 byte
s
  result = self._query(query)

原因は以下を参照。

MySQL(InnoDB) で "Index column size too large. The maximum column size is 767 bytes." いわれるときの対策 - かみぽわーる

ERROR 1071 (42000): Specified key was too long; max key length is 1000 bytes - sonots:blog


my.cnf で innodb の設定を変える。

MySQL の設定だけを変えて、alembic を実行してもエラーになる。

INFO  [alembic.migration] Context impl MySQLImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade  -> 202b6777d6c, Create init table.
Traceback (most recent call last):
  File "/opt/virtualenvs/pisces/bin/alembic", line 9, in <module>
....
sqlalchemy.exc.InternalError: (pymysql.err.InternalError) (1709, 'Index column s
ize too large. The maximum column size is 767 bytes.') [SQL: 'CREATE INDEX ix_co
ntacts_email ON contacts (email)']

マイグレーションようのスクリプトにも手を入れてやる必要がある。

"""Create init table.

Revision ID: 202b6777d6c
Revises: None
Create Date: 2014-10-11 18:23:14.081574

"""

# revision identifiers, used by Alembic.
revision = 'xxxxx'
down_revision = None

import os
import simplejson as json
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import table, column
from sqlalchemy.dialects.mysql import INTEGER as Integer


def upgrade():
    op.create_table(
        'users',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('name', sa.Unicode(50), nullable=False, index=True),
        sa.Column('password', sa.String(128), nullable=False, index=True),
        sa.Column('email', sa.String(254), nullable=False, index=True),
        sa.Column('created_at', sa.DateTime),
        sa.Column('updated_at', sa.DateTime),
        mysql_row_format='DYNAMIC'
    )

def downgrade():
    op.drop_table('users')

こんな感じで `create_table()` で mysql_xxx という形で MySQL 特有のオプションを渡す。

これで実行すると、エラーなく完了する。

 $ alembic upgrade head
INFO  [alembic.migration] Context impl MySQLImpl.
INFO  [alembic.migration] Will assume non-transactional DDL.
INFO  [alembic.migration] Running upgrade  -> xxxxx, Create init table.

2015-04-28

[] factory_boy 2.4.0 で仕様が変った

Python の Fixture ツールである factory_boy を最新版の 2.4.0 にアップデートしたら、今までテストが通っていたものが fail するようになった。

# -*- coding: utf-8 -*-
from factory import Sequence
from factory.alchemy import SQLAlchemyModelFactory
from models.db import session
from models.entities.user import User
        

class UserFactory(SQLAlchemyModelFactory):
    FACTORY_FOR = User
    FACTORY_SESSION = session
    FACTORY_HIDDEN_ARGS = ('id',)
        
    id = Sequence(lambda n: n)
    name = Sequence(lambda n: u'User%d' % n)
    password = Sequence(lambda n: u'password%d' % n)
    email = Sequence(lambda n: u'user%d@example.com' % n)

元々のコードがこれだったが、テストを実行すると以下のメッセージが表示された。

Cannot generate instances of abstract factory UserFactory; Ensure UseryFactory.Meta.model is set and UserFactory.Meta.abstract is either not set or False.

ドキュメントを探してみると、class Meta を使うようになったよう。

Reference — Factory Boy 2.5.2 documentation

という訳でこういう感じになった。

from factory import Sequence
from factory.alchemy import SQLAlchemyModelFactory
from models.db import session
from models.entities.user import User


class UserFactory(SQLAlchemyModelFactory):
    class Meta:
        model = User
        sqlalchemy_session = session
        abstract = False
        inline_args = ('name', 'password', 'email',)
        exclude = ('id',)

    id = Sequence(lambda n: n)
    name = Sequence(lambda n: u'User%d' % n)
    password = Sequence(lambda n: u'password%d' % n)
    email = Sequence(lambda n: u'user%d@example.com' % n)

SQLAlchemy を使う場合には sqlalchemy_session に格納するように変った。

また SQLAlchemy の model で以下のように __init__() で登録している場合は、inline_args を使う必要がある。

def __init__(self, name, password, email):
    self.name = name
    self.password = password
    self.email = email

逆に id のように自動で生成するものは exclude に格納する。

2015-04-05

[] Golang で Web アプリケーションを作る際のメモ

自分用メモ。

ここら辺を参考にした。

Golang でのウェブ開発を考えてみる - Qiita


cli

codegangsta/cli ? GitHub

ここら辺が鉄板。

ちょっとアレと思ったのが、グローバルオプションの位置がサブコマンドの前に書かないといけない。

$ ./app runserver --config=./setting.hcl とかやる場合は、runserver のオプションにしないと行けない。

あと、グローバルオプションが goji の goji.Serve() と被って困った。


試してないけど良さそうと思ったけど、どうなんだろう。

mitchellh/cli ? GitHub


Web Framework

goji で決定。

Flask みたいなので良い。


コンテキストを持ち回すのをどうすれば良いのか悩む。

func showPage(c web.C, w http.ResponseWriter, r *http.Request) {
}
func (ctx AppContext) showPage(c web.C, w http.ResponseWriter, r *http.Request) {
}

goji のコンテキストアプリケーションコンテキストとして渡すのか、goji のコンテキストを使うか…。

とりあえずアプリケーションコンテキストは goji 以外でも持ち回すことになりそうなので、後者でやってみる。

アプリケーションコンテキストに、ロガー、設定ファイル、Redis のコネクション等を持ち回す予定。


テンプレートエンジン

flosch/pongo2 ? GitHub

Jinja2 というか Djangoテンプレートエンジン。

フィルタとか自分で作れそうなのが良い。

Jinja2 の `set var` みたいなのがテンプレートの中で出来たら良いんだけど、出来ないのでそれはあきらめる。

設定ファイル

hashicorp/hcl ? GitHub

これで良い。

最初使い方が良くわからなかったけど、サンプル探して読んだら出来た。


ログ

プロダクションで使う場合ログはきちんとしたい。

標準のはシンプルなので、保守運用の場合にもう少し高機能なのが欲しい。

Sirupsen/logrus ? GitHub


開発時は Stdout とファイルに出力してほしい。

io.MultiWriter 使えとの事なので、以下のようにした。

logf, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
out := io.MultiWriter(os.Stdout, logf)
logger := logrus.Logger{
    Formatter: &logrus.TextFormatter{DisableColors: true},
    Level:     level,
    Out:       out,
}

logrus にはローテーション機能はなくて、logrotate(8) を使えとなっているが、OS ではなくてアプリケーションで制御したい場合もあるので、そういう場合は natefinch/lumberjack ? GitHub を使えば良さそう。


使わなかったもの

フォーム

bluele/gforms ? GitHub

WTForm みたいなのらしい。

ORM

どれが良いんだろう。

JSON

bitly/go-simplejson ? GitHub

i18n

nicksnyder/go-i18n ? GitHub

Pongo2 と連動してたら良いんだけど、自分で連携するものを作る必要あり。

Redis

garyburd/redigo ? GitHub

Session

sessions - Gorilla, the golang web toolkit

boj/redistore ? GitHub

Memcahced

bradfitz/gomemcache ? GitHub

Graceful restart

facebookgo/grace ? GitHub

もっともこれ使う場合は、goji の graceful shutdown と被らないように作らないといけない。

アセット管理?

jteeuwen/go-bindata ? GitHub


他にも色々試したら追記していく。


追記

まず Vim を用意する。

Release MacVim-KaoriYa 20150314 ? splhack/macvim-kaoriya ? GitHub


次に Golang 用のプラグインをインストールする。

google/vim-ft-go ? GitHub

vim-jp/vim-go-extra ? GitHub

vim-jp » vim-go-extra を公開致します。

vimrc に以下を追記する。

" Golang {{{
execute 'set rtp+=' . globpath($GOPATH, 'src/github.com/nsf/gocode/vim')
" }}}
:Fmt

とかでフォーマットしたり、C-x C-o とかで補完が出来るようになる。