尝试将Express的访问日志输出到MongoLab中
首先在这个领域,如果有一些建议或批评,请随时提出。
背景信息一段时间以前,我把自己的网站放在与朋友一起共享的租用服务器上。但在迁移所使用的服务器的过程中,我决定将几乎所有管理的域名移动到Heroku上。在这个过程中,我并没有特别考虑日志之类的事情,所以除了Logplex(通过$ heroku logs显示的内容)之外,没有其他日志了。换句话说,只有最新的1500行。虽然并没有太多访问量,但没有日志也感觉很孤单,所以我决定想办法解决这个问题。
最近的潮流使用Logstash或Fluentd作为中间件,最后将数据流式传输到用户喜欢的数据库中,并根据需要进行监控和分析。一些好用的分析工具通常与后端和密切合作,并引导用户付费。还有一些例子,将后端进一步分为MongoDB和ElasticSearch两个阶段,分别处理各自擅长的任务。
最近的潮流使用Logstash或Fluentd作为中间件,最后将数据流式传输到用户喜欢的数据库中,并根据需要进行监控和分析。一些好用的分析工具通常与后端和密切合作,并引导用户付费。还有一些例子,将后端进一步分为MongoDB和ElasticSearch两个阶段,分别处理各自擅长的任务。
嗯,因為這只是個興趣網站,所以日誌的量並不太多,不至於花錢去使用TD或BigQuery等服務,更不用說租借虛擬機器來一直運行Fluentd或ElasticSearch了,這種程度的舉措都有些猶豫。其實我本來也想試試Kibana之類的,但果然還是無法實現吧。
有趣的网站甚至连前端服务器都没有统一,使用的是Express/Node.js、Sinatra/Ruby、http/go等等不同的技术来搭建网站,根据建站时间进行试验,所以加入像Fluentd这样的中间件是很有吸引力的。但是在免费阶段运营时,由于服务的原因,后端可能不得不更换。
暫時使用MongoDB
最終結局全都放棄所以,各种梦想都膨胀起来,然后全部重置了。嗯,在Heroku上以免费模式玩的话,把它连接到MongoDB可能是个不错的选择。虽然没有考虑得太远。
由于没有足够的余地来放置像Fluentd这样的中间层,所以至少我会考虑将可灵活应用的MongoDB放在中间层。而且,如果在Heroku支持的Data Store Add-ons中有免费套餐,那么选择余地就不太多了。如果使用MongoDB,可以使用MongoLab,它允许免费使用高达496MB的套餐,而且如果使用MongoDB的Capped Collection,设定最大大小后,它会自动删除旧数据以防溢出,因此维护起来非常方便。
添加MongoLab
toyoshim@tss:~/workspace/Heroku (master) $ heroku addons:add mongolab:sandbox
Adding mongolab:sandbox on chime... done, v11 (free)
Welcome to MongoLab. Your new subscription is being created and will be available shortly. Please consult the MongoLab Add-on Admin UI to check on its progress.
Use `heroku addons:docs mongolab` to view documentation.
toyoshim@tss:~/workspace/Heroku (master) $ heroku addons:add mongolab:sandbox
Adding mongolab:sandbox on chime... done, v11 (free)
Welcome to MongoLab. Your new subscription is being created and will be available shortly. Please consult the MongoLab Add-on Admin UI to check on its progress.
Use `heroku addons:docs mongolab` to view documentation.
当在Heroku的仪表盘上进入项目的资源选项卡时,会发现MongoLab已添加。如果本地的mongo shell是2.x版本,则无法通过mongo shell登录,因此需要以某种方式准备3.0版本的mongo shell(参考:解决无法通过mongo shell登录MongoLab的方法)。
或者,通过 Web 用户界面完成所有操作也是一个选择。在这里,我会留下命令行备忘录。虽然大小为496MB,但我只创建了大约200MB作为在中途想要做些其他事情时的备用。影响应该不会太大,当保存期限减少一半左右时。
root@d2760283f209:/# mongo ********.mongolab.com:*****/heroku_******** -u <dbuser> -p <dbpassword>
MongoDB shell version: 3.0.8
connecting to: ********.mongolab.com:*****/heroku_********
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
http://docs.mongodb.org/
Questions? Try the support group
http://groups.google.com/group/mongodb-user
rs-********:PRIMARY> db.createCollection("log", { capped: true, size: 200000000 })
{ "ok" : 1 }
}
将Express集成到程序中
将其转换成JSON格式morganを使っていたので、こいつを拡張してMongoDBに吐き出すことにしました。今から思えば1から書いたほうが良かった気もしますが。
Morgan在自定义日志格式方面有两种方法。第一种方法是使用自定义令牌格式的方法,如介绍所示。
app.use(morgan('{"method": ":method"}'));
只需将格式作为参数传递给它
{"method": "GET"}
会被整形成这种样子。
另一种方法是使用morgan.format()的方式。
morgan.format('json', '{"method": ":method"}');
app.use(morgan('json'));
こんな感じで事前に任意の名前(ここではjson)でフォーマットを登録して、後から名前で指定してあげます。どちらでも結果は同じなのでお好みで。
またフォーマットも書式で指定する方法以外に関数を渡す方法もあるようです。実際、書式で指定した場合には内部的にcompile()が走り、生成された関数がフォーマッタとして登録されるようです。
MongoDBへの出力
app.use(morgan('json', {stream: dbstream}));
app.use(morgan('json', {stream: dbstream}));
みたいな形でstream.Writableを継承したinstanceを渡してあげると、各ログの出力が1回のwriteで渡ってくるようになります。エラー処理とか省いてざっくり書くと、
function MongoDBStream() {
stream.Writable.call(this);
this._db = null;
this._collection = null;
this.morgan = morgan('json', {stream: this});
}
util.inherits(MongoDBStream, stream.Writable);
MongoDBStream.prototype._write = function(chunk, encoding, cb) {
this._collection.insertOne(JSON.parse(chunk)).then(() => {
cb(null);
}, (e, db) => {
cb(e);
});
};
MongoDBStream.prototype.connect = function(url, collection) {
return new Promise((resolve, reject) => {
mongodb.MongoClient.connect(url, (err, db) => {
if (err)
return reject(err);
this._db = db;
this._collection = db.collection(collection);
resolve();
});
});
};
var dbstream = new MongoDBStream();
dbstream.connect('mongodb://....', 'log').then(() => {
app.use(dbstream.morgan);
// ...
});
こんな感じで動きます。_writeの中でJSON.parse()してるのがイマイチ。フォーマッタを関数で渡して、フォーマッタが呼ばれた段階でオブジェクトを構築してDBにストアしちゃうのも手かもしれません。まぁ、性能気にしない用途なので。ユルフワ感を大切にしました。
npmモジュール化
せっかくなので、というか自分自身がたくさんサーバがある関係でnpmモジュールにしちゃいました。momologって名前で登録してあります。morgan使ったMongoDB向けのロガーだからmomolog。PromiseとかES6の()=>{}構文を使ってるので、nodeは0.*系は全滅です。自分は4.2.1で作業してました。これ使えば数行の修正でMongoDBにログが吐き出せます。
在Heroku上的实际操作
ダッシュボードからMongoLabを追加。検索ボックスでMongoとか入れれば2つくらいにまで絞れますので、あとはポチッと。で、ここにできたMongoLabのアイコンをクリックすると以下のコンソールが開きます。
色々と見えちゃってますが、パスワード見えてないので良しとしましょう。Add collectionから作る場合にはCappedにチェックを入れます。MongoDBなので事前にCollectionを作らなくても最初のログ書き込み時に自動生成されるのですが、その場合にはCappedにはならないので事前に作ったほうが良いです。
所以,大致上添加以下修改。推荐使用express 4.x版本和node 4.x版本。
{
...
"dependencies": {
...
"momolog": "0.1.0"
},
...
var express = require('express')
var momolog = require('momolog')
var app = express();
var log = momolog();
// 環境変数にユーザ名・パスワード込みのURIが自動登録されているので
// それをそのまま渡すだけ。第2引数は先ほど作ったCollection名。
log.connect(process.env.MONGOLAB_URI, 'log').then(() => {
// 以下のuse()はconnect前でも大丈夫。早いほうが安心。
app.use(log.morgan());
// 他の設定はここより後で。
// morgan登録前にハンドラを登録するとログ対象から外れます。
app.listen(process.env.PORT, () => {
// Started.
});
});
哎呀,真简单。
总结所以,我已经制作了大约五万个工具,并且尽力去寻找了合适的工具(虽说已经找到不少了)。今后,我还想为Ruby和Go编写相同的输出工具,并在日志收集后创建一个简单的分析页面,或者利用现有的工具。子索引等问题以后再考虑。如果格式不合适,稍后再进行转换(为此我准备了200MB的空间!)。


所以,大致上添加以下修改。推荐使用express 4.x版本和node 4.x版本。
{
...
"dependencies": {
...
"momolog": "0.1.0"
},
...
var express = require('express')
var momolog = require('momolog')
var app = express();
var log = momolog();
// 環境変数にユーザ名・パスワード込みのURIが自動登録されているので
// それをそのまま渡すだけ。第2引数は先ほど作ったCollection名。
log.connect(process.env.MONGOLAB_URI, 'log').then(() => {
// 以下のuse()はconnect前でも大丈夫。早いほうが安心。
app.use(log.morgan());
// 他の設定はここより後で。
// morgan登録前にハンドラを登録するとログ対象から外れます。
app.listen(process.env.PORT, () => {
// Started.
});
});
哎呀,真简单。