JUNのブログ

JUNのブログ

活動記録や技術メモ

TyporaとDropboxで快適メモ環境構築

快適なメモ環境を探して1年弱... いろいろ探して試しみましたが, 自分にピッタリ合うものはなかなか見つからず...

とりあえず現状いい感じのメモ環境を構築出来たので共有したいと思います.

私がメモ環境に求める条件は以下の通り

  • 使いやすい
  • オフラインで使える
  • クラウド上に自動でバックアップを取ってくれる
  • Markdownで書ける
  • カテゴリ(ディレクトリ)で分けることができる

とりあえず上記の全ての条件を満たした環境を構築したのでこの記事に書いていきたいと思います.

結論

Typora + Dropbox

です.

完成イメージ

f:id:JUN_NETWORKS:20200204014924p:plain

環境

MacOS でも Windows でもいけると思います.

やり方

Typoraのインストール

Typora公式サイト からインストールします.

Dropboxのインストール

Dropbox 公式サイト からDropboxアプリケーションをインストールします.

markdownファイルの保存先ディレクトリをDropboxと同期されているディレクトリ内に作成する.

Ubuntuの場合は, デフォルトでは /home/user/Dropbox/ でした. 他のOSは多分適当に探せば見つかります.

Typoraの設定

設定を開き以下の項目を設定します.

起動時にデフォルトで開くディレクトリを先程作成したディレクトリに変更します.

f:id:JUN_NETWORKS:20200204014749p:plain

次に自動保存を有効にします.

f:id:JUN_NETWORKS:20200204014826p:plain

自動保存の保存間隔は"高度な設定"を開き, autoSaveTimer の値を変更すれば変えることができる.

画像を保存するディレクトリを指定します.

これをすることで貼り付けた画像などが一箇所にまとまり, さらにはDropboxの自動同期の恩恵も受けられるので別のPCで開いた時に画像がなくなることがないです. また, 相対パスを使用するようにすることで他のPCで開いたときのパスの違いを受けにくくなるので, 画像をどの端末でも表示できます.

f:id:JUN_NETWORKS:20200204014844p:plain

最後に表示の欄からファイルツリー表示に変更すれば完了です.

f:id:JUN_NETWORKS:20200204014855j:plain

これで快適メモ環境が完成です!

感想

今回は今自分が使っているメモ環境と構築方法を紹介しました.

メモ環境論争はいつまで経っても正解が出ませんが, 自分はしばらくこれでいきます. 形式もMarkdownなので他のメモアプリを使うとなった場合でも移動しやすいと思います.

今回はTyporaに下書きを書いてからはてなブログにコピペするスタイルで記事を作成しましたが, 画像らへんや表示形式の違いなどで結局二度手間だった感があるので, ブログ記事の下書きにはあんまり向いてないのかなぁって気がしました.

2019年振り返りと2020年について

この記事を書いている時点で2020年になってから11日が経ってしまっていますが, そんなことは気にせず去年の振り返りと今年2020年の目標設定みたいなのをやっていきたいと思います.

前回こんな感じの1年振り返りの記事を書いたのは2月の誕生日だったので, 今年も誕生日に書くか〜って思っていましたが, やっぱり新年始まってすぐ書いた方がきりが良い気がするので今年以降は1月に書いていこうと思います.

ちなみに去年のやつはこれです.

jun-networks.hatenablog.com

そういえば去年の3月にTwitterのアカウント消えたので最初の四半期は何してるか詳しくわからないです.

1月

  • Kaggle の Quoraコンペに参加した.

そういえば1年前はKaggleやってましたね... 確か銅メダルだった気がします.

ただ, 多くの参加者が最終サブミットをしなかったから銅メダル取れただけだし, なんなら殆どコピペだったので記事にはしてないです.

f:id:JUN_NETWORKS:20200111011456p:plain

2月

2月は何したかなぁってカレンダー見たら結構色々ありましたね.

この中で特筆すべきことは 学内ハッカソン と 株式会社Sportip (以下 Sportip と呼ぶ) にジョインしたことでしょうか.

学内ハッカソンの方では確か Globally and Locally Consistent Image Completion という論文を実装しようとして, 途中で学習に2ヶ月かかることに気づいて制作を途中で辞めた気がします. 結局GLCICは完成しませんでしたが, これをしたことにより, 論文から実際にコードとして実装する難しさ, 論文の読み方 などを知れたので, 目に見える結果は出なかったけどいい経験だったと思います.
結局, 学内ハッカソンに出したのは前日に作った小さなブラウザミニゲームですが, そこでGLCICの話をする機会があり, そこで 株式会社EBILAB賞 をいただき, その後 社員の方々と会食をご一緒させて頂きました.

Sportipについては, この前からお誘いを頂いており, 何回か面談をした後にジョインしました.
現在もSportipの方で業務に参加させてもらってます. 業務内容に関しては言えませんが, スタートアップの速度と, Sportipがどんどん成長していくのを感じています. 頑張らねば!

3月

  • Mix Leep Study #36 に参加した
  • Mix Leep Study #37 に参加した.
  • めっちゃ業務やってた.
  • Twitterアカウント消えた.

こうして見るとめっちゃ Mix Leep 参加してますね.

特に#37 の量子コンピュータの回が印象に残っています.

Twitterアカウントに関しては以下の記事に全て書いたのでこっち読んで.

jun-networks.hatenablog.com

4月

  • 3年生になった.
  • 基本情報落ちた.

1年生の時にやらかしたので4年目でやっと3年生になれました. 長かった...!

基本情報は 「まぁいけるやろ」って感じで舐めプしたら落ちました. アホですね.

5月

  • Maker Faire Kyoto に参加した(一般)
  • UdemyのAWS講座をやった.
  • Computer Science (CS) に興味を持ち始める.

Maker Faire Kyoto は凄い作品多かったです(語彙力のNASA).

UdemyのAWS講座はめちゃくちゃわかりやすくて超おすすめしたいところですが, 今はもう販売していないのです. 残念.

6月

  • N高の説明会行った
  • LinkedInのリクルーターから連絡きた,
  • 競プロ少し始めた.

N高については転校出来そうならしようと思ってたけど, 単位的に卒業までに2年必要らしいのでやめた.

LinkedInのリクルーターから連絡来たけど, アルバイトじゃなくて正社員にしたくて連絡したらしいので話は無くなった.

このへんから競プロ始めた気がする. けど, ここ数ヶ月全くしていないのでそろそろやらねば

7月

  • 学校辞めたい欲が高まってた.
  • Netflix契約した(沼)
  • 中国語勉強し直そうとして1週間で辞めた.
  • 京アニの事件で病んでた.

僕はだいたい3ヶ月に一回くらい学校辞める辞めるって言ってるんですけど, この月はかなり辞めたい欲が強かったです. 理由としては3年生になって授業数が多り, 専門科目も多くなり, 好きな情報工学系の勉強ができなくなっていたからです. 自分の学科は電子機械系なので情報系は基本的に独学です. 一応デジタル回路の授業があるので完全に独学ではないですが...

中国語は, 昔中国に4年住んでいたので, その当時はネイティブスピーカーだったのですが, 今では全く話せません. 10年も話していないと仕方ないね.
でも, 昨今の中国語需要に伴い自分も勉強しなおそうとしましたが, 生きるのに必須では無いので1週間で辞めちゃいました. 英語は普段からコミュニケーションや情報収集で使うのでモチベーションが保つのですが, 中国語は普段使わないのでモチベーションが続きませんでした.

8月

アメリカ行った!! (帰りに台湾も行った)

8月はとにかく「アメリカに行った」これに尽きます. アメリカに行った件は全て以下の記事に書いたので気になる人は読んでください.

jun-networks.hatenablog.com

9月

普段私はリモートで業務に参加しているのですが, 夏休みの後半2週間は東京で対面で働いてました. 世間ではリモートワークを賞賛する声が多いようですが, やはり対面で仕事をしたほうが仕事はしやすい気がします.

10月

低レイヤーに手を出す

  • x86本 を読んだ
  • TOEICの勉強始めた
  • 弊校のロボコンの応援に行った
  • 授業が辛くて病んでた.

この辺から低レイヤーに興味を持ち始めました. 特にシステムプログラミングなどに興味が湧いてきました. 最初に読んだ本は x86CPUエミュレータ を作る本なのですが, これが本当にわかりやすくてめちゃくちゃ楽しかったのを覚えています. この本をシステムプログラミングの最初の本として読んだのは幸運でした. システムプログラミングは今までブラックボックだったコンピュータという高度な機械をどんどん理解出来るようになっていく過程がとんでもなく楽しいです.

jun-networks.hatenablog.com

ロボコン高専に3年半在学して初めて見に行きました. 自分はソフト系の人間なわけですが, それでもロボットが実際に動き, 競技を行っている姿には興奮しました. 私のクラスにもロボコン部の人間は数人いるわけですが, 本当に尊敬します.

授業に関しては, どうしても機械系科目に対して興味がでなくて辛かったです.
頭の中でCPUとかのことを考えながら過ごしてました. 微積の授業とデジタル回路の授業のおかげでメンタルは保たれました.

11月

  • Rust始めた
  • HHKBを手に入れた. 我流タイピングの矯正を始めた.
  • 42TokyoのWebテストに合格してPicineに参加確定した.

Rustを始めたのはいいけど, チュートリアル終わってから触って無いので何も覚えてないです. てかRustってシステムプログラミングとかに使うのはわかるねんけど, もう少し敷居の低い手軽に作れるやつないん? Web鯖とかか?

42Tokyoは, プレスリリースを見た瞬間に会員登録して, その1週間後にはWebテストやりました. ちなみにPicineは3月参加予定です. 受かるといいなぁ.

12月

  • CS を本気で勉強する決意をする
  • 全裸監督 見た
  • Turing Complete FM (TCFM) を発見した
  • TOEIC 初受験 670点 を取った
  • CPU制作のために論理ゲートなどをトランジスタで作った.

12月は1,2週間目はTOEICの勉強をしていた. おかげで初受験で670点というなかなかの成績が取れた. 個人的には800点以上は欲しいのでまた余裕が出来た時に受験してみます.

CSを本気で勉強する決意は, 前から勉強しなきゃって思ってたんですが, 以下のブログが決定的でした. 情報系の人は全員読んでほしい. 心に刺さりすぎて泣きたくなってくるので.

masa-lab.hateblo.jp

論理ゲートとか作ったりしたのは論理ゲートの仕組みを理解したかったためです. 一応DFFとかも作りたいなと思っています. CPU作るときにはIC使いますが, 論理ゲートなどがブラックボックなのもあれなのでとりあえず手を動かしてみた感じです.

TCFM は正直システムプログラミング初心者の僕には何言ってるか8割くらいわからないけれど, それでも聞いているとシステムプログラミング楽しそ〜〜って感じるのでたまに聞いてます.

2019総振り返り

気づいてたかもしれませんが, 今年は殆どハッカソン等のコンテストに参加していまんせん. なので, 対外的な成果や活動実績が殆ど無く「今年はあんまり何もしてないなぁ」って思ってましたが, こうして振り返ってみると対外的な成果は無くとも人間的にも技術者としても成長と経験を多く重ねることの出来た1年だったのではないかと思います.

学業の方に関しては, 専門科目が増えてきたことにより今まで気にならなかった学科と自分のやりたいこととのギャップに苦しんだ1年でした.

2019年で特筆すべきことはアメリカに行ったことでしょうか.
アメリカに行き多種多様な人々と会話したり,イベントに参加したりしたことにより, 自分の中の価値観が大きく変わりました. アメリカに行く前までは留年による同世代に対する遅れや高専に入って結構な年数が経ってしまい心理的に辞めるハードルが上がり辞めにくくなっていました. しかし, アメリカで 年齢,性別,人種などは関係なく, 大人でもやりたいことのために大学に入り直す人もいるし, アメリカでバスケをするためにアメリカの高校に通う同い年の日本人の子など多種多様な価値観の元で自由に生きて自分のやりたいことを追いかけている人達を沢山見ました. そこで自分も気づきました. 「自分の好きなように生きたらいいし, 世間の目なんか関係ない」 ということに. なのでこれからは僕の好きなように生きます. 学びたいことを学び, 行きたいところに行きます. たとえ中卒になろうが結婚できなかろうが悔いのない選択をしていきたいと思います.
アメリカに行ったことで自分の中の重荷が取れて楽になった気がします. アメリカに行かせてくれた私の両親に感謝の限りを捧げます.

2020年は...

2020年は人生の大きな転換期になる気がしています.

とりあえず今ある2020年の目標は

  • CPU, コンパイラ, OSを作る (つまりはシステムプログラミング!)
  • 自分のポートフォリオサイトを作成する.
  • 毎日充実した日々を送り, 好きなことをいっぱい勉強する.
  • いろんな分野をやってみて自分の進みたい道を探す
  • 定期的に運動する.
  • 42Tokyoに受かる!!

今年は低レイヤーなどを中心とした含めたCS系の基礎固めの年になりそうです.

プログラミングを初めて3年くらいが経とうとしていて, 最近になってやっとこCSといものを知りしっかりと勉強したいと思うようになりました. 今年は"勉強したい"ではなく"勉強する"年に出来たらいいなと思います. そして来年のこの時期に1年の振り返りをした時に今年よりも充実し満足し幸せな1年を送れたらいいなと思います.

なんだかポエムっぽい記事になっちゃいましたが, こんな感じでこの記事を締めさせていただきます. 今年もよろしくお願いします.

Python Logging SlackHandler の作り方

python で logging を使っていると, たまに 「このログをSlackに飛ばしたいなぁ」ってときがあります.

なのでログをSlackに投稿する Logging Handler 作りました.

Slack側の準備

Incoming Webhook アプリをSlackに導入します.

その他の管理項目 -> Appの管理 でワークスペースのアプリ設定ページにアクセス出来ます.

アプリの設定ページ内のAppディレクトリを検索して Incoming Webhook をSlackに追加します.

f:id:JUN_NETWORKS:20191130005317p:plain
Slack Incoming Webhook

Slackに追加 を押したらどのチャンネルに送信するかというのが出てくるので ログを送信したいチャンネルを選んでください. 選んだらIncoming Webhook インテグレーションの追加 を押してください.

f:id:JUN_NETWORKS:20191130005543p:plain

そうすると Incoming Webhook アプリの設定画面に飛ぶと思います.

その中にある Webhook URL をメモしておいてください.

試しに何かメッセージを送ってみましょう

import requests

webhook_url = "先程メモしたWebHookURL"
requests.post(webhook_url, json={"text": "Pythonから送信したテストメッセージ"})

これを実行すると先程指定したチャンネルにメッセージが来たと思います.

f:id:JUN_NETWORKS:20191130010223p:plain

テキストの投稿以外にも Incoming Webhook が出来ることは沢山ありますが, 今回は扱いません. 気になる人は Slackワークスペース内にある Incoming Webhook アプリ の設定ページを見てください.

SlackHandler を作る

いよいよ本題です. SlackHandler を作っていきます.

SlackHandler を作成する前に Python Loggingモジュールにおける Handler とは何かを理解しないといけません.

Pythonの公式ドキュメント によると

ハンドラは、(ロガーによって生成された) ログ記録を適切な送信先に送ります。

らしいです.

では作っていきましょう.

handlers.py というファイルを作って, 中身を以下の様にします.

import logging

import requests


class SlackHandler(logging.StreamHandler):

    def __init__(self, url):
        super(SlackHandler, self).__init__()
        self.url = url

    def emit(self, record):
        msg = self.format(record)
        self.send_message(msg)

    def send_message(self, text):
        message = {
            'text': text,
        }

        requests.post(self.url, json=message)

コードの解説をします.

SlackHandler が継承している logging.StreamHandler とは[Python公式ドキュメント] から引用すると

logging コアパッケージに含まれる StreamHandler クラスは、ログ出力を sys.stdout, sys.stderr あるいは何らかのファイル風 (file-like) オブジェクト (あるいは、より正確に言えば write() および flush() メソッドをサポートする何らかのオブジェクト) といったストリームに送信します。

つまりは, ログをどこかに送る 基礎的なHandlerです.

def __init__() では スーパークラスである StreamHandlerインスタンスメソッド __init__() を呼び出して StreamHandler の各種変数やメソッドの初期化をします.
また, 引数 url で webhook_url をインスタンス変数に登録しています.

def emit(self, record) は StreamHandler のメソッドで, 例によってPython公式ドキュメントから引用すると,

emit(record)

フォーマッタが指定されていれば、フォーマッタを使ってレコードを書式化します。 次に、レコードが終端記号とともにストリームに書き込まれます。 例外情報が存在する場合、 traceback.print_exception() を使って書式化され、 ストリームの末尾につけられます。

つまりは来たrecordをsetFormatterで設定したformat形式に変換してStreamに記録するというメソッドです. 今回の場合はSlackにログを送信したいのでこのメソッドをオーバーライドすればいいですね.
今回は来たrecordをformatの形式に変形するのはそのままに, その後Slackにメッセージを送信する関数 send_message() を作り, それを使ってformat形式に変形されたログを送信しています.

SlackHandlerを使う

SlackHandler を使う時は他の Logging Handler とほとんど同じように使えます.

以下のコードを実行するとSlackにログが送信されます.

import logging

from handlers import SlackHandler

webhook_url = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# logger の作成
logger = logging.getLogger(__file__)
logger.setLevel(logging.INFO)
# Slack Handler の作成
slack_handler = SlackHandler(webhook_url)
slack_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s')
slack_handler.setFormatter(formatter)
logger.addHandler(slack_handler)


def main():
    logger.info("This is Logging Test!!")

    ans = 1 + 2
    logger.info(f"1 + 2 = {ans}")


if __name__ == "__main__":
    main()

f:id:JUN_NETWORKS:20191130031947p:plain
Slackに送られてきたメッセージ

SlackHandler (応用編)

SlackHandlerの改造

先程作成したSlackHandlerは最低限の機能を満たしていますが, 私が実際に使っているものはさらに改造を施したものです. 折角なのでご紹介します.

class SlackHandler(logging.StreamHandler):

    def __init__(self, url, username="", icon_emoji=":computer:"):
        super(SlackHandler, self).__init__()
        self.url = url
        self.username = username
        self.icon_emoji = icon_emoji

    def emit(self, record):
        msg = self.format(record)
        self.send_message(msg, record.levelno)

    def send_message(self, text, level_num):
        icon_emoji = self.icon_emoji
        if level_num > 30:  # Warning 以上
            # メンションを飛ばす
            text = "<@MEMBERID> " + text
            icon_emoji = ":exclamation:"

        message = {
            'text': text,
            'username': self.username,
            'icon_emoji': icon_emoji
        }

        requests.post(self.url, json=message)

emojiusername に関しては Slackのワークスペース内の Incoming Webhookアプリの説明に書いてあるのでそっちを見てほしいです. また, SlackのIncoming Webhooksを使い倒す - Qiita もわかりやすくていいと思います.

それではそれ以外の部分について解説します.

send_message(self, text, levelno) でlogging のレベルを渡しています. これにより, levelごとに処理を分けることが出来ます.
今回の場合は, WARNING以上のログはWebHookアプリのアイコンを❗に変更し, 私のアカウントに対してメンションを付けてログをSlackに送信しています. この部分をChannelIDに変えるとチャンネルに参加している全員に通知を飛ばせます.

f:id:JUN_NETWORKS:20191130164909p:plain
WARNING 以上のレベルは アイコンが変わる & ユーザーにメンションされる

メンションを飛ばす部分については SlackのIncoming Webhooksでメンションを飛ばす方法 - Qiita を見てください.

SlackHandler をもう少し高度に使う

先程の仕様例でもいいのですが, あれは少しスマートじゃないですし, 実際に使う際はHandlerやFormatを dictConfigfileConfig で管理すると思います. 自分の場合は dictConfig をいつも使っているので, 今回はそれで説明します.

私は dictConfig を使って. 以下のように設定しています.

import logging
import logging.config


# logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
    },
    'handlers': {
        'default': {
            'level': 'INFO',
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',  # Default is stderr
        },
        # ファイル出力ハンドラー
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': "test.log",
            'formatter': 'standard',
        },
        'slack': {
            'level': 'INFO',
            'class': 'handlers.SlackHandler',
            'url': os.environ.get("SLACK_WEBHOOK_URL"),
            'username': 'Landing Classification Observer',
        }
    },
    'loggers': {
        '': {  # root logger
            'handlers': ['default', 'file', 'slack'],
            'level': 'INFO',
            'propagate': False
        },
    }
}


logging.config.dictConfig(LOGGING)
logger = logging.getLogger(__name__)

全ての設定を LOGGING で管理出来るようになりました.

ここでのポイントは

  • SlackのWebHook URLを環境変数で管理している.
    WebHook URLなどはバージョン管理などに入れたくない情報だと思うので, 環境変数に入れています. こちらのほうがセキュリティ的にもいいと思います.

  • SlackHandler のインスタンス作成時に必要な情報(引数) を全て LOGGING 内で完結している.
    コードの見た目的にも綺麗です.

おまけ

ここからは SlackHandler に必要な訳ではないが, あると便利かも? しれないものを紹介します.

SlackHandler用に新しいレベルを作る

標準出力ハンドラー と ファイルハンドラー ではログを出力したいけど, SlackHandlerには出力しなくない,,,
だからといって SlackHandler のレベルを WARNING に設定したくはない...

ってなった時には 新しいレベルを作りましょう!!

ちなみに python の logging の標準で用意されているレベルは以下のようになっています. Python 公式ドキュメントより以下の様になっています

Level Numeric value
CRITICAL 50
ERROR 40
WARNING 30
INFO 20
DEBUG 10
NOTSET 0

今回は INFO ってほど気軽な感じじゃないけど, WARNINGを使うほどでもない... けど Slackに送信してほしいので INFO と WARNING の間を取って レベル25 の IMPORTANT というレベルを作りましょう.

# add new level called IMPORTANT
IMPORTANT_LEVEL_NUM = 25
logging.addLevelName(IMPORTANT_LEVEL_NUM, "IMPORTANT")


def important(self, message, *args, **kws):
    if self.isEnabledFor(IMPORTANT_LEVEL_NUM):
        # Yes, logger takes its '*args' as 'args'.
        self._log(IMPORTANT_LEVEL_NUM, message, args, **kws)


logging.Logger.important = important

これで IMPORTANT という新しいレベルが logging に登録されました.

使う際には以下の様に Handler などを設定する前に定義してから使います.

import logging
import logging.config


# logging settings
# add new level called IMPORTANT
IMPORTANT_LEVEL_NUM = 25
logging.addLevelName(IMPORTANT_LEVEL_NUM, "IMPORTANT")


def important(self, message, *args, **kws):
    if self.isEnabledFor(IMPORTANT_LEVEL_NUM):
        # Yes, logger takes its '*args' as 'args'.
        self._log(IMPORTANT_LEVEL_NUM, message, args, **kws)


logging.Logger.important = important

# Define logging dict
# logging
LOGGING = {
    'version': 1,
    'disable_existing_loggers': True,
    'formatters': {
        'standard': {
            'format': '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
        },
    },
    'handlers': {
        'default': {
            'level': 'INFO',
            'formatter': 'standard',
            'class': 'logging.StreamHandler',
            'stream': 'ext://sys.stdout',  # Default is stderr
        },
        # ファイル出力ハンドラー
        'file': {
            'level': 'INFO',
            'class': 'logging.FileHandler',
            'filename': "test.log",
            'formatter': 'standard',
        },
        'slack': {
            'level': 'IMPORTANT',
            'class': 'handlers.SlackHandler',
            'url': os.environ.get("SLACK_WEBHOOK_URL"),
            'username': 'Landing Classification Observer',
            'formatter': 'standard',
        }
    },
    'loggers': {
        '': {  # root logger
            'handlers': ['default', 'file', 'slack'],
            'level': 'INFO',
            'propagate': False
        },
    }
}


logging.config.dictConfig(LOGGING)
logger = logging.getLogger(__name__)


def main():
    logger.info("This is Logging Test!!")

    ans = 1 + 2
    logger.important(f"1 + 2 = {ans}")


if __name__ == "__main__":
    main()

これを実行すると, 標準出力とファイル出力ハンドラーに対しては 2つのログが出力されます.

f:id:JUN_NETWORKS:20191130155400p:plain
標準出力

出力された test.log の中身は以下のようになっています.

2019-11-30 15:53:35,869 [INFO] __main__: This is Logging Test!!
2019-11-30 15:53:35,870 [IMPORTANT] __main__: 1 + 2 = 3

そして, 肝心のSlackの方ですが, 以下のように IMPORTANT のログのみが送信されています.

f:id:JUN_NETWORKS:20191130155605p:plain
Slackに送られてきたメッセージ

いい感じですね!!

画像をSlackに自動投稿する

これは色んな所で既出なのであんまりガッツリは書きません. 詳しい解説は他のサイトを見てください.

コードだけ貼ります.

import requests


def upload_image(token, image, filename="", channels="logging", title="", initial_comment=''):
    """ 画像をSlackワークスペース内のチャンネルにアップロードする.
    別に画像以外でもいける

    Parameters
    ----------
    token : str
        Slack API Token.
        取得方法はggって
    image : binary
        画像のバイナリーデータ.
        ファイルから読み込む時は  open("filename", "rb")
        np.ndarray形式で変数imgに保持している時は  cv2.imencode('.jpg', img)[1].tostring()
    filename : str, optional
        slackに投稿される画像のファイル名, by default ""
    channels : str, optional
        投稿先のチャンネル名, by default "logging"
    title : str, optional
        Slack上でのタイトル, by default ""
    initial_comment : str, optional
        コメント(メッセージ), by default ''
    """
    files = {'file': image}
    params = {
        'token': token,
        'channels': channels,
        'filename': filename,
        'title': title,
        'initial_comment': initial_comment
    }
    url = "https://slack.com/api/files.upload"
    requests.post(url=url, params=params, files=files)

上記のコードでわからなかった場合は以下の記事読んでください.

qiita.com

あとがき

今回は Slack にログを送信できる SlackHandler を作成しました.

そもそもこれを作ろうと思ったのは機械学習の学習の学習の経過をSlackに送信したかったからです. そこで方法を調べたのですが, 多くの記事が別に関数を作って実装していました. 個人的には ログ関係は全て Logging にまとめてしまいたかったので今回SlackHandlerを作成しました. 調べた感じSlackHandlerについて書いてある記事は 日本語,英語 共に無かったのでもしかして初かな? 初だと少し嬉しいかもですね.

また, おまけの 新しいレベルを作る 部分は正直必須ではないです. 昔結構苦労して作ったのに現在使っていなくて悲しいので作り方を忘れる前に残しておきたかったので書きました. またどこかで使うかもしれないのでね.

もし, 参考になったり, 使って便利だったらこの記事のリンクを貼ったり, 共有してくれたら嬉しいです. 結構書くの時間かかったので.

てか今思ったんですけど, Slack APP に WebHook と uploadfile の権限持たせれば1つに統合出来るやん.

HHKB をUbuntuで使う

俺「HHKB気になるなぁ〜触ってみたいなぁ〜」

後輩「私のHHKB, 数日貸しましょうか?」

俺「😘😘😘😘😘😘😘😘😘😘😘😘😘」

ってことで前から触ってみたいなぁって思ってたHHKB(US)を後輩から貸してもらいました. やっぱり持つべきものは可愛い後輩ですね.

UbuntuでHHKB(US) を使うのに結構苦労したのでブログに残しておきたいと思います.

環境

  • HHKB Professional 2 US
  • Ubuntu 18.04 LTS

また, 以下のソフトウェアはインストール済みだと想定しています.

  • fcitx
  • mozc

PID スイッチの設定

HHKBにはPIDというキーボードの出力信号をハードウェア的に変えれるのがあるらしいですね,

f:id:JUN_NETWORKS:20191119073737p:plain

画像は 公式サイト より.

PIDスイッチは以下のようにしました.

PW1 PW2 PW3 PW4 PW5 PW6
OFF ON ON ON OFF ON

Macモードにしているのはこうしないと音量調整のショートカットキーが使えなくて不便だからです. UbuntuMacモードを使っていても特に違和感は感じないのでMacモードで大丈夫だと思います.

このPIDスイッチスイッチの構成は以下のブログのものを使いました. 細かい説明などはそちらでわかりやすく解説してらっしゃるので気になる方は見てください

the-tanaka.com

Ubutu キーボード設定

以下のコマンドでHHKB(US)を指定します.

$ dpkg-reconfigure keyboard-configuration

f:id:JUN_NETWORKS:20191119182757p:plain
HHKBを選ぶ

f:id:JUN_NETWORKS:20191119182821p:plain
配列はUS配列

f:id:JUN_NETWORKS:20191119182904p:plain
US配列にもなんか種類があるっぽい? まぁ普通のやつで大丈夫です.

f:id:JUN_NETWORKS:20191119182929p:plainf:id:JUN_NETWORKS:20191119182948p:plainf:id:JUN_NETWORKS:20191119183006p:plain
この辺は特に設定しなくてOK

/etc/default/keyboardの中身が先程設定したものになっているか

$ cat /etc/default/keyboard

こんな感じだったらOK

# KEYBOARD CONFIGURATION FILE

# Consult the keyboard(5) manual page.

XKBMODEL="hhk"
XKBLAYOUT="us"
XKBVARIANT=""
XKBOPTIONS=""

BACKSPACE="guess"

fcitxとmozc設定

/usr/share/ibus/component/mozc.xml の中の <layout>default</layout><layout>en</layout> に変える

$ cat /usr/share/ibus/component/mozc.xml
<component>
  <version>2.20.2673.102+dfsg-2ubuntu0.18.04.1</version>
  <name>com.google.IBus.Mozc</name>
  <license>New BSD</license>
  <exec>/usr/lib/ibus-mozc/ibus-engine-mozc --ibus</exec>
  <textdomain>ibus-mozc</textdomain>
  <author>Google Inc.</author>
  <homepage>https://github.com/google/mozc</homepage>
  <description>Mozc Component</description>
<engines>
<engine>
  <description>Mozc (Japanese Input Method)</description>
  <language>ja</language>
  <symbol>&#x3042;</symbol>
  <rank>80</rank>
  <icon_prop_key>InputMode</icon_prop_key>
  <icon>/usr/share/ibus-mozc/product_icon.png</icon>
  <setup>/usr/lib/mozc/mozc_tool --mode=config_dialog</setup>
  <layout>en</layout>
  <name>mozc-jp</name>
  <longname>Mozc</longname>
</engine>
</engines>
</component>

mozcは直接入力と日本語入力の2種類の状態を持っているので, fcitxの設定で入力メソッドをmozcのみにします.

f:id:JUN_NETWORKS:20191119185330p:plain

その下側にある歯車とキーボードアイコンでキーボードのレイアウトの設定が出来るのでUS配列に変更します

f:id:JUN_NETWORKS:20191119185454p:plain
キーボードレイアウトをUS配列にする

入力メソッドがmozc1つだけなので入力メソッド切り替えのショートカットキーは必要無いのでショートカットキーを無効にします

f:id:JUN_NETWORKS:20191119190442p:plain


そして, US配列では全角半角キーを使った入力切り替えが出来ないので別のキーを割り当てます.

別のキーの割り当て方は, mozcの設定の中のキーマップのところから変更します.

ベースにするキーマップは MS-IME を使います.

f:id:JUN_NETWORKS:20191119190948p:plain

keymap style の中で 半角全角キーを使っている動作を変更します.

f:id:JUN_NETWORKS:20191119191134p:plain

半角全角キーを使っている動作はIMEの 有効/無効 だけなのでこれに別のキーを割り当てます. Key部分を3回クリックすることでキーの変更画面が出てくるのでそこで変更出来ます.

自分は以下のように設定しました.

  • Ctrl + Space : IMEを有効にする
  • Chift + Space : IMEを無効にす

以上でUS配列で 日本語入力/英語入力 の切り替え, 入力が出来るようになったはずです

matplotlibで高画質のfigure画像をnumpy arrayで手に入れる

この記事の目的, 背景

matplotlibではプロットや画像の表示が出来ます. そしてそれを画像ファイルとして出力することも出来ます.
しかし, tensorboardへの記録やその後の画像の加工などがしたい場合には 画像ファイルでは無く numpy array 形式で欲しい時があります.

しかし, 調べて出てきたやり方では低画質になってしまうという欠点がありました.

なので, 高画質な状態で numpy array 形式に出力するやり方を考えたのでそれについて書きます.

ベースとなるプロット

今回は定番のsin波をベースにしたいと思います.

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)

x = np.linspace(-np.pi, np.pi)

ax.set_xlim(-np.pi, np.pi)
ax.set_xlabel("x")
ax.set_ylabel("y")

ax.plot(x, np.sin(x), label="sin")

ax.legend()
ax.set_title("sin(x)")

プロットは以下のようになります.

f:id:JUN_NETWORKS:20191101012127p:plain
sin figure

従来の方法

savefig()

matplotlib の関数として savefig() というものがあり, それを使うと 画像ファイルとして出力できます.

fig.savefig("sin.png")

f:id:JUN_NETWORKS:20191101012127p:plain
出力された sin.png

高画質で素晴らしいのですが, 我々が今欲しいのは numpy array 形式なのでこれは却下です.

もちろん, 出力したファイルを読み込めば numpy array 形式で扱えますけどね. なんか違うんだよなぁ.

プロットを文字列にして, それを numpy で読み込む

私が検索した限り, この方法が多かったです.

fig.canvas.draw()

data = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
data = data.reshape(fig.canvas.get_width_height()[::-1] + (3,))

stackoverflow.com

このコードは上手く動作します!! figure を numpy array で入手できてるしいいんじゃない?

だがしかし, 私はやり方では満足出来ませんでした.

以下が 上記の data変数を画像として出力したものです.

f:id:JUN_NETWORKS:20191101013914p:plain
data変数の numpy array を画像として出力したもの

画質悪くない??

今回のプロットが簡単なのであまり気にならないのですが, これが大量のデータのプロットなどになるとプロットが潰れて見るに耐えないものになります.

今回の手法

従来の2つの手法はまとめると以下のようになります

  • savefig() : 画質は良いが, 画像ファイルが出力されるため numpy array にするのに 出力画像を読み込まないといけないのでめんどくさい.
  • np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8) : figure をファイルI/O無しで numpy array として入手出来るのはいいが, 画質がいまいち.

そして, 今回の私の手法がこれです. 上記2つの手法を組み合わせたとも言えなくないかも?

import io
import cv2

buf = io.BytesIO()  # インメモリのバイナリストリームを作成
fig.savefig(buf, format="png", dpi=180)  # matplotlibから出力される画像のバイナリデータをメモリに格納する.
buf.seek(0)  # ストリーム位置を先頭に戻る
img_arr = np.frombuffer(buf.getvalue(), dtype=np.uint8)  # メモリからバイナリデータを読み込み, numpy array 形式に変換
buf.close()  # ストリームを閉じる(flushする)
img = cv2.imdecode(img_arr, 1)  # 画像のバイナリデータを復元する
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # cv2.imread() はBGR形式で読み込むのでRGBにする.

そして img変数に入っている numpy array を画像として出力したものがこちらです

f:id:JUN_NETWORKS:20191101014951p:plain
img変数に入っているnumpy arrayを画像として出力したもの

いい感じじゃないですか?
また, fig.savefig(buf, format="png", dpi=180)dpi の値を変えることで画質を変えれます.

参考にしたリンク

stackoverflow.com

stackoverflow.com

【書評】x86本を読んだ

x86本, 正確には 「自作エミュレータで学ぶx86アーキテクチャ コンピュータが動く仕組みを徹底理解!」を読んだのでそれの感想を書きます.

また, この本の想定環境はWindowsですが, 自分はLinux環境なので, Linux環境で実行する際に気をつけるべきポイントも書きました.

ちなみに, 書評を書くのはこれが初めてなので文章が多分下手くそです. 保険を掛けていく

各章の感想

Chapter 1 C言語アセンブリ言語

この本の最初となる第1章では C言語機械語, アセンブリ言語 の関連性や特徴についての説明されています.

また, アセンブリ言語の基本命令, 2進数, 16進数, アセンブル, ディスアセンブル(逆アセンブル)のやり方などのこの後の章を読む上でベースとなる要素を解説してくれます.

基本的にこの本はわからないことがあっても読み進めていくうちにわかるようになることがあるので, わからないところがあっても基本的にはそのまま読み進めれば良いのですが, 2進数と16進数だけは出来ると出来ないではこの後の章での理解度が変わってきますので必ず理解するべきだと思います.

Chapter 2 ポインタとアセンブリ言語

この章から本格的に CPUとメモリ, ポインタ について解説が入り, そしてエミュレータ作成が始まります.

前半はレジスタやメモリの解説, 後半ではポインタの復習とプログラムを実行する上でのポインタの役割などについて説明してくれます.

CPUとメモリ, ポインタの関係性がよく理解出来ました. また, 初めてエミュレータを使いバイナリファイルを動かした時には少し感動しました.

Chapter 3 CPUがプログラムを実行する仕組み

この章では章のタイトルにもあるとおり, 今まで学んできた ポインタやアセンブリ, CPU, メモリ, etc... などの各要素が1つに集まり実際にプログラムを構築し, 実行するのが理解出来る最高に興奮する章です.

3章内にある "3.3 プログラムの実行" というセクションでは x86アーキテクチャの代表的なCPU i386を対象に各レジスタの説明と, 演算処理, メモリと補助記憶装置のCPU側から見た違い, 機械語命令の構成など盛り沢山で大変読み応えがあり, 私が一番好きなセクションでもあります.

あと, "3.6 call命令とスタック", "3.9 フラグレジスタと条件分岐命令" もとても興奮しました. 各セクションごとに感想を書くと切りがないので書きませんが, きっと読めば解ると思います.

この3章は1,2章で学んできた知識がつながる一番楽しい章だと個人的には思います. この章を読み終わる頃にはC言語コンパイルしバイナリファイルを生成し, そのバイナリファイルがどのようにCPUやメモリなどを使い動いているかがイメージ出来るようになっていると思います.

Chapter 4 BIOSの仕組みと実装

この章では BIOS の文字表示機能を実際にエミュレータに組み込み, アセンブリでソフトウエア割り込みを使ってBIOSの機能を呼び出し任意の文字列を出力するコードを作成し, 実際にそれを動かします. また, HDDやUSBなどの記憶媒体の記憶方式について解説し, 実際にUSBメモリのPBRを実際に見て, その後アセンブリ言語プログラムをPBRとして認識されるように作り 実機で動かします.

この章でパソコンの電源を押した瞬間からOS起動までの流れをつかむことが出来ました.

また, 割り込み処理の部分は 「なるほど,うまく出来てるなぁ〜」と感心しました.

全体を通しての感想

自分はアセンブリもCPUの各レジスタもなんもわからないレベルから プログラムが実行される際のCPUとメモリの動きや, ポインタの重要性を理解し, アセンブリも読めて, 少し書けるくらいになりました..
初めて低レイヤーに触ったのですが, 特に辛い思いをせず, 楽しく学べました. 私はこの本で初めて低レイヤーについて学びましたが, 読み終わった今では読む前よりも 低レイヤーに学びたい という気持ちが強まりました. そういう意味でも, 低レイヤーに興味がある人が初めて読むのにとても良いと思いました.
読み進めていくうちに各要素が繋がっていき, 段々理解出来るはやはり楽しかったです.

本自体も200ページ弱で, 大変読みやすいと思います. また, 図やソースコードが豊富であり, 各要素についての説明も丁寧に細かく書いてあるのでC言語の入門が終わってる人なら誰でも楽しめると思います.

LInux環境で実行する際の注意点

ここからは本の感想から少し離れて Linux環境で実行する際に注意した方がいいところをいくつか挙げます.

インストールするソフトウェア

NASM

$ sudo apt install nasm

この記事がいい感じにまとまってます. : Linuxでx86アセンブラ(道具編)

gcc

以下の記事見てください. linuxize.com

変更が必要な書籍の中で実行しているコマンド

いくつかのプログラムにて, make が上手くが上手く行きません. 原因は Windows 用に Makefile が作られており, その中でコマンドツールの指定の仕方が Windows 仕様だからです.

書籍内の多くの Makefile はさほど複雑なことはしていないので, make に失敗したら自分で Makefie を少しイジるか, 自分で gcc などを使ってコンパイルすればいいと思います.

3.7

p115のアセンブラとcのオブジェクトファイルを作成しリンクして実行ファイルを作るところ

$ nasm -f elf32 crt0.asm
$ gcc -c -nostdlib -fno-asynchronous-unwind-tables -m32 -fno-pie -g -o test.o test.c
$ ld --entry=start --oformat=binary -m elf_i386 -Ttext 0x7c00 -o test.bin crt0.o test.o

次の記事を参考にしました. ldコマンドの説明

4.5

このセクションでは dd コマンドと boots という著者が作成したツールを使い USBメモリのPBRを見たり書き込んだりします.

dd コマンドについては Linux に標準で搭載されているので良いのですが, boots の方が自分の環境では依存しているライブラリのインストールが出来ずに失敗しました.

誰か出来た人おしえてください.

完走した感想 (おまけ)

完走した感想ですが, 最後の章のUSBのPBRを見る部分が出来なかったのが少し心残りです. 誰かLinux環境で動かせた人はコメントか何かで教えてください.

ここ最近は流行りの技術を追いかけるのではなく, コンピュータサイエンスをしっかりやろうと思っており, その第一歩として低レイヤーに触り始めました.
低レイヤーに関してはこの本が本当に初めてで, ちゃんと読めるか少し不安でしたが, しっかり読めました. いきなり激ムズな本だと心が折れますからね.

今日においては 別に低レイヤーをやらなくても高級言語やライブラリを触るだけでプログラムは書けてしまうのですが, やはり低レイヤーをブラックボックスとして意識せずにコードを書くのと, ある程度理解してコードを書くのとではやっぱり違ってくると思います.

今回で低レイヤーのCPUでの演算や機械語の実行方法などがわかったわけですが, 実はまだ少しモヤっとしていて, それは CPUがなぜそのような動きをするのかがわからない ということです.
勿論今回の書籍で扱っているx86を始めとしたCPUアーキテクチャで決められているからというのはわかっていますが, そうではなく, CPUを回路レベルで知りたいのです.

ということで次はこの本をやります(予定)

CPUの創りかた

CPUの創りかた

  • 作者:渡波 郁
  • 発売日: 2003/10/01
  • メディア: 単行本(ソフトカバー)

この本も読み終わったら書評を書きたいと思っていますが, この本に関しては本で知識を得るだけではなく 実際にCPUを自ら作成するところまでやりたいと思っています. だいぶ時間が掛かりそうですね.



書評を完走した感想ですが, いかんせん日本語が下手くそですね. 小学生の頃に書いた作文の方が日本語上手い気がします. Twitterでオタク構文ばっかり使ってるからですかね.
あと, 本と書籍の2つのワードが混在しているのでどちらかに揃えたい気はします.

それでは次の記事でまたお会いしましょう. さよなら〜.

Pipenvメモ (随時更新)

Pipenv使ってたら時々躓いて,そんで解決して, メモしてって感じなのですが, 今回の記事はそのメモをコピペしただけです. まぁ, 要は備忘録です.

では本編どうぞ.

Packages

  • Pipfile からパッケージを一括インストール

    pipenv install

    開発用パッケージも一緒にインストール

    pipenv install --dev

  • Pipfile を参照してシステムにパッケージをインストールする

    pipenv install --system

  • 新しいパッケージをpipenvにインストール

    pipenv install <pkg_name>

    開発時にしか使わないパッケージをpipenvにインストール

    pipenv install --dev <pkg_name>

  • パッケージのアンインストール

    pipenv uninstall <pkg_name>

  • インストールされているパッケージの一覧を確認

    pipenv graph

  • インストールされているパッケージの一括バージョンアップ

    pipenv update

    パッケージ毎に個別でアップデート

    pipenv update <pkg_name>

  • Pipfile.lock を用いることで安全で確実なインストールが可能だが、依存関係の計算に時間がかかるため、待てない場合は --skip-lock オプションを使うことで Pipfile.lock の計算をスキップできる. reference

    pipenv install --skip-lock

  • Pipfile.lock を作成

    pipenv lock

  • VCS(gitとか) からインストール

    ex (albumentations):

    pipenv install --editable git+https://github.com/albu/albumentations.git#egg=albumentations

Run

  • pipenv で作成した環境に入る

    pipenv shell

  • pipenv shellに入っていない状態でpipenvの環境上でpythonスクリプトを動かす

    pipenv run python script.py

Other

  • pipenvの仮想環境のパスを確認

    pipenv --venv

  • pipenv 仮想環境の削除の仕方

    pipenv --rm

  • PycharmでのPipenvの設定方法

  • Anacondaを使っている環境上でpipenvを使う方法

    Install pipenv

    sudo apt install python3-pip

    pip3 install pipenv

    create pipenv environment on anaconda

    conda create -n pipenv_test python=3.6

    source activate pipenv_test

    pipenv install --python=$(which python)

    pipenv shell

    参考リンク: Pipenv with Conda?

Error

References