「log.io」を使ってログを集約し、ブラウザからリアルタイムにモニタリングしてみます。
log.io
http://logio.org/
log.ioは、TCPでログを受信するとともに、ブラウザからログを参照することができます。
Webサーバの機能が付いているので楽ちんなのと、WebSocketを使っているので、リアルタイムにログが出力されてきます。
それから、TCPでログを受信するので、AndroidやArduinoやNode.jsなど、様々なプラットフォームからのログを集約することができそうです。
log.ioのセットアップ
npmでモジュール化されています。
npm install -g log.io
mkdir ~/.log.io
vi ~/.log.io/server.json
log.io-server
server.jsonはこんな感じです。
{"messageServer":{"port":6689,"host":"【起動させるマシンのIPアドレス】"},"httpServer":{"port":6688,"host":"【起動させるマシンのIPアドレス】"},"debug":false,"basicAuth":{"realm":"【適当な名前】","users":{"【ログインユーザ名】":"【ログインパスワード】"}}}
【起動させるマシンのIPアドレス】には、log.ioを起動させたマシンのホスト名またはIPアドレスを指定します。
上記の場合、ポート6688にWebサーバが立ち上がり、ポート6689にログ受信を待ち受けます。
http:// 【起動させるマシンのIPアドレス】:6688
を開いてみます。ログインパスワードを聞かれますので、server.jsonで指定した【ログインユーザ名】、【ログインパスワード】を入力すればログインできて以下のような画面が表示されます。
ログの送信
それではログを送信しています。
送信は、TCPで以下のようなフォーマットの文字列を送ります。
+msg|streamName1|sourceName1|this is log message\0
streamName1やsourceName1で区別されるログを表示したり非表示にしたりできます。
TCPで送信すればよいだけなので、様々なプラットフォームから送信することができます。
今回は以下で送信してみます。
・Android
・Arduino
・Node.js
・Javascript(ブラウザ)
出力後はこんな感じ。
Android
クラスファイル化してみました。
packagecom.example.logiotest;importandroid.content.Context;importandroid.util.Log;importjava.io.OutputStream;importjava.net.Socket;importno.nordicsemi.android.log.LogSession;importno.nordicsemi.android.log.Logger;importno.nordicsemi.android.log.LogContract.Log.Level;publicclassLogIo{publicStringtag="logio";publicStringdefault_stream_name=null;publicStringdefault_source_name=null;publicStringhost=null;publicintport;Socketsocket=null;OutputStreamouts=null;enumConnectState{Disconnected,Connecting,Connected};ConnectStateisconnected=ConnectState.Disconnected;LogSessionlogSession=null;publicLogIo(Stringhost,intport){this.default_stream_name="stream";this.default_source_name="source";this.host=host;this.port=port;}publicLogIo(Contextcontext,Stringkey,Stringname){logSession=Logger.newSession(context,key,name);}publicvoidlog(Stringmessage){log3(default_stream_name,default_source_name,message);}publicvoidlog2(Stringsource,Stringmessage){log3(default_stream_name,source,message);}synchronizedpublicvoidlog3(Stringstream,Stringsource,Stringmessage){if(host!=null){Log.d(tag,"["+stream+"] ["+source+"] - "+message);finalStringpacket="+msg|"+stream+"|"+source+"|"+message+"\0";newThread(newRunnable(){@Overridepublicvoidrun(){try{byte[]buffer=packet.getBytes("UTF-8");if(isconnected==ConnectState.Connected){outs.write(buffer);outs.flush();}elseif(isconnected==ConnectState.Disconnected){isconnected=ConnectState.Connecting;socket=newSocket(host,port);outs=socket.getOutputStream();isconnected=ConnectState.Connected;outs.write(buffer);outs.flush();}}catch(Exceptionex){try{if(outs!=null){outs.close();outs=null;}socket.close();socket=null;}catch(Exceptionex2){}isconnected=ConnectState.Disconnected;}}}).start();}if(logSession!=null){Stringpacket=message;if(source!=null)packet="["+source+"] "+packet;if(stream!=null)packet="["+stream+"] "+packet;Log.d(tag,packet);Logger.log(logSession,Level.INFO,packet);}}}
※LoggerSessionなるものが混在してわかりにくいですが、nRF Loggerというもので、後述します。ということで、盲目的に、appのbuild.gradleのdependenciesに以下を追記しておきます。
implementation 'no.nordicsemi.android:log:2.3.0'
あとは、以下のように呼び出せばよいです。
LogIologio=newLogIo(“【起動させるマシンのIPアドレス】”,6689);logio.log("こんばんは1");logio.log2("fromAndroid","こんばんは2");
【起動させるマシンのIPアドレス】のところに、log.ioを立ち上げたマシンのホスト名またはIPアドレスを指定します。
それと、ネットワークを使うので、AndroidManifest.xmlに以下を追記します。
<uses-permission android:name="android.permission.INTERNET" />
Arduino
Arduinoもログ送信可能です。
#include <WiFiClient.h>
WiFiClientlogio_client;Stringlogio_host;intlogio_port=6689;Stringdefault_stream_name="stream";Stringdefault_source_name="source";voidlogio_setup(Stringhost,intport){logio_host=host;logio_port=port;}voidlogio_log(Stringmessage){logio_log3(default_stream_name,default_source_name,message);}voidlogio_log2(Stringsource,Stringmessage){logio_log3(default_stream_name,source,message);}voidlogio_log3(Stringstream,Stringsource,Stringmessage){Serial.println("["+stream+"] ["+source+"] - "+message);if(!logio_client.connected()){if(!logio_client.connect(logio_host.c_str(),logio_port)){Serial.println("connection failed");return;}}if(logio_client.connected()){Stringpacket="+msg|"+stream+"|"+source+"|"+message;logio_client.write(packet.c_str(),strlen(packet.c_str())+1);logio_client.flush();}}
あとは、setup()でWiFiをAPに接続したのち、以下を呼び出し、
logio_setup("【起動させるマシンのIPアドレス】", 6689);
loop()などの適当なところで以下を呼び出します。
logio_log("Test Message");logio_log2("fromArduino”, "TestMessage");
Node.js
モジュール化しました。
'use strict';constnet=require('net');varConnectState={disconnected:0,connecting:1,connected:2};classLogIo{constructor(host,port){this.host=host;this.port=port;this.default_stream_name='stream';this.default_source_name='source';this.isconnected=ConnectState.disconnected;this.client=newnet.Socket();this.client.on('close',()=>{this.isconnected=ConnectState.disconnected;console.log('[LogIo] disconnected');});}log(message){this.log3(this.default_stream_name,this.default_source_name,message);}log2(source_name,message){this.log3(this.default_stream_name,source_name,message);}log3(stream_name,source_name,message){console.log(`[${stream_name}] [${source_name}] - ${message}`);varpacket=`+msg|${stream_name}|${source_name}|${message}\0`;if(this.isconnected==ConnectState.connected){this.client.write(packet);}elseif(this.isconnected==ConnectState.disconnected){try{this.isconnected=ConnectState.connecting;this.client.connect(this.port,this.host,()=>{this.isconnected=ConnectState.connected;this.client.write(packet);console.log('[LogIo] connected to '+this.host+':'+this.port);});}catch(error){console.error(error);}}else{console.log('[LogIo] connecting');}}}module.exports=LogIo;
あとは、呼び出し側で以下を呼び出せばよいです。
varlogio=newLogIo('【起動させるマシンのIPアドレス】',6689)logio.log('test message');logio.log2('fromNodejs','test message');
javascript
Javascriptには残念ながらTCP通信する機能がないため、TCP通信するNode.jsサーバを立ち上げて、そこからlog.ioサーバに転送してもらいましょう。
サーバ側の実装です。
'use strict';constHELPER_BASE=process.env.HELPER_BASE||'../../helpers/';constResponse=require(HELPER_BASE+'response');constLOGIO_HOST=process.env.LOGIO_HOST||'【起動させるマシンのIPアドレス】';constLOGIO_PORT=process.env.LOGIO_PORT||6689;varLogIo=require('./logio');varlogio=newLogIo(LOGIO_HOST,LOGIO_PORT)exports.handler=async(event,context,callback)=>{if(event.path=='/logio-post'){varbody=JSON.parse(event.body);if(body.stream&&body.source)logio.log3(body.stream,body.source,body.message);elseif(!body.stream&&body.source)logio.log2(body.source,body.message);elseif(!body.stream&&!body.source)logio.log(body.message);elsethrow"invalid param";returnnewResponse({"status":"OK"});}}
Node.jsのところで作成したlogio.jsを流用しています。
エンドポイント「/logio-post」に、JSONでPOSTすれば、log.ioサーバに転送します。
POSTするJSONのフォーマットは以下の感じです。
{
"stream": "【任意のStream名】", // オプション
"source": "【任意のSource名】", // オプション
"message": "【任意のメッセージ】", // 必須
}
あとは、ブラウザのJavascriptで以下のように呼び出せばよいです。
varurl="http:// 【起動させるマシンのIPアドレス】:6689/logio-post";varparam={source:"fromHttp",message:"こんにちは"};do_post(url,param);functiondo_post(url,body){constheaders=newHeaders({"Content-Type":"application/json; charset=utf-8"});returnfetch(newURL(url).toString(),{method:'POST',body:JSON.stringify(body),headers:headers}).then((response)=>{if(!response.ok)throw'status is not 200';returnresponse.json();});}
log.ioもうちょっと
触ってみて気づいたけど、
・StreamやSourceを作らないと初回のメッセージが表示されないのが面倒だなあ。。。
・日付を自動的に入れてくれるとありがたいのに。。。
・ブラウザだけで、StreamやSourceが削除できないなあ。。。
(おまけ) Androidでローカルログ保存
Androidでは、log.ioで集約する方法のほかに、Android内にインストールした別のアプリに集約し、そのアプリからモニタリングする方法もあります。
その便利なアプリが「nRF Logger」です。
Google Play:nRF Logger
https://play.google.com/store/apps/details?id=no.nordicsemi.android.log&hl=ja
もともと、BLEを使ったアプリのLoggerとして作られたようです。
とりあえず、このアプリをAndroidにインストールしておきます。
ログを送信する側のために、ライブラリを用意してくれています。
NordicSemiconductor/nRF-Logger-API
https://github.com/NordicSemiconductor/nRF-Logger-API
ソースはすでに、LogIo.javaの中に実装しています。
appのbuild.gradleのdependenciesにimplementation 'no.nordicsemi.android:log:2.3.0'
の追加もお忘れずに。
で、使うときには以下の感じです。
LogIologio=newLogIo(this,"testKey","testName");logio.log("こんばんは1");
(ちなみに、ログレベルはINFO固定にしてます)
こんな感じで、nRF Loggerアプリからログをモニタリングできます。
以上