タグ「ruby」が付けられているもの

RubyのsequelというORMライブラリがあります。

細かい使い方はQiitaにいくつか記事があるのでそちらを参照するとして、今回はMySQLで以下のような列にcharacter setが指定してあるDDLを発行させる方法のメモ書きです。

確認したバージョンは sequel 4.2.0, adapterにmysql2 0.3.13です。 

create table example1(
  id bigint not null,
  url varchar(255) character set ascii not null,
  constraint pk primary key (id)
) engine=InnoDB, default character set='utf8mb4';

先にMySQLにテーブルを作ってdumpをしても、認識してくれません(´・ω・`)

bundle exec sequel -e development ./config/database.yml -d
Sequel.migration do
  change do
    create_table(:example1) do
      primary_key :id, :type=>Bignum
      String :url, :size=>255, :null=>false
    end
  end
end
bundle exec sequel -e development ./config/database.yml -D
Sequel.migration do
  change do
    create_table(:example1) do
      primary_key :id, :type=>"bigint(20)"
      column :url, "varchar(255)", :null=>false
    end
  end
end

これを実行しても、列に指定してあるcharacter setがおちてしまいます。あと create table のオプションに InnoDB, utf8が出ています。utf8mb4 を指定したのに・・。このへんは Sequel::MySQL.default_engine Sequel::MySQL.default_charset Sequel::MySQL.default_collate あたりで設定するみたいです。今回はcreate_tableの時に指定してみます。

mysql> show create table example1 \G;
*************************** 1. row ***************************
       Table: example1
Create Table: CREATE TABLE `example1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

ERROR:
No query specified

というわけで、以下のように修正してみました。まず、create_table する時に明示的に engine と charset を指定しました。 また列定義で明示的に character set asciiを追加しました。オマケでchangeからup/downに変更しています。

Sequel.migration do
  up do
    create_table(:example1, :engine => 'InnoDB', :charset=>'utf8mb4') do
      primary_key :id, :type=>"bigint(20)"
      column :url, "varchar(255) character set ascii", :null=>false
    end
  end
  down do
    drop_table(:example1)
  end
end

↑を実行すると、↓のような結果にできました。

mysql> show create table example1 \G;
*************************** 1. row ***************************
       Table: example1
Create Table: CREATE TABLE `example1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) CHARACTER SET ascii NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

ERROR:
No query specified

ただし、この方法だとRDB間の差異は吸収できません。String :url, :size=>255, :charset=>'ascii'と書けると嬉しいんですけどね・・。知ってる人いたら教えてください。

 

READMEを書くときはMarkdown形式で書くと思うのですが、四六時中その記法で書いているわけでは無いので、なかなか覚えられないわけです。Rubyの場合はRDoc形式で書くのが普通なのかな?まぁWikiにしろ何にしろ似たような記法増えすぎだと思うのです。

わたしのようなゆとりには、プレビューしながら確認できるととても幸せになれます。とは言っても、ブラウザ上で入力なんてしたくないし、自分のお気に入りのエディタを使いたいわけです。

こちらの記事にあるツールを使っていたのですが、どうも自分の環境ではファイルを保存しなおすと、高確率でreadでこけるようで・・。

というわけで(上記ツールを参考にして)自分で作ってみました。

 

 

こちらの記事で書いた、Apacheのログを生成し続けるスクリプトをRubyGemsに登録してみました。

なので、今後は

gem install apache-loggen

とインストールするだけで、

apache-loggen

というコマンドが使えるようになります。使い方は今までと同じです。(以下のような感じです) 

apache-loggen [--rate=0] [--limit=0] [--rotate=0] [--progress] [--json] [outfile]
  --rate     毎秒何レコード生成するかの指定。0以下は制限無し。
  --limit    最大何件出力するか。0以下は延々と出力し続ける。
  --rotate   ローテーションの間隔。単位は秒指定。0以下はローテーションしない。
  --progress STDERRに経過情報を出力する
  --json     apacheのログ形式ではなくJSON形式で出力する

  outfile    指定するとファイルに出力します。省略するとSTDOUTに出力します。

毎秒10件をaccess.logに出力。60秒毎にローテーション。経過をSTDERRに表示。

apache-loggen --rate=10 --rotate=60 --progress access.log

また、この機構を使って独自のログ形式を出力できるようにしました。 以下のように使います。

require 'apache-loggen/base'
LogGenerator.generate(conf=nil, gen_obj=nil, &block)

confはnilを渡すとARGVの内容をパースします。プログラムで直接指定する場合は、Hashを渡す事でデフォルトの設定を上書きする事ができます。実際にログを出力する処理は、gen_objにLogGenerator::Baseを継承しgenerate(context, config)メソッドを定義したクラスのインスタンスを渡すか、ブロックを与えます。両方指定した場合は、gen_objが先に実行され、blockが評価されます。(blockではgen_objで出力したrecordを受け取る事ができます)

いくつかサンプルを置いておきます。

独自のログ形式を出力したい場合。

require 'apache-loggen/base'
class MyGen < LogGenerator::Base
  def generate(context, config)
    return "#{Time.now.to_s} #{context.inspect}\n"
  end
end
LogGenerator.generate(nil, MyGen.new)

もしくは

require 'apache-loggen/base'
LogGenerator.generate do | context, config, record |
  "#{Time.now.to_s} #{context.inspect}\n"
end

上記の2つは同じ処理です。

Apacheログに新しく情報を追加したい場合。

require 'apache-loggen/base'
class MyGen < LogGenerator::Apache
  # オリジナル実装はhashをJSONか1行の文字列にしているが
  # 今回はそれに情報を追加する
  def format(record, config)
    record["process_time"] = grand(1000000) + 1000
    if config[:json] then
      return record.to_json + "\n"
    else
      return %[#{record['host']} - #{record['user']} [#{Time.now.strftime('%d/%b/%Y:%H:%M:%S %z')}] "#{record['method']} #{record['path']} HTTP/1.1" #{record['code']} #{record['size']} "#{record['referer']}" "#{record['agent']}" #{record['process_time']}\n]
    end
  end
end
LogGenerator.generate(nil, MyGen.new)

LogGenerator::ApacheはLogGenerator::Baseを継承しており、generateの中でformatを呼び出しています。このformatの実装が、1行分のレコードをhashとしてうけとりJSONか1行の文字列に変換するという処理をしているので、これをOverrideするとApacheログの生成ロジックを使いつつ書式を変更する事ができます。

いくつかのサンプルはREADMEにも書いてあります。

(∩´∀`)∩

 

最新の記事はこちらです。

Fluentdのテストをする過程で欲しかったので、Apacheのログを生成し続けるスクリプトを書きました。

と言っても生成部分はTreasureDataのスクリプトを丸ぱくりで流量制限をするオプションを追加して延々と出力するようにしただけです。

frsyuki++。

手元でサクッとproductionで確認できる人はうらやましす。

使い方は以下のような感じです。詳細はREADMEを見てください。

ruby sample_apache_gen.rb [--rate=0] [--limit=0] [--rotate=0] [--progress] [--json] [outfile]
  --rate     毎秒何レコード生成するかの指定。0以下は制限無し。
  --limit    最大何件出力するか。0以下は延々と出力し続ける。
  --rotate   ローテーションの間隔。単位は秒指定。0以下はローテーションしない。
  --progress STDERRに経過情報を出力する
  --json     apacheのログ形式ではなくJSON形式で出力する

  outfile    指定するとファイルに出力します。省略するとSTDOUTに出力します。

ローテーションについては以下のルールでローテーションします。

  hoge.log --> hoge.[yyyy-MM-dd_HHmmss].log

例えば、こんな感じで使います。
毎秒10件をaccess.logに出力。60秒毎にローテーション。経過をSTDERRに表示。

ruby sample_apache_gen.rb --rate=10 --rotate=60 --progress access.log

普通はクラス化して、出力内容を決める部分をBlockで渡すようにして再利用性を高めると思うのですが、ごにょごにょ(´・ω・`)

ソースはここに置いておきます。
https://github.com/tamtam180/apache_log_gen 

これでWeb屋じゃなくてもそれっぽいログが出来るのでFluentdで遊べますね(∩´∀`)∩

Ruby(1.9.3)のURI.parseはホスト名にアンダースコアが入っているとURI::InvalidURIErrorが発生します。

require 'uri'
p URI.parse('http://hoge_fuga.example.com/page?id=xxx#yyy')
`initialize': the scheme http does not accept registry part: hoge_fuga.example.com (or bad hostname?) (URI::InvalidURIError)

仕様として、URIについては、RFC-2396とRFC-3986, IRIとしてRFC-3987があります。
アンダースコアはRFC-2396ではNGだったけど、RFC-3986ではOKになっているのですが、どうやらRubyのURIライブラリの対応が追いついていない(?)ようです。ちょっとRuby界隈には疎いのでどういう状況なのかは正確には把握していません。

代替案を探していたら、addressableというライブラリが対応しているという事なので、さっそく使ってみました。

さくっとgemでインストールします。addressableのバージョンは2.2.8でした。

gem install addressable

簡単な確認。

require "addressable/uri"
uri = Addressable::URI.parse("http://hoge_fuga.example.com/page?id=xxx#yyy")
p uri.host
p uri.port
p uri.request_uri
"hoge_fuga.example.com"
nil
"/page?id=xxx"

パースできました。
ただ、標準のURIだと上記の場合でもportは80を返してくれるのですが、addressableは返してくれないみたいです。しょんぼり。

参考:
http://d.hatena.ne.jp/kitamomonga/20100316/ruby_gem_addressable_howto