Padrinoでlog4rを使う
以前PadrinoのロガーでMongoDBを使う記事を書きましたが、MongoDBだけで無くエラーレベルによってはメールで出したかったので、log4rを使ってみました。
まず、log4rの設定から
lib/log4r.yaml
ここで定義しているMongoOutputterとMailOutputterは自作です。Mailに関してはEMailOutputterというlog4rで提供されているものも有るのですが、日本語が通らないので使いません。
lib/mongo_outputter.rb
lib/mail_outputter.rb
次に設定です。MongoDBのコレクションとメールはここで作成します。メールは以前書いた独自で作ったクラスです。libに置いておきます。
app/app.rb
利用の仕方は
my_logger.info('abc')
のようになります。
以前はPadrinoのロガーの仕組みにのっかりましたが、その仕様に会わせるのがめんどうくさかったので、独自にのmy_loggerという形にしました。
まず、log4rの設定から
lib/log4r.yaml
log4r_config:
pre_config:
global: "DEBUG"
loggers:
- name: "development"
type: "Log4r::Logger"
level: "DEBUG"
trace: "true"
outputters:
- "Stdout"
- "Mongo"
- "Mail"
- name: "production"
type: "Log4r::Logger"
level: "DEBUG"
trace: "true"
outputters:
- "Mongo"
- "Mail"
outputters:
- name: "Stdout"
type: "StdoutOutputter"
formatter:
name: "P1"
type: "PatternFormatter"
pattern: "%d [%l] %c(%t) - %m"
date_pattern: "%H:%M:%S"
- name: "Mail"
type: "MailOutputter"
level: "WARN"
formatter:
name: "P2"
type: "PatternFormatter"
pattern: "%d [%l] %c(%t) - %m"
date_pattern: "%H:%M:%S"
- name: "Mongo"
type: "MongoOutputter"
safe: "false"
formatter:
name: "P3"
type: "PatternFormatter"
pattern: "%d [%l] %c(%t) - %m"
date_pattern: "%H:%M:%S"
ここで定義しているMongoOutputterとMailOutputterは自作です。Mailに関してはEMailOutputterというlog4rで提供されているものも有るのですが、日本語が通らないので使いません。
lib/mongo_outputter.rb
#coding: utf-8
require 'log4r/outputter/outputter'
require 'mongo'
class MongoOutputter < Log4r::Outputter
attr_accessor :collection
def initialize(_name, hash={})
super(_name, hash)
@safe = hash['safe'] ? hash['safe'] : false
if hash['host'] || hash['port'] || hash['db'] || hash['collection']
conn = Mongo::Connection.new(
hash['host'] ? hash['host'] : 'localhost',
hash['port'] ? hash['port'].to_i : 27017)
db = conn[hash['db'] ? hash['db'] : 'test']
@collection = db[hash['collection'] ? hash['collection'] : 'syslog']
end
end
def canonical_log(logevent)
@collection.insert({l: logevent.level, i: Time.now, d: logevent.data}, {safe: @safe})
end
end
lib/mail_outputter.rb
#coding: utf-8
require 'log4r/outputter/outputter'
class MailOutputter < Log4r::Outputter
attr_accessor :mail
def initialize(_name, hash={})
super(_name, hash)
end
def write(data)
@mail.send_mail({content: data})
end
end
次に設定です。MongoDBのコレクションとメールはここで作成します。メールは以前書いた独自で作ったクラスです。libに置いておきます。
app/app.rb
#coding: UTF-8
require 'mongo'
require 'log4r'
require 'log4r/yamlconfigurator'
class Api < Padrino::Application
register SassInitializer
register Padrino::Rendering
register Padrino::Mailer
register Padrino::Helpers
configure do
conn = Mongo::Connection.new('localhost', 27017)
Log4r::YamlConfigurator.load_yaml_file(File.dirname(__FILE__) + '/../lib/log4r.yaml')
logger = Log4r::Logger["development"]
logger.outputters.each do |it|
if it.name == "Mongo"
it.collection = conn['test']['syslog']
elsif it.name == "Mail"
it.mail = MyMail.new(
from: "noreply@example.com",
subject: "error",
to: "my@example.com"
)
end
end
set :my_logger, logger
end
def my_logger
settings.my_logger
end
end
利用の仕方は
my_logger.info('abc')
のようになります。
以前はPadrinoのロガーの仕組みにのっかりましたが、その仕様に会わせるのがめんどうくさかったので、独自にのmy_loggerという形にしました。
yii-environmentを使う
Yiiフレームワークには、開発環境と本番環境の設定を切り分ける仕組みがありません。
それだといちいち参考演算子を使って設定することになっちゃいます。
'connectionString' => YII_DEBUG ? 'pgsql:host=localhost;port=5432;dbname=development' : 'pgsql:host=localhost;port=5432;dbname=production'
これだと悲しいので、拡張機能のyii-environmentを使って切りわけるようにしてみます。
yii-environmentはconfig/main.phpに共通の設定を書いて、環境ごとに変わる値はconfig/mode_[環境名].phpに書きます。私の場合config/main.phpにproductionの設定を書いて、config/mode_development.phpに開発環境の設定を書き、config/mode_production.phpには何も書かないようにしています。
設定にはPostgreSQLのDB設定とdirectmongosuiteを使ったMongoDBの設定を記述してあります。
アプリケーションのデータはPostgreSQLに保存して、ログ、セッション、権限管理はMongoDBに保存するようにしています。
ではまずWebアプリケーションを作ります。
yiic webapp test
ファイルが生成されたら、production/extensionsに
yii-environmentとdirectmongosuiteをおきます。
次に以下のようにファイルを修正してください。
index.php
protected/config/main.php
protected/config/mode_development.php
protected/config/mode_production.php
最後に環境の設定を行います。nginxなら以下の通りです。
それだといちいち参考演算子を使って設定することになっちゃいます。
'connectionString' => YII_DEBUG ? 'pgsql:host=localhost;port=5432;dbname=development' : 'pgsql:host=localhost;port=5432;dbname=production'
これだと悲しいので、拡張機能のyii-environmentを使って切りわけるようにしてみます。
yii-environmentはconfig/main.phpに共通の設定を書いて、環境ごとに変わる値はconfig/mode_[環境名].phpに書きます。私の場合config/main.phpにproductionの設定を書いて、config/mode_development.phpに開発環境の設定を書き、config/mode_production.phpには何も書かないようにしています。
設定にはPostgreSQLのDB設定とdirectmongosuiteを使ったMongoDBの設定を記述してあります。
アプリケーションのデータはPostgreSQLに保存して、ログ、セッション、権限管理はMongoDBに保存するようにしています。
ではまずWebアプリケーションを作ります。
yiic webapp test
ファイルが生成されたら、production/extensionsに
yii-environmentとdirectmongosuiteをおきます。
次に以下のようにファイルを修正してください。
index.php
<?php
// common functions
require_once(dirname(__FILE__).'/protected/config/global.php');
// set environment
require_once(dirname(__FILE__) . '/protected/extensions/yii-environment/Environment.php');
$env = new Environment();
// set debug and trace level
defined('YII_DEBUG') or define('YII_DEBUG', $env->yiiDebug);
defined('YII_TRACE_LEVEL') or define('YII_TRACE_LEVEL', $env->yiiTraceLevel);
// run Yii app
//$env->showDebug(); // show produced environment configuration
require_once($env->yiiPath);
$env->runYiiStatics(); // like Yii::setPathOfAlias()
Yii::createWebApplication($env->configWeb)->run();
protected/config/main.php
<?php
return [
// Set yiiPath (relative to Environment.php)
'yiiPath' => '/opt/yii/framework/yii.php',
'yiicPath' => '/opt/yii/framework/yiic.php',
'yiitPath' => '/opt/yii/framework/yiit.php',
// Set YII_DEBUG and YII_TRACE_LEVEL flags
'yiiDebug' => false,
'yiiTraceLevel' => 0,
// Static function Yii::setPathOfAlias()
'yiiSetPathOfAlias' => [
// uncomment the following to define a path alias
//'local' => 'path/to/local-folder'
],
// This is the main Web application configuration. Any writable
// CWebApplication properties can be configured here.
'configWeb' => [
'basePath' => dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
'name' => 'My Web Application',
// preloading 'log' component
'preload' => ['log'],
// autoloading model and component classes
'import' => [
'application.models.*',
'application.components.*',
'ext.directmongosuite.components.*',
],
'behaviors' => [
'edms' => [
'class'=>'EDMSBehavior',
],
],
'modules' => [
],
// application components
'components' => [
'user' => [
// enable cookie-based authentication
'allowAutoLogin' => true,
],
'edms' => [
'class' => 'EDMSConnection',
'dbName' => 'test',
],
'session' => [
'class' => 'EDMSHttpSession',
'collectionName' => 'session',
],
'db' => [
'connectionString' => 'pgsql:host=localhost;port=5432;dbname=test',
'username' => 'username',
'password' => 'password',
],
'errorHandler' => [
// use 'site/error' action to display errors
'errorAction'=>'site/error',
],
'log' => [
'class' => 'CLogRouter',
'routes' => [
[
'class' => 'EDMSLogRoute',
'levels' => 'trace, info, error, warning, edms',
'collectionName' => 'syslog',
],
],
],
'authManager' => [
'class' => 'EDMSAuthManager',
'authFile' => 'authmanager',
'defaultRoles' => ['admin', 'member', 'normal', 'guest'],
]
],
// application-level parameters that can be accessed
// using Yii::app()->params['paramName']
'params' => [
// this is used in contact page
'adminEmail'=>'webmaster@example.com',
],
],
];
protected/config/mode_development.php
<?php
return [
'yiiDebug' => true,
'yiiTraceLevel' => 3,
'configWeb' => [
'modules' => [
'gii' => [
'class' => 'system.gii.GiiModule',
'password' => false,
],
],
'components' => [
],
],
];
protected/config/mode_production.php
<?php
return [
];
最後に環境の設定を行います。nginxなら以下の通りです。
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
include fastcgi_params;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_index index.php;
fastcgi_param YII_ENVIRONMENT "development";
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass phpfpm;
}
PL/pgSQLでテーブルのカラムで唯一のランダム文字列を作成する
以前ランダム文字列を作成するPL/pgSQLの関数を紹介しました。
これを使って(ちょっとだけ関数名を変えています)、テーブルに存在しないユニークな値をとってきます。
これを使って(ちょっとだけ関数名を変えています)、テーブルに存在しないユニークな値をとってきます。
-- テーブルで一意の値を生成する
-- 引数
-- p_table_name : テーブル名
-- p_column_name : カラム名
-- p_try_count : 試行回数
-- p_length : 長さ
-- p_length : 生成する文字列の長さ
-- p_source : 生成する文字列の要素
-- 戻り値
-- ランダムな文字列
CREATE OR REPLACE FUNCTION dyn_random_string(
p_table_name TEXT
,p_column_name TEXT
,p_try_count INT DEFAULT 10
,p_length INT DEFAULT 10
,p_source TEXT DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
) RETURNS TEXT AS $$
DECLARE
w_target TEXT;
w_value TEXT;
BEGIN
FOR i IN 1..p_try_count LOOP
w_value := NULL;
w_target := uv_string_random(p_length, p_source);
EXECUTE
'SELECT ' || p_column_name ||' FROM ' || p_table_name ||
' WHERE ' || p_column_name || ' = $1'
INTO
w_value
USING
w_target
;
IF w_value IS NULL THEN
RETURN w_target;
END IF;
END LOOP;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
jQueryでクリックしたふりをする
jQueryでリンクをクリックしてみたかったんですが、
jQuery('#aaa').click();
では遷移しませんでした。
調べてみたらEventオブジェクトを作成してそれを投げればいいようです。
ちなみに、これだとclickのイベントハンドラーは実行されませんでした。
動作はChrome18で確認しています。
jQuery('#aaa').click();
では遷移しませんでした。
調べてみたらEventオブジェクトを作成してそれを投げればいいようです。
ちなみに、これだとclickのイベントハンドラーは実行されませんでした。
動作はChrome18で確認しています。
<html>
<body>
<a id="aaa" href="http://www.yahoo.co.jp">Yahoo</a>
<br/>
<a href="javascript:aaa();">ok</a>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"/></script>
<script type="text/javascript">
function aaa() {
var a = jQuery('#aaa')[0];
var e = document.createEvent('MouseEvents');
e.initEvent('click', true, true);
a.dispatchEvent(e);
}
</script>
</body>
</html>
MongoDBでSQL的なMAXの計算と日単位の集計
以下のようなデータがあるときに、ある期間のmaxを取る集計と、日単位の合計を出す集計をMongoDBのMapReduceで計算してみます。
{data_id: 123, insert_ts: Date(), data_count: 10}
2012/4/9 23:00から2012/4/10 24:00の間のdata_idの中で最大の件数を集計する。
data_id=123のデータを日単位でdata_countを合計する。
ちなみにdata_countは数字ならparseIntなんてしなくてもいいですが、何故か手持ちのデータが文字列だったので、わざわざ付けています。
{data_id: 123, insert_ts: Date(), data_count: 10}
2012/4/9 23:00から2012/4/10 24:00の間のdata_idの中で最大の件数を集計する。
m = function() { emit(this.data_id, parseInt(this.data_count)) };
r = function(key, values) {
var max_count = 0;
for (var i = 0; i < values.length; i++) {
if (max_count < values[i]) {
max_count = values[i]
}
}
return max_count;
};
res = db.data.mapReduce(m, r, {query: {insert_ts:{$gte: new Date(2012,3,9,18), $lt: new Date(2012,3,9,19)}}, out: 'tmp'});
data_id=123のデータを日単位でdata_countを合計する。
m = function() {
var dt = this.insert_ts;
dt.setTime(dt.getTime() + 9 * 3600);
emit(String(dt.getFullYear()) + "-" + String(dt.getMonth() + 1) + "-" + String(dt.getDate()), parseInt(this.data_count));
};
r = function(key, values) {
var data_count = 0;
for (var i = 0; i < values.length; i++) {
data_count += values[i];
}
return data_count;
};
res = db.data.mapReduce(m, r, {query: {data_id: 123}, out: 'tmp2'});
ちなみにdata_countは数字ならparseIntなんてしなくてもいいですが、何故か手持ちのデータが文字列だったので、わざわざ付けています。



