- 投稿日:2022-03-23T23:14:58+09:00
【LINE BotをAPI連携させたいだけなのに #1】エラー祭りへようこそ
LINE Botを思い通りに動かしたーい!! LINE Botでプッシュメッセージとリプライメッセージ(おうむ返しと固定テキスト)返すところまではできた。(やっとのことで。息も絶え絶えで。) そして息切れしながら向かった先は、 脳みそに新たなひらめきをくれるAPI連携Botが作りたい 仕事ではプランナーとして企画制作の1000本ノックを日々行い、大学院ではアート作品を作る私。煮詰まった時に脳みそをリフレッシュさせたい!自分の思考の外から新たな風がほしい!! そんな時のお助けアイテムとして、「デザインに関してのインスピレーションを与える名言」 というステキAPIからお言葉を頂戴しようと思った。 まずは単純なオウム返しBot 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const PORT = process.env.PORT || 3000; const config = { channelSecret: 'ひみつ', channelAccessToken: 'ひみつ' }; const client = new line.Client(config); async function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } return client.replyMessage(event.replyToken, { type: 'text', // テキストメッセージ text: event.message.text // }); } const app = express(); app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)')); app.post('/webhook', line.middleware(config), (req, res) => { if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); console.log('検証イベントを受信しました!'); return; } else { console.log('受信しました:', req.body.events); } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); ここまではできた。 この後特定の単語でのリプライもできた。 今度はAPIを連携させる "axios" を利用できるようにするため、コードを書き換える。 npm i axios をターミナルに入力。 そして、下記のコードを元に、APIを引っ張ってくるコードを入れる。 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const config = { channelSecret: 'ひみつ', channelAccessToken: 'ひみつ' }; const client = new line.Client(config); async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } const app = express(); app.post('/webhook', line.middleware(config), (req, res) => { if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); console.log('検証イベントを受信しました!'); return; } else { console.log('受信しました:', req.body.events); } Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); ここにひらめき名言をもらうQuotes on DesignとAPI連携できるようコードを完成させたい 入れるコードはこちら。 // axiosライブラリを呼び出す const axios = require('axios'); // 実際にデータを取得する getRequest 関数 async function getRequest() { let response; try { response = await axios.get('https://quotesondesign.com/wp-json/wp/v2/posts/?orderby=rand'); console.log(response.data); } catch (error) { console.error(error); } } // getRequest を呼び出してデータを読み込む getRequest(); しかし、鬼のエラー祭りが私の足を止める。 一歩進むたびに現れる奴ら(=エラー) API連携のため、まず新しいファイルを作った私の前に現れたのは、Webhook設定できない問題。 これは私がMessaging API設定で/Webhookを入力していなかった、というドえらいシンプルなミスをしていたせいでした。 そして次に現れたのが、 ターミナルに文字打てない問題。 こいつが長居するのよ・・・ しかし刻々と時間は過ぎ、メンタルがしおれていくのを感じた私は思った。 「よし、ファイルごと消そう」 しかし私はこの時点で、Control+Cやってエラー出てるファイル消してゼロからやり直す、という作業を、かれこれ20回以上はやっている。 エラーたちがいよいよ盛大に祭りを始める PROBLEMSだけでなく、ターミナルにも直にエラーが表記されているとここら辺で(やっと)気が付いた私。 なるほど!なら検索して直せばいいだけじゃない と思ったら大間違い!初心者は検索そのものがトンチンカンなのでなかなか求める答えドンピシャのページにたどり着かない。 エラーの意味はわかった。 けれど、答えがわからない。 私が出会ったエラーたち(一部) ReferenceError: sampleFunction is not defined at handleEvent これは他の人の記事を読んで、 const sampleFunction = async (event) => { を、コードのどこかに入れる必要がある、というところまではたどり着けた。 でも、ど、どこに・・・・??? コードを最初から一つ一つじー---っと読み込む。 これは何の指示をしているのか、何の設定をするために書いているのか。 コードをじっくり読み返した私が、const sampleFunction = async (event) => {を入れるのはここに違いない!と思ったのは こちらご査収ください。 // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます const sampleFunction = async (event) => { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } そしてターミナルに出てきたのは、 SyntaxError: Identifier 'axios' has already been declared 初めましてのエラー。 意味はわかるよ、わかるの。 構文が違うよ、ってことだよね・・・うん・・・わかった。 ここまでの紆余曲折。 あまりに悔しいので、正しい構文を見つけてやります。 次回、 「#2 ついにLINE BOtとAPIが連携する」 乞うご期待。 おわり!
- 投稿日:2022-03-23T22:30:07+09:00
気になった飲食店を探すシンプルなLINE Botを作ってみた
飲食店をさくっと調べて、後から見返したい 気になるお店が見つかった時、どうしますか? いつもと違う道を散歩中にお好みの飲食店を発見したり、グルメな友達が行きつけのお店を紹介してくれたのを聞いて、気になるお店が見つかった時、皆さんどうされていますか? メモアプリは二度手間だし、ググると後から見返すのに不便 私はiPhoneのメモアプリに店名を書き留めておくか、店名をググって、グルメ情報サイトを見るかどっちかだと思います。 特に休日の朝は、カフェで大好きなカプチーノを飲みながらのんびりするのが好きなので、朝早くから営業している居心地の良さそうなカフェを見つけた時は宝物を探し当てたかのような、ほっこりした気持ちになります。更に、カプチーノがメニューにあれば、そりゃーもう言うことないですよ!そのまま入店できない時は、店名を絶対忘れないようにメモっておきます。 メモアプリに入力した場合は、あとから店を探すのは意外にめんどくさくて二度手間です。メモアプリからえぇーと、店名をコピーして、Google開いて貼り付けて、はぃ、検索ってな感じで時間がかかります (お前どこまで遅いねん!)。 一方で、直接検索サイトで探してみると、複数の検索結果に多少目移りしてしまうものの、それなりにさくっとお店の情報にいきつきます。但し、ググって見つけたのは良いけど、後から見返すことができない。整理整頓が苦手な私は、お気に入りに登録したつもりが、分からなくなってしまった、、あーまた検索みたいなことが度々発生します。 ホットペッパーAPI X LINE Botが救世主に さくっとお店を探せて、その情報に戻れるのがベスト 大好きなカフェを見つけた時も、お店の検索と情報の記録にフラストレーションを感じていた私は、①さくっと探せる ②後から容易に情報に戻れる これらの2つのニーズを満たせたらいいなと思い、LINE Botの制作を行いました。 LINE Botの特徴として、友達登録するだけで簡単に使ってもらえて、普段から使い慣れているLINEアプリを開くだけで、多くの人にすぐに使っていただける早さと手軽さは大きなメリットです。 まずはAPI探し 課題解決したいことが決まったら、次は利用可能なAPI探し! APIとは、アプリケーションの開発を容易にするために、あらかじめ備えられたソフトウエア資源。共通して用いられる機能を一定の規約に基づいて利用します。会社によって、無料版があったり、有料版だけの場合もあります。 プログラミングを始めた時にはそもそもAPIという用語も知らなかったけど、こんなに便利なものがあるんだなー!困っていることを解決するエンジニア特有のシェア文化に改めて感心しました。 無料版で使えそうなAPIはホットペッパーだけ 馴染みがある飲食店検索サイトは、4つ。ホットペッパー以外にも、ぐるなび、食べログ、Rettyを検討しましたが、2022年3月現在無料版が使えるのはホットペッパーだけでした。ということで、ホットペッパーAPIを使わせていただくことにしました! 公式ホットペーパーAPI 【参考】飲食店検索アプリのAPI いざLINE Botを制作しよう LINE公式アカウントの作成 LINE Botを作成するには、LINE Developersにアクセスし公式アカウントの作成します。チャネルシークレットとチャネルアクセストークンが取得できたら準備完了です。 LINE公式アカウントの作成手順 ngrokの利用準備 次にngrokを使うためのアカウント登録が必要になります。こちらも事前準備が必要になります。 ngrokの利用準備手順 実行環境の準備 VScodeのインストール node.jsとnpmのインストール VScode内で、jsファイルを作成 ターミナルでフォルダをnpm管理できるように初期化 コード 取得したホットペッパーAPIを入れて実行します。 const sampleFunction = async (event) => { // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。 const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=ここにAPIキーを入れる&format=json&keyword='+encodeURI(event.message.text)); console.log(res.data.results.shop[0].urls.pc); return client.replyMessage(event.replyToken, { type: 'text', text: res.data.results.shop[0].urls.pc }); } 続いてLINEサーバーからのWebhookデータを処理する部分です。 // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } 結果こんな感じになります 余計な部分も多いですが、、イメージをご理解いただくためのデモ動画をツイッターにあげたので、良かったらご覧ください。 コード全文 'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用 const config = { channelSecret: 'チャンネルシークレットをコピペ', channelAccessToken: 'アクセストークンをコピペ' }; const sampleFunction = async (event) => { // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。 const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=APIキーをコピペ&format=json&keyword='+encodeURI(event.message.text)); console.log(res.data.results.shop[0].urls.pc); return client.replyMessage(event.replyToken, { type: 'text', text: res.data.results.shop[0].urls.pc }); } const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return sampleFunction(event); } // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); 実際使ってみての改善ポイント 検索に引っかからない時、エラーが出てシステムが止まってしまう。でもどうしてよいか分からない。。(涙) ネットで調べてみると、検索にかからない時に、エラー情報を取得してシステムが止まらないようにするには「//try{ } catch (error)」を使用するとあったので試してみたものの、これでもエラーは回避できずに今回は断念。前述のコードから変えたのはここだけなので、//try{ } catch (error)の挟み方が間違っているのだろうか?はたまた、別の対処方法が抜け漏れているのか?Qiita Q&Aでも質問させていただきました。 const sampleFunction = async (event) => { // ホットペッパーAPIのURLとAPIキーを入れる。Event.message.textでメッセージ内容を拾いEncodeURIで日本語変換されたキーワードを返す。キーワード情報からホットペッパーのURLを表示。 try { const res = await axios.get('http://webservice.recruit.co.jp/hotpepper/shop/v1/?key=ここにAPIキーをコピペ&format=json&keyword='+encodeURI(event.message.text)); console.log(res.data.results.shop[0].urls.pc); //try{ } catch (error)で検索にかからない時に、エラー情報を取得してシステムが止まらないようにする }catch (error) { console.error(error)}; return client.replyMessage(event.replyToken, { type: 'text', text: res.data.results.shop[0].urls.pc }); } 名前を直接入力じゃなくて、場所から飲食店を探せないか フィードバックをお願いしたら、次は場所から検索できるのも作ってもらえると便利だねという意見を妻にもらいました。店名で探してライン上で見るのもそれなりに使えると思うけど、確かに場所で検索出来たら更にいいよね~。知らない場所を訪れた時も、場所を送るか、GPSから近くの飲食店を5つ位、距離かランキングのパラメータを使って取り出せたら面白そう。 何を隠そう、実はこれ実現したかったんですが、ホットペッパーの個別のパラメータを組み合わせていくのは、私の力不足でどうにもなりませんでした。プログラミング経験者からみればシンプルとお感じになるんでしょうが、今回のBot制作も、周りにサポートしていただいて、その後エラーとも格闘しながら苦しんだ果てに、どうにかここまでたどり着いた状況です、とほほ。。私にとってプログラミングは、暗闇の中のトンネルをとぼとぼ進んでいく感じ、まだまだそんな感覚が続きそうです、、光は見えてくるのだろうか? 至らぬ点が多い記事ですが、最後までご覧いただき、ありがとうございました!!
- 投稿日:2022-03-23T21:22:30+09:00
nodejsでcsv形式でログを取ってtar.gz形式で圧縮する
の続き 開発背景 電力が貴重なこの時代!学術研究でも計算機の消費電力は無視できない!そもそもCPU温度が下がらない状況は危ない! ということで常駐で温度を監視してログを取れるやつにまで昇華しました,やってることは基礎的なことのはずなんだけどな… ちなみに冷却装置の設定によっては室温との相関があるかもしれない,流体の熱伝導を考えればそれはそうという感じはあるが 機能 1秒毎にCPU温度を監視 設定温度を超過するとSlackにログを送信 毎分10n秒でログを.csvに書き込む excelとかで見るのを前提に 毎時0分でSlackにログを送信 動作確認の意味合いも兼ねて 毎日0時5分に前日の.csvを.tar.gzに圧縮し元の.csvを削除 もちろん最大圧縮 以下,反省と言い訳 csvの操作がクソ難しい こちらもオブジェクト型や配列型の酷使に慣れていなかったというのもあるが,単純にnpmのcsvの各ライブラリ(csv-parseとかcsv-stringifyとか)の仕様がかなり変わっていて導入が面倒だった,ごく最近のいい感じのブログとかもなかったので公式ドキュメントを見ないとなかなか実装できなかった,あっ営利目的でこの記事を参考にしてもいいけど参考にしたらここのリンクを広めといてね♡ async/await多すぎ問題 これってsyncなライブラリだよな〜と思ったらasyncでした〜みたいなことがちょっと多すぎて頭にきますよ〜,じゃけんasync/awaitかけますね(保険的な意味合いで),async functionで定義した関数をawaitなしで呼び出すとpromiseが返ってくるなんて初めて知りましたね…言われてみればそうなんだけど,本当にそうなんだ… .csvファイルの成形問題 ヘッダの有無くらいしか実装できなかったが,実際の利用においては問題はないと判断したのでこれで行くことにした,調べた限りではヘッダありで.csvをパースしてjsonライクにオブジェクト型でデータを操作する的な場合もあるらしいが,わざわざ各ループの間で状態を渡し続ける実装なんて面倒だし,もう1行目の完全一致だけで判定した方がはるかに早そうなのでなぁ… その無駄っぽい変数宣言いる? いる(確信),書いてるうちにわからなくなりかけたので必要だった(確信),ちなみに文字数と修飾語で用途を分けている 地味に温度の精度が悪い 1℃単位の測定しか許容されていなかったり誤差が無駄に大きかったりという噂がOpen Hardware Monitorにはあるらしいが,まぁ気休め程度だと思って使うことにする,実際に測定結果を見ていても面白いから問題なし コード index.js // IO plugin const fs = require('fs-extra'); const tar = require('tar'); const tryCatch = require('try-catch'); const scheduler = require('node-schedule'); // API plugin const { getJSON } = require('simple-get-json'); const { stringify } = require('csv-stringify/sync'); const { parse } = require('csv-parse/sync'); const csvStringify = stringify; const csvParsing = parse; //Util plugin const datetime = require('date-and-time'); const { IncomingWebhook } = require("@slack/webhook"); // Parameters const slackURL = "https://hooks.slack.com/services/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"; const slackWebhook = new IncomingWebhook(slackURL); const monitorURL = "http://localhost:8085/data.json"; const coreTempLimit = 60; // API for Open Hardware Monitor async function accessJSON() { try { var res = await getJSON(monitorURL); var raw = JSON.stringify(res, null, 4); var obj = JSON.parse(raw)['Children']; // for Open Hardware Monitor var tgt = obj[0]['Children'][1]['Children'][1]['Children']; // for Open Hardware Monitor return tgt; } catch (err) { console.log("something wrong happened with CPU Monitor!") tgt = [ { Text: "0", Value: "0" } // for open hardware monitor ]; return tgt; } } // Shaper for Open Hardware Monitor async function makeMessage(tgt) { var cpuParam = ""; var coreName = ""; var coreTemp = ""; var heatFlag = false; var i = 0; var j = 0; tgt.forEach(cpuCore => { coreName = cpuCore['Text']; // for Open Hardware Monitor coreTemp = cpuCore['Value']; // for Open Hardware Monitor cpuParam += (coreName + " is " + coreTemp + "\n"); floatCoreTemp = parseFloat(coreTemp); i = i + 1 if (floatCoreTemp > coreTempLimit) { j = j + 1; } }); if (i == j) { heatFlag = true; // whether need to notice or not } var txt = cpuParam + datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS') + "\n"; var flg = heatFlag; return [flg, txt]; } async function makeCSVtext(tgt) { var cpuParam = []; var coreName = ""; var coreNames = ["date"]; var coreTemp = ""; var coreTemps = [datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS')]; var heatFlag = false; var i = 0; var j = 0; tgt.forEach(cpuCore => { coreName = cpuCore['Text']; // for Open Hardware Monitor coreNames.push(coreName); coreTemp = cpuCore['Value']; // for Open Hardware Monitor floatCoreTemp = parseFloat(coreTemp); coreTemps.push(floatCoreTemp); i = i + 1 if (floatCoreTemp > coreTempLimit) { j = j + 1; } }); if (i == j) { heatFlag = true; // judge whether to notice or not } cpuParam = [coreNames, coreTemps]; var txt = csvStringify(cpuParam, { header: false }, { delimiter: "," }); var flg = heatFlag; return [flg, txt]; } // csv(array)_txt(string) Parse&Build interface async function parseCSVdata(txt) { var csv = await csvParsing(txt, { columns: false, delimiter: "," }); return csv; } async function parseCSVdataHead(txt) { var csv = await csvParsing(txt, { columns: false, delimiter: ",", to_line: 1 }); // get only header line return csv; } async function buildCSVdata(csv) { var txt = await csvStringify(csv, { header: false, delimiter: "," }); return txt; } // API for Slack async function sendMessage(flg, txt) { if (flg) { // grasp whether to notice or not try { await slackWebhook.send({ text: `${txt}` }); } catch (err) { var text = "temp flag is " + flg + ", but something error happened with slack!"; console.log(text); } } } async function sendLogging(flg, txt) { try { await slackWebhook.send({ text: `${txt}` }); } catch (err) { var text = "something error happened with slack!"; console.log(text); } } // commonly file system utils async function createFolder(path) { try { await fs.ensureDir(path); } catch (err) { var text = path + " (folder) can not be created!"; console.log(text); }; } async function createFile(path) { try { await fs.ensureFile(path); } catch (err) { var text = path + " (file) can not be created!"; console.log(text); }; } async function writeupFile(path, txt) { await createFile(path); try { await fs.appendFile(path, txt); } catch (err) { var text = path + " (file) can not be written!"; console.log(text); }; } async function writeupCSVdata(path, txt) { var preTxt; var preCsv; var csv; try { preTxt = await fs.readFile(path); } catch (err) { var text = path + " (file) can not be read!"; console.log(text); preTxt = ""; }; preCsv = await parseCSVdataHead(preTxt); csv = await parseCSVdataHead(txt); if (JSON.stringify(preCsv) == JSON.stringify(csv)) { // check a header, need to developed csv = await parseCSVdata(txt); csv.shift(); txt = await buildCSVdata(csv); } await writeupFile(path, txt); } async function createArchive(filePath, archivePath) { try { tar.create({ sync: true, file: archivePath, gzip: '-k -9' }, [ filePath ]) } catch (err) { var text = archivePath + " (archive) can not be created!"; console.log(text); }; } async function removeFileFolder(path) { try { fs.removeSync(path); } catch (err) { var text = path + " (file) can not be removed!"; console.log(text); }; } // initialize (async() => { createFolder(".", "/Logs"); })(); // routine definition const scheduleSendMessage = scheduler.scheduleJob("0-59 * * * * *", function() { var tgt; var txt; var flg; (async() => { tgt = await accessJSON(); [flg, txt] = await makeMessage(tgt); await sendMessage(flg, txt); })(); }); const scheduleSendLogging = scheduler.scheduleJob("0 0 */1 * * * ", function() { var tgt; var txt; var flg; (async() => { tgt = await accessJSON(); [flg, txt] = await makeMessage(tgt); await sendLogging(flg, txt); })(); }); const scheduleLogging = scheduler.scheduleJob("0,10,20,30,40,50 * * * * *", function() { var date = datetime.format(new Date(), 'YYYY_MM_DD'); var path = "./Logs/" + date + ".csv" var tgt; var txt; var flg; (async() => { tgt = await accessJSON(); [flg, txt] = await makeCSVtext(tgt); await writeupCSVdata(path, txt); })(); }); const manageLogfiles = scheduler.scheduleJob("0 5 0 */1 * *", function() { var cur = new Date(); var old = datetime.addDays(cur, -1); var curDate = datetime.format(cur, 'YYYY_MM_DD'); var oldDate = datetime.format(old, 'YYYY_MM_DD'); var curPath = "./Logs/" + curDate + ".csv"; var oldPath = "./Logs/" + oldDate + ".csv"; var curGzip = "./Logs/" + curDate + ".tar.gz"; var oldGzip = "./Logs/" + oldDate + ".tar.gz"; (async() => { await createFile(curPath); await createFile(curGzip); await createArchive(oldPath, oldGzip); await removeFileFolder(oldPath); })(); console.log(datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS')); var text = "logging files are regularLy updated!"; console.log(text); });
- 投稿日:2022-03-23T21:22:30+09:00
Node.jsで.csv形式でログを取って.tar.gz形式で圧縮する
の続き 開発背景 電力が貴重なこの時代!学術研究でも計算機の消費電力は無視できない!そもそもCPU温度が下がらない状況は危ない! ということで常駐で温度を監視してログを取れるやつにまで昇華しました,やってることは基礎的なことのはずなんだけどな… ちなみに冷却装置の設定によっては室温との相関があるかもしれない,流体の熱伝導を考えればそれはそうという感じはあるが 機能 1秒毎にCPU温度を監視 設定温度を超過するとSlackにログを送信 毎分10n秒でログを.csvに書き込む excelとかで見るのを前提に 毎時0分でSlackにログを送信 動作確認の意味合いも兼ねて 毎日0時5分に前日の.csvを.tar.gzに圧縮し元の.csvを削除 もちろん最大圧縮 以下,反省と言い訳 csvの操作がクソ難しい こちらもオブジェクト型や配列型の酷使に慣れていなかったというのもあるが,単純にnpmのcsvの各ライブラリ(csv-parseとかcsv-stringifyとか)の仕様がかなり変わっていて導入が面倒だった,ごく最近のいい感じのブログとかもなかったので公式ドキュメントを見ないとなかなか実装できなかった,あっ営利目的でこの記事を参考にしてもいいけど参考にしたらここのリンクを広めといてね♡ async/await多すぎ問題 これってsyncなライブラリだよな〜と思ったらasyncでした〜みたいなことがちょっと多すぎて頭にきますよ〜,じゃけんasync/awaitかけますね(保険的な意味合いで),async functionで定義した関数をawaitなしで呼び出すとpromiseが返ってくるなんて初めて知りましたね…言われてみればそうなんだけど,本当にそうなんだ… .csvファイルの成形問題 ヘッダの有無くらいしか実装できなかったが,実際の利用においては問題はないと判断したのでこれで行くことにした,調べた限りではヘッダありで.csvをパースしてjsonライクにオブジェクト型でデータを操作する的な場合もあるらしいが,わざわざ各ループの間で状態を渡し続ける実装なんて面倒だし,もう1行目の完全一致だけで判定した方がはるかに早そうなのでなぁ… その無駄っぽい変数宣言いる? いる(確信),書いてるうちにわからなくなりかけたので必要だった(確信),ちなみに文字数と修飾語で用途を分けている 地味に温度の精度が悪い 1℃単位の測定しか許容されていなかったり誤差が無駄に大きかったりという噂がOpen Hardware Monitorにはあるらしいが,まぁ気休め程度だと思って使うことにする,実際に測定結果を見ていても面白いから問題なし コード index.js // IO plugin const fs = require('fs-extra'); const tar = require('tar'); const tryCatch = require('try-catch'); const scheduler = require('node-schedule'); // API plugin const { getJSON } = require('simple-get-json'); const { stringify } = require('csv-stringify/sync'); const { parse } = require('csv-parse/sync'); const csvStringify = stringify; const csvParsing = parse; //Util plugin const datetime = require('date-and-time'); const { IncomingWebhook } = require("@slack/webhook"); // Parameters const slackURL = "https://hooks.slack.com/services/xxx/yyy/zzz"; const slackWebhook = new IncomingWebhook(slackURL); const monitorURL = "http://localhost:8085/data.json"; const coreTempLimit = 60; // API for Open Hardware Monitor async function accessJSON() { try { var res = await getJSON(monitorURL); var raw = JSON.stringify(res, null, 4); var obj = JSON.parse(raw)['Children']; // for Open Hardware Monitor var tgt = obj[0]['Children'][1]['Children'][1]['Children']; // for Open Hardware Monitor return tgt; } catch (err) { console.log("something wrong happened with CPU Monitor!") tgt = [ { Text: "0", Value: "0" } // for open hardware monitor ]; return tgt; } } // Shaper for Open Hardware Monitor async function makeMessage(tgt) { var cpuParam = ""; var coreName = ""; var coreTemp = ""; var heatFlag = false; var i = 0; var j = 0; tgt.forEach(cpuCore => { coreName = cpuCore['Text']; // for Open Hardware Monitor coreTemp = cpuCore['Value']; // for Open Hardware Monitor cpuParam += (coreName + " is " + coreTemp + "\n"); floatCoreTemp = parseFloat(coreTemp); i = i + 1 if (floatCoreTemp > coreTempLimit) { j = j + 1; } }); if (i == j) { heatFlag = true; // whether need to notice or not } var txt = cpuParam + datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS') + "\n"; var flg = heatFlag; return [flg, txt]; } async function makeCSVtext(tgt) { var cpuParam = []; var coreName = ""; var coreNames = ["date"]; var coreTemp = ""; var coreTemps = [datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS')]; var heatFlag = false; var i = 0; var j = 0; tgt.forEach(cpuCore => { coreName = cpuCore['Text']; // for Open Hardware Monitor coreNames.push(coreName); coreTemp = cpuCore['Value']; // for Open Hardware Monitor floatCoreTemp = parseFloat(coreTemp); coreTemps.push(floatCoreTemp); i = i + 1 if (floatCoreTemp > coreTempLimit) { j = j + 1; } }); if (i == j) { heatFlag = true; // judge whether to notice or not } cpuParam = [coreNames, coreTemps]; var txt = csvStringify(cpuParam, { header: false }, { delimiter: "," }); var flg = heatFlag; return [flg, txt]; } // csv(array)_txt(string) Parse&Build interface async function parseCSVdata(txt) { var csv = await csvParsing(txt, { columns: false, delimiter: "," }); return csv; } async function parseCSVdataHead(txt) { var csv = await csvParsing(txt, { columns: false, delimiter: ",", to_line: 1 }); // get only header line return csv; } async function buildCSVdata(csv) { var txt = await csvStringify(csv, { header: false, delimiter: "," }); return txt; } // API for Slack async function sendMessage(flg, txt) { if (flg) { // grasp whether to notice or not try { await slackWebhook.send({ text: `${txt}` }); } catch (err) { var text = "temp flag is " + flg + ", but something error happened with slack!"; console.log(text); } } } async function sendLogging(flg, txt) { try { await slackWebhook.send({ text: `${txt}` }); } catch (err) { var text = "something error happened with slack!"; console.log(text); } } // commonly file system utils async function createFolder(path) { try { await fs.ensureDir(path); } catch (err) { var text = path + " (folder) can not be created!"; console.log(text); }; } async function createFile(path) { try { await fs.ensureFile(path); } catch (err) { var text = path + " (file) can not be created!"; console.log(text); }; } async function writeupFile(path, txt) { await createFile(path); try { await fs.appendFile(path, txt); } catch (err) { var text = path + " (file) can not be written!"; console.log(text); }; } async function writeupCSVdata(path, txt) { var preTxt; var preCsv; var csv; try { preTxt = await fs.readFile(path); } catch (err) { var text = path + " (file) can not be read!"; console.log(text); preTxt = ""; }; preCsv = await parseCSVdataHead(preTxt); csv = await parseCSVdataHead(txt); if (JSON.stringify(preCsv) == JSON.stringify(csv)) { // check a header, need to developed csv = await parseCSVdata(txt); csv.shift(); txt = await buildCSVdata(csv); } await writeupFile(path, txt); } async function createArchive(filePath, archivePath) { try { tar.create({ sync: true, file: archivePath, gzip: '-k -9' }, [ filePath ]) } catch (err) { var text = archivePath + " (archive) can not be created!"; console.log(text); }; } async function removeFileFolder(path) { try { fs.removeSync(path); } catch (err) { var text = path + " (file) can not be removed!"; console.log(text); }; } // initialize (async() => { createFolder(".", "/Logs"); })(); // routine definition const scheduleSendMessage = scheduler.scheduleJob("0-59 * * * * *", function() { var tgt; var txt; var flg; (async() => { tgt = await accessJSON(); [flg, txt] = await makeMessage(tgt); await sendMessage(flg, txt); })(); }); const scheduleSendLogging = scheduler.scheduleJob("0 0 */1 * * * ", function() { var tgt; var txt; var flg; (async() => { tgt = await accessJSON(); [flg, txt] = await makeMessage(tgt); await sendLogging(flg, txt); })(); }); const scheduleLogging = scheduler.scheduleJob("0,10,20,30,40,50 * * * * *", function() { var date = datetime.format(new Date(), 'YYYY_MM_DD'); var path = "./Logs/" + date + ".csv" var tgt; var txt; var flg; (async() => { tgt = await accessJSON(); [flg, txt] = await makeCSVtext(tgt); await writeupCSVdata(path, txt); })(); }); const manageLogfiles = scheduler.scheduleJob("0 5 0 */1 * *", function() { var cur = new Date(); var old = datetime.addDays(cur, -1); var curDate = datetime.format(cur, 'YYYY_MM_DD'); var oldDate = datetime.format(old, 'YYYY_MM_DD'); var curPath = "./Logs/" + curDate + ".csv"; var oldPath = "./Logs/" + oldDate + ".csv"; var curGzip = "./Logs/" + curDate + ".tar.gz"; var oldGzip = "./Logs/" + oldDate + ".tar.gz"; (async() => { await createFile(curPath); await createFile(curGzip); await createArchive(oldPath, oldGzip); await removeFileFolder(oldPath); })(); console.log(datetime.format(new Date(), 'YYYY/MM/DD HH:mm:ss:SSS')); var text = "logging files are regularLy updated!"; console.log(text); });
- 投稿日:2022-03-23T20:40:41+09:00
zsh: command not found: npmが出た時の対処法
ある日npmコマンドを打つとエラーが出ました。 $ npm -v zsh: command not found: npm npmコマンドが一切使えなくなってしまいました。 解決までに行った方法を共有します。 環境はmacOS Montereyです。 nodebrewがインストール済みか確認する nodebrewと打って確認します。 色々と表示されれば問題ないです。一番上の行にバージョンの記載が出ます。 $ nodebrew nodebrew 1.2.0 Usage: ~~~ nodebrewをインストールする もしnodebrewが入っていなけばHomebrewを使用してインストールします。 $ brew install nodebrew Homebrewが入っていなれば下記コマンドでインストールできます。 $ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" ちなみにこちらのコマンドは公式からのコピーですのでご安心ください。 https://brew.sh/index_ja nodeをインストールして使用するバージョンを指定する nodeをインストールします。 nodebrew installでもインストールは可能ですが、install-binaryのほうがインストール時間が短いです。 $ nodebrew install-binary stable // 補足 stable 最新の安定版 latest 最新版 vXX.XX.X バージョン直接指定 nodeがインストールがされたかをnodebrew lsで確認します。 $ nodebrew ls v16.14.2 current: none currentに表示されるのが現在使用中のバージョンになります。 この場合はcarrentがnoneになっていて、v16.14.2がインストールされていますが使用するように設定されていない状態です。 nodebrew useで使用するバージョンを指定します。 こちらもinstall時同様、stableやlatestで指定することも可能です。 $ nodebrew use v16.14.2 or $ nodebrew use stable $ nodebrew use latest これで完了です。 確認してみます。 $ nodebrew ls v16.14.2 current: v16.14.2 $ node -v v16.14.2 指定したバージョンが使用されているのが確認できました。 npmコマンドも打てるようになります。 $ npm -v 8.5.0 原因は何? わかりませんでした。 数日前まで問題なくnpmコマンドが打てていたのに、ある日突然といった形です。 エラー文で検索すると同じエラーがmacのアップデートによって発生したと記載がありますので、そちらの可能性が高いと思っています。 pathを通せば解決する? エラー文で検索するとpath関係が原因だと書いてあるサイトがたくさん出てきました。 しかし今回、pathは関係なくnodeをインストールし直す形で解決しました。 もしこの記事の方法で解決できなければ、pathを疑ってみるのもありです。 参考にしたサイト https://qiita.com/sinmetal/items/154e81823f386279b33c https://github.com/hokaccha/nodebrew
- 投稿日:2022-03-23T15:56:26+09:00
asdfでNode.jsの環境設定
たまに Ubuntu の新しい環境で Node.js を入れることがあるので、メモ代わりに環境設定法を書いておく。 ネットで検索するとMacだったり、yarnを入れてなかったりするので。 asdf インストール 基本は公式サイトに従う。 非常に丁寧に書いてある。 インストール apt install curl git git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.9.0 .zshrcに設定追加 ~/.zshrc . $HOME/.asdf/asdf.sh 既存のバージョンファイルも有効にする これで .node-version も有効になる。プロジェクトによっては .node-version を使っているものもあるので。 $HOME/.asdfrc legacy_version_file = yes プラグインインストール nodejs https://github.com/asdf-vm/asdf-nodejs latest のところは使う Node.js のバージョンに合わせる。 asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git asdf install nodejs latest asdf global nodejs latest yarn https://github.com/twuni/asdf-yarn yarn は asdf でインストールするのが楽かなと思う。 npm でインストールすると新しい Node.js を入れた時に競合して面倒なことになる(過去に何度もあったので)。 asdf plugin add yarn asdf install yarn latest
- 投稿日:2022-03-23T09:03:53+09:00
VSCode上でNode.js(ES6で実装)のdebugをするための設定をしてみた
以下の記事を参照して頂ければと思います ※Qiitaの記事は全て個人的な記載であり、所属する組織団体とは無関係です 補足 ソースコード全体は以下。