日々のあれこれφ(..)

もっぱら壁打ち

fishの色設定をカスタマイズして持ち運べるようにする

はじめに

fishを使っており、promptを自分でカスタマイズしています(といってもoh-my-fishのデフォルトを少し改造しただけですが)。カラーテーマに関してはfish_configから選択できる適当なものを使っていたのですが、PCの引越しの際に色設定がうまく引き継がれず、どんな設定を残しておけばよかったのか調べていたら最終的に1から自分で色設定していたという話です。

fishの色の設定に関して

  • 変数 fish_color_*に色を定義する

例えば fish_color_normal はデフォルトの色、 fish_color_quote は ""で囲まれた部分の色、といった具合です。

変数の全種類は以下です。

Interactive use — fish-shell 3.3.1 documentation

  • fish_configでUIから設定したものは、fish_variablesファイルにユニバーサル変数として残る

fish_variablesは基本システム側が管理しているものなので、dotfileなどに登録して別の環境で適用させることはしません。このファイルに色が定義されていると新しい環境に引っ越してきたときにまたfish_configコマンドを叩いてUIから色の設定をしないといけなくなります。その作業だけなら別に手間ではありませんが、環境設定をできるだけ全てスクリプトを実行させるだけで完結させたいので、各色の指定もfish.configで行うようにしたいと思います。

fish_variableの中では以下のように定義されています。

SETUVAR fish_color_match:\x2d\x2dbackground\x3d61AFEF
SETUVAR fish_color_normal:ABB2B
SETUVAR fish_color_operator:C678DD
  • 色の指定に関して

予約語(red, cyanなど)を使うことができます。それより細かく色を指定するには16進数を使えます。また文字色以外にも --background で背景の色を指定したり、 --bold で太字にするといったoptionが存在します。

set_color - set the terminal color — fish-shell 3.3.1

設定をカスタマイズするにあたって、fish_variableの中身を見ただけだと何色になるのかよくわからないものもあったので、set_colorのサンプルに倣って以下のように一個ずつ確認していました😳

# 設定を確認
set_color $fish_color_normal; echo 'foo' # 設定された色が付いたfooが出力される

# 設定したい色を当てて表示を確認
set_color 61AFEF; echo 'foo' # 16進数の青
set_color 61AFEF -b FFFFFF; echo 'foo' # 上記に加えて背景が白くなる

fish.configに追加したもの

最終的に以下のようになりました。

# set color
set -g black brblack
set -g blue 61AFEF
set -g red E06C75
set -g green 98C379
set -g yellow E5C07B
set -g magenta C678DD
set -g cyan 56B6C
set -g white normal
set -g gray 9C9C9C

set -U fish_color_normal $white
set -U fish_color_autosuggestion $gray
set -U fish_color_cancel --background=$gray
set -U fish_color_command $fish_color_normal
set -U fish_color_comment $gray
set -U fish_color_cwd $green
set -U fish_color_cwd_root $red
set -U fish_color_end $gray
set -U fish_color_error $red
set -U fish_color_escape $gray
set -U fish_color_history_current --bold
set -U fish_color_host $fish_color_normal
set -U fish_color_match --background=$blue
set -U fish_color_operator $magenta
set -U fish_color_param $magenta
set -U fish_color_quote $green
set -U fish_color_redirection $gray
set -U fish_color_user $blue
set -U fish_color_search_match --background=$cyan
set -U fish_color_selection --background=$fish_color_normal
set -U fish_color_valid_path --background=$fish_color_normal
set -U fish_pager_color_progress --background=$cyan
set -U fish_pager_color_completion $fish_color_normal
set -U fish_pager_color_prefix $fish_color_normal

1ブロック目に関しては、もともとあった色の予約語を上書きしたものになっています。

カラーパレットは「Atom's One Dark」のを参考に、赤はE06C75の色、青は61AFEFの色、といった具合に設定しています。

Atom's One Darkのカラーコードはjoshdick/onedark.vimリポジトリを参考にしました。

2ブロック目に関しては、各変数を色の予約語で再設定したものになります。

f:id:reiichii:20220105194703p:plain

これで次引っ越した際にはスムーズになるはず🐟

alpコマンドでNginxのログ解析をする

「2021年はブログから離れない!」とか書いておきながら、1ヶ月ぶりの更新です🙂

理由は仕事が忙しかったからとか、プライベートが忙しかったからというのもあるのですが、その中でも時間はあったにも関わらず結局書けなかったのは、やっぱり書くことのハードルが知らず知らずの内に上がっていたからでした。なので初心にかえります。

alpコマンドでNginxのアクセスログを集計するやり方を知ったので、その覚書です。

alpコマンド

github.com

Access Log Profiler の意味らしいです。

NginxなどのWebサーバのアクセスログ

+-------+-----+------+-----+-------+-----+------------------+------------------------------------------------------------------------------+-------+-------+----------+-------+-------+-------+-------+--------+-----------+------------+--------------+------------+
| COUNT | 1XX | 2XX  | 3XX |  4XX  | 5XX |      METHOD      |                                     URI                                      |  MIN  |  MAX  |   SUM    |  AVG  |  P90  |  P95  |  P99  | STDDEV | MIN(BODY) | MAX(BODY)  |  SUM(BODY)   | AVG(BODY)  |
+-------+-----+------+-----+-------+-----+------------------+------------------------------------------------------------------------------+-------+-------+----------+-------+-------+-------+-------+--------+-----------+------------+--------------+------------+
|     1 |   0 |    1 |   0 |     0 |   0 | GET              | /isu/f46ad522-d196-487e-b666-c9b000c3d458/graph                              | 0.000 | 0.000 |    0.000 | 0.000 | 0.000 | 0.000 | 0.000 |  0.000 |   528.000 |    528.000 |      528.000 |    528.000 |
|     1 |   0 |    0 |   0 |     1 |   0 | GET              | /solr/                                                                       | 0.000 | 0.000 |    0.000 | 0.000 | 0.000 | 0.000 | 0.000 |  0.000 |   120.000 |    120.000 |      120.000 |    120.000 |
|     1 |   0 |    1 |   0 |     0 |   0 | GET              | /isu/c9add5fc-28ca-4eea-9b77-6cb90852508a/graph                              | 0.000 | 0.000 |    0.000 | 0.000 | 0.000 | 0.000 | 0.000 |  0.000 |   528.000 |    528.000 |      528.000 |    528.000 |
|     1 |   0 |    1 |   0 |     0 |   0 | GET              | /isu/023c8e56-4410-484d-b7fa-702d48188d3c                                    | 0.000 | 0.000 |    0.000 | 0.000 | 0.000 | 0.000 | 0.000 |  0.000 |   528.000 |    528.000 |      528.000 |    528.000 |
|     1 |   0 |    0 |   0 |     1 |   0 | -                | -                                                                            | 0.198 | 0.198 |    0.198 | 0.198 | 0.198 | 0.198 | 0.198 |  0.000 |   166.000 |    166.000 |      166.000 |    166.000 |
|     1 |   0 |    0 |   0 |     1 |   0 | GET              | /manager/text/list                                                           | 0.000 | 0.000 |    0.000 | 0.000 | 0.000 | 0.000 | 0.000 |  0.000 |   264.000 |    264.000 |      264.000 |    264.000 |
|     1 |   0 |    0 |   0 |     1 |   0 | POST             | /mifs/.;/services/LogService                                                 | 0.000 | 0.000 |    0.000 | 0.000 | 0.000 | 0.000 | 0.000 |  0.000 |   120.000 |    120.000 |      120.000 |    120.000 |
|     1 |   0 |    0 |   0 |     1 |   0 | GET              | /manager/html                                                                | 0.000 | 0.000 |    0.000 | 0.000 | 0.000 | 0.000 | 0.000 |  0.000 |   264.000 |    264.000 |      264.000 |    264.000 |

のような形に集計してくれます。

準備

準備として、nginxのログフォーマットをjson形式に対応させる必要があります。json以外にも、ltsv、pcapにも対応しています。

# nginx.conf

    log_format json escape=json '{"time":"$time_iso8601",'
                                '"host":"$remote_addr",'
                                '"forwardedfor":"$http_x_forwarded_for",'
                                '"req":"$request",'
                                '"status":"$status",'
                                '"method":"$request_method",'
                                '"uri":"$request_uri",'
                                '"body_bytes":$body_bytes_sent,'
                                '"referer":"$http_referer",'
                                '"ua":"$http_user_agent",'
                                '"request_time":"$request_time",'
                                '"cache":"$upstream_http_x_cache",'
                                '"runtime":"$upstream_http_x_runtime",'
                                '"rseponse_time":"$upstream_response_time",'
                                '"vhost":"$host"}';

    access_log  /var/log/nginx/access.log  json;

参考:alp/README.ja.md at main · tkuchiki/alp

これでnginxをrestartします。

$ tail -f /var/log/nginx/access.log
{"time":"02/Nov/2021:20:39:07 +0900","host":"147.192.34.114","forwardedfor":"","req":"GET /?jwt=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzU4NTQ5NDcsImlhdCI6MTYzNTg1MzE0NywiamlhX3VzZXJfaWQiOiJpc3Vjb24xIn0.CjOfNYVRt65cNgpMn3uxKxZiZ5PnRwX50NjHyf34SEIxsCtvmZ-aLL-GpZOJO8GAX1VvSAZeTZe7wdcNTT282w HTTP/2.0","status":"200","method":"GET","uri":"/?jwt=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzU4NTQ5NDcsImlhdCI6MTYzNTg1MzE0NywiamlhX3VzZXJfaWQiOiJpc3Vjb24xIn0.CjOfNYVRt65cNgpMn3uxKxZiZ5PnRwX50NjHyf34SEIxsCtvmZ-aLL-GpZOJO8GAX1VvSAZeTZe7wdcNTT282w","body_bytes":528,"referer":"http://localhost:5000/","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36","request_time":"0.003","cache":"","runtime":"","rseponse_time":"0.004","vhost":"54.238.210.34"}
{"time":"02/Nov/2021:20:39:08 +0900","host":"147.192.34.114","forwardedfor":"","req":"GET /assets/index.23dac98b.js HTTP/2.0","status":"200","method":"GET","uri":"/assets/index.23dac98b.js","body_bytes":26667,"referer":"https://54.238.210.34/?jwt=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzU4NTQ5NDcsImlhdCI6MTYzNTg1MzE0NywiamlhX3VzZXJfaWQiOiJpc3Vjb24xIn0.CjOfNYVRt65cNgpMn3uxKxZiZ5PnRwX50NjHyf34SEIxsCtvmZ-aLL-GpZOJO8GAX1VvSAZeTZe7wdcNTT282w","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36","request_time":"0.005","cache":"","runtime":"","rseponse_time":"0.004","vhost":"54.238.210.34"}
{"time":"02/Nov/2021:20:39:09 +0900","host":"147.192.34.114","forwardedfor":"","req":"GET /assets/index.144d8ca8.css HTTP/2.0","status":"200","method":"GET","uri":"/assets/index.144d8ca8.css","body_bytes":19066,"referer":"https://54.238.210.34/?jwt=eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2MzU4NTQ5NDcsImlhdCI6MTYzNTg1MzE0NywiamlhX3VzZXJfaWQiOiJpc3Vjb24xIn0.CjOfNYVRt65cNgpMn3uxKxZiZ5PnRwX50NjHyf34SEIxsCtvmZ-aLL-GpZOJO8GAX1VvSAZeTZe7wdcNTT282w","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36","request_time":"1.579","cache":"","runtime":"","rseponse_time":"0.008","vhost":"54.238.210.34"}

使い方

基本形

最小限のコマンドが以下です。

| alp json

$ cat /var/log/nginx/access.log | alp json
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                        URI                         |  MIN  |  MAX  |  SUM  |  AVG  |  P90  |  P95  |  P99  | STDDEV | MIN(BODY)  | MAX(BODY)  | SUM(BODY)  | AVG(BODY)  |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /                                                  | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |    528.000 |    528.000 |    528.000 |    528.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/index.23dac98b.js                          | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 |  0.000 |  26667.000 |  26667.000 |  26667.000 |  26667.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/index.144d8ca8.css                         | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 |  0.000 |  19066.000 |  19066.000 |  19066.000 |  19066.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/vendor.ee7444dd.js                         | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 |  0.000 | 743417.000 | 743417.000 | 743417.000 | 743417.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/logo_white.svg                             | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |   3285.000 |   3285.000 |   3285.000 |   3285.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/b82dcff4-8fae-4773-9956-22ab354adb7e/icon | 0.019 | 0.019 | 0.019 | 0.019 | 0.019 | 0.019 | 0.019 |  0.000 |   7994.000 |   7994.000 |   7994.000 |   7994.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | POST   | /api/auth                                          | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 |  0.000 |      0.000 |      0.000 |      0.000 |      0.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu                                           | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 |  0.000 |   3312.000 |   3312.000 |   3312.000 |   3312.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/840e993f-3909-4970-8d9d-37b0a9ba0426/icon | 0.006 | 0.006 | 0.006 | 0.006 | 0.006 | 0.006 | 0.006 |  0.000 |   7994.000 |   7994.000 |   7994.000 |   7994.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/4f2e409a-e5fb-48aa-92d6-56de38427e02/icon | 0.009 | 0.009 | 0.009 | 0.009 | 0.009 | 0.009 | 0.009 |  0.000 |  11209.000 |  11209.000 |  11209.000 |  11209.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/ad39933b-791a-4f8f-90d7-0862212d71ad/icon | 0.012 | 0.012 | 0.012 | 0.012 | 0.012 | 0.012 | 0.012 |  0.000 |   6969.000 |   6969.000 |   6969.000 |   6969.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/8fb74e6e-4f8d-4a33-9862-dbcdc0712c7e/icon | 0.013 | 0.013 | 0.013 | 0.013 | 0.013 | 0.013 | 0.013 |  0.000 |  14566.000 |  14566.000 |  14566.000 |  14566.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/15063e10-26fc-4dac-ae3a-1f195e0c6c4a/icon | 0.014 | 0.014 | 0.014 | 0.014 | 0.014 | 0.014 | 0.014 |  0.000 |   9453.000 |   9453.000 |   9453.000 |   9453.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/8469e6f6-0d29-4052-87cf-dd9bfde45014/icon | 0.015 | 0.015 | 0.015 | 0.015 | 0.015 | 0.015 | 0.015 |  0.000 |  20872.000 |  20872.000 |  20872.000 |  20872.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu/eb104b95-3532-4828-8550-89da717b9667/icon | 0.021 | 0.021 | 0.021 | 0.021 | 0.021 | 0.021 | 0.021 |  0.000 |  11209.000 |  11209.000 |  11209.000 |  11209.000 |
|     2 |   0 |   1 |   0 |   1 |   0 | GET    | /api/user/me                                       | 0.002 | 0.003 | 0.005 | 0.003 | 0.003 | 0.003 | 0.003 |  0.001 |     21.000 |     26.000 |     47.000 |     23.500 |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+

同種のリクエストをまとめる

/api/isu/{uuid}/icon のような同種のリクエストをまとめることができます。以下のように正規表現でパターンを指定します。

--matching-groups= '{PATTERN}'

PATTERNはカンマ区切りで複数指定することもできます。

$ cat /var/log/nginx/access.log | alp json -m '/api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon'
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                                    URI                                     |  MIN  |  MAX  |  SUM  |  AVG  |  P90  |  P95  |  P99  | STDDEV | MIN(BODY)  | MAX(BODY)  | SUM(BODY)  | AVG(BODY)  |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /                                                                          | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |    528.000 |    528.000 |    528.000 |    528.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/index.23dac98b.js                                                  | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 |  0.000 |  26667.000 |  26667.000 |  26667.000 |  26667.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/index.144d8ca8.css                                                 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 |  0.000 |  19066.000 |  19066.000 |  19066.000 |  19066.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/vendor.ee7444dd.js                                                 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 |  0.000 | 743417.000 | 743417.000 | 743417.000 | 743417.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/logo_white.svg                                                     | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |   3285.000 |   3285.000 |   3285.000 |   3285.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | POST   | /api/auth                                                                  | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 |  0.000 |      0.000 |      0.000 |      0.000 |      0.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu                                                                   | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 |  0.000 |   3312.000 |   3312.000 |   3312.000 |   3312.000 |
|     2 |   0 |   1 |   0 |   1 |   0 | GET    | /api/user/me                                                               | 0.002 | 0.003 | 0.005 | 0.003 | 0.003 | 0.003 | 0.003 |  0.001 |     21.000 |     26.000 |     47.000 |     23.500 |
|     8 |   0 |   8 |   0 |   0 |   0 | GET    | /api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon | 0.006 | 0.021 | 0.109 | 0.014 | 0.021 | 0.021 | 0.021 |  0.005 |   6969.000 |  20872.000 |  90266.000 |  11283.250 |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+

ソート

カラムを指定して、ソートすることができます。

--sort {カラム名} --reverse

例えばリクエスト数が多いもの順に見たいときは、以下のようにできます。

$ cat /var/log/nginx/access.log | alp json -m '/api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon' --sort count -r
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                                    URI                                     |  MIN  |  MAX  |  SUM  |  AVG  |  P90  |  P95  |  P99  | STDDEV | MIN(BODY)  | MAX(BODY)  | SUM(BODY)  | AVG(BODY)  |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+
|     8 |   0 |   8 |   0 |   0 |   0 | GET    | /api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon | 0.006 | 0.021 | 0.109 | 0.014 | 0.021 | 0.021 | 0.021 |  0.005 |   6969.000 |  20872.000 |  90266.000 |  11283.250 |
|     2 |   0 |   1 |   0 |   1 |   0 | GET    | /api/user/me                                                               | 0.002 | 0.003 | 0.005 | 0.003 | 0.003 | 0.003 | 0.003 |  0.001 |     21.000 |     26.000 |     47.000 |     23.500 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /                                                                          | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |    528.000 |    528.000 |    528.000 |    528.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/index.23dac98b.js                                                  | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 | 0.005 |  0.000 |  26667.000 |  26667.000 |  26667.000 |  26667.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/vendor.ee7444dd.js                                                 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 | 7.236 |  0.000 | 743417.000 | 743417.000 | 743417.000 | 743417.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/logo_white.svg                                                     | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |   3285.000 |   3285.000 |   3285.000 |   3285.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | POST   | /api/auth                                                                  | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 | 0.169 |  0.000 |      0.000 |      0.000 |      0.000 |      0.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu                                                                   | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 | 0.068 |  0.000 |   3312.000 |   3312.000 |   3312.000 |   3312.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/index.144d8ca8.css                                                 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 | 1.579 |  0.000 |  19066.000 |  19066.000 |  19066.000 |  19066.000 |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+------------+------------+------------+------------+

特定の時間以降(以前)のログを出力

--filters "Time >= '{日時}'"のような形で指定できます。

日時はログと同じフォーマットで指定します。

$ cat /var/log/nginx/access.log | alp json -m '/api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon' --filters "Time >= '2021-11-02T21:09:10+09:00'"
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                                    URI                                     |  MIN  |  MAX  |  SUM  |  AVG  |  P90  |  P95  |  P99  | STDDEV | MIN(BODY) | MAX(BODY) | SUM(BODY) | AVG(BODY) |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu                                                                   | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 |  0.000 |  3312.000 |  3312.000 |  3312.000 |  3312.000 |
|     8 |   0 |   8 |   0 |   0 |   0 | GET    | /api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon | 0.006 | 0.019 | 0.099 | 0.012 | 0.019 | 0.019 | 0.019 |  0.005 |  6969.000 | 20872.000 | 90266.000 | 11283.250 |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+

フォーマット

--format csvcsv 形式に出力します。

他にも tsvmd (or markdown)でも出力できるみたいでした👀

結果をgitやslackで共有したいときに使えそう。

$ tail -n 10 /var/log/nginx/access.log | alp json -m '/api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon' --format csv
Count,1xx,2xx,3xx,4xx,5xx,Method,Uri,Min,Max,Sum,Avg,P90,P95,P99,Stddev,Min(Body),Max(Body),Sum(Body),Avg(Body)
1,0,1,0,0,0,GET,/assets/favicon.d0f5f504.svg,0.003,0.003,0.003,0.003,0.003,0.003,0.003,0.000,592.000,592.000,592.000,592.000
1,0,1,0,0,0,GET,/api/isu,0.071,0.071,0.071,0.071,0.071,0.071,0.071,0.000,3312.000,3312.000,3312.000,3312.000
8,0,8,0,0,0,GET,/api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon,0.006,0.019,0.099,0.012,0.019,0.019,0.019,0.005,6969.000,20872.000,90266.000,11283.250

dumpとload

--dump /tmp/alp.dump --load /tmp/alp.dump

dumpしたファイルは解析を行わずに解析結果を出力できるそうです。

$ tail -n 10 /var/log/nginx/access.log | alp json -m '/api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon' --dump /tmp/alp.dump
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                                    URI                                     |  MIN  |  MAX  |  SUM  |  AVG  |  P90  |  P95  |  P99  | STDDEV | MIN(BODY) | MAX(BODY) | SUM(BODY) | AVG(BODY) |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/favicon.d0f5f504.svg                                               | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |   592.000 |   592.000 |   592.000 |   592.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu                                                                   | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 |  0.000 |  3312.000 |  3312.000 |  3312.000 |  3312.000 |
|     8 |   0 |   8 |   0 |   0 |   0 | GET    | /api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon | 0.006 | 0.019 | 0.099 | 0.012 | 0.019 | 0.019 | 0.019 |  0.005 |  6969.000 | 20872.000 | 90266.000 | 11283.250 |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+

$ alp json --load /tmp/alp.dump
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+
| COUNT | 1XX | 2XX | 3XX | 4XX | 5XX | METHOD |                                    URI                                     |  MIN  |  MAX  |  SUM  |  AVG  |  P90  |  P95  |  P99  | STDDEV | MIN(BODY) | MAX(BODY) | SUM(BODY) | AVG(BODY) |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /assets/favicon.d0f5f504.svg                                               | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 | 0.003 |  0.000 |   592.000 |   592.000 |   592.000 |   592.000 |
|     1 |   0 |   1 |   0 |   0 |   0 | GET    | /api/isu                                                                   | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 | 0.071 |  0.000 |  3312.000 |  3312.000 |  3312.000 |  3312.000 |
|     8 |   0 |   8 |   0 |   0 |   0 | GET    | /api/isu/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/icon | 0.006 | 0.019 | 0.099 | 0.012 | 0.019 | 0.019 | 0.019 |  0.005 |  6969.000 | 20872.000 | 90266.000 | 11283.250 |
+-------+-----+-----+-----+-----+-----+--------+----------------------------------------------------------------------------+-------+-------+-------+-------+-------+-------+-------+--------+-----------+-----------+-----------+-----------+

ちなみに /tmp/alp.dump の中身は以下のようになっていました。

- uri: /assets/favicon.d0f5f504.svg
  count: 1
  status1xx: 0
  status2xx: 1
  status3xx: 0
  status4xx: 0
  status5xx: 0
  method: GET
  response_time:
    max: 0.003
    min: 0.003
    sum: 0.003
    usepercentile: true
    percentiles:
    - 0.003
  request_body_bytes:
    max: 592
    min: 592
    sum: 592
    usepercentile: false
    percentiles: []
  response_body_bytes:
    max: 0
    min: 0
    sum: 0
    usepercentile: false
    percentiles: []
  time: ""

その他

  • --limit N: 出力する行数をN行に絞る

おわりに

できることは全部以下に載ってます。

alp/usage_samples.ja.md at main · tkuchiki/alp

多機能だけど、使い勝手はシンプルで公式ドキュメントが充実していて使いやすいなと感じました。

業務ではもっぱらモニタリングツールに転送したりしているので、こうやってアクセスログを直接見る機会はだいぶ減ったのですが、機会があれば活用していけたらと。

以下は製作者のzenn記事です。

zenn.dev

【Python】CognitoのJWT検証

Cognito IDトークンの検証を実装する機会があり、やることは書いてあるけれど一通り説明できるくらいには理解しておきたかったのでその忘備録です。

gntrm.medium.com

とりわけ実装はこちらの方の記事を参考にさせていただいた形になります。

JWTとは

rfc7519

JSON Web Tokenとは、2者間で転送されるクレームを表すコンパクトでURLセーフな手段です。JWTのクレームは、JSON Web署名(JWS)構造のペイロードとして使用されるJSONオブジェクト、またはJSON Web暗号化(JWE)構造のプレーンテキストとしてエンコードされ、メッセージ認証コード(MAC)で暗号化してクレームをデジタル署名または整合性保護を可能にしたものになります。

JWTは以下のような構造になっています

  1. ヘッダー:key id、tokenを暗号化に使うアルゴリズムなどが表されている。
  2. ペイロード:ユーザー名やemailアドレスなどユーザーの情報や、認証方法、有効期限などクレームが入っている。
  3. 署名:署名は1と2をそれぞれbase64urlでエンコードして.で繋いだ文字列を、ヘッダーで指定したアルゴリズムで暗号化したもの。
# ヘッダー例
{
  "kid" : "1234example="
  "alg" : "RS256",
}

# ペイロード例
{
  "sub": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "aud": "xxxxxxxxxxxxexample",
  "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example",
  "email": "janedoe@example.com",
  "exp": 1500013000,
  "token_use": "id",
  "email_verified": true,
}

# 署名
RS256(
 base64urlEncoding(header) + '.' +
 base64urlEncoding(payload)
)

このヘッダー、ペイロード、署名をbase64urlでエンコードして . で繋いだ文字列がtokenになります

11111.22222.33333

検証の流れ

大まかな流れとしては以下です。

  1. 公開鍵を取得
  2. 署名の検証
  3. クレームの中身の検証

JSON Web トークンの検証 - Amazon Cognito

1. 公開鍵の取得

Cognitoでは、ユーザープールごとにパブリック JSON Web キー (JWK)が2組生成されます。クライアントのtokenはそのどちらかを使って復号する形になるのですが、クライアント側のkidとパブリックkidを比較し、一致する方がそれに当たります。

from jose import jwt, jwk

# パブリックJWKを取得
def get_jwks() -> JWKS:
    return requests.get(
        f"https://cognito-idp.{os.environ.get('REGION')}.amazonaws.com/"
        f"{os.environ.get('COGNITO_POOL_ID')}/.well-known/jwks.json"
    ).json()


# kid一致を判定
def get_hmac_key(token: str, jwks: JWKS) -> Optional[JWK]:
    kid = jwt.get_unverified_header(token).get("kid")  # 1
    for key in jwks.get("keys", []):
        if key.get("kid") == kid:
            return key


token = os.environ.get('TOKEN')
hmac_key = jwk.construct(get_hmac_key(token, get_jwks()))

パブリックJWKのサンプルは以下です。

{
    "keys": [{
        "kid": "1234example=",
        "alg": "RS256",
        "kty": "RSA",
        "e": "AQAB",
        "n": "1234567890",
        "use": "sig"
    }, {
        "kid": "5678example=",
        "alg": "RS256",
        "kty": "RSA",
        "e": "AQAB",
        "n": "987654321",
        "use": "sig"
    }]
}

今回は python-jose というライブラリを使って次のようなことをしています。

kid = jwt.get_unverified_header(token).get("kid")

今回のcognitoのケースように、token発行者が複数のキーを使用していてヘッダーの情報でそれを識別しないといけないケースでは、検証の前に先にデコードします。

この処理でクライアントのkidを取得し、複数あるパブリックJWKのkidと照らし合わせて一致する方のパブリックJWKを取得します。

hmac_key = jwk.construct(get_hmac_key(token, get_jwks()))

jwk.construct(key)で与えられたJWKからキーを生成しています。

2. 署名の検証

鍵が手に入ったので、暗号化されている署名を複合化し、中身を確認します。

header_payload, encoded_signature = token.rsplit(".", 1)
decoded_signature = base64url_decode(encoded_signature.encode())
hmac_key.verify(header_payload.encode(), decoded_signature)

やっていることは単純に、署名を復号したものがヘッダー + ペイロードが一致しているか比較しています。署名は元々はヘッダー+ペイロードでできているので、パブリックjwkで復号したものが一致していれば、tokenが本物であることが証明されます。

3. クレームの中身の検証

署名を検証することで本人確認が行えましたが、本人であるからといってその本人がこのアプリケーションでリクエストを取得できる条件を満たしているとは限りません。

とりわけCognitoの場合、以下のようなものを確認する必要があります。

  • トークンの有効期限(exp)が切れていないこと
  • Audience (aud) クレームは、Amazon Cognito ユーザープールで作成されたアプリクライアント ID と一致していること
  • Issuer (iss) クレームは、ユーザープールと一致すること
  • token_use クレームが意図したものになっているか

ペイロードの中身がこれらの項目が条件を満たしているのかそれぞれを比較します。

token = os.environ.get('TOKEN')
try:
    claim = get_claim(token)
except Exception:
    raise Exception("You are not verified!")

#  有効期限が現時刻より先であることを確認
if time.time() > float(claim['exp']):
    raise Exception("token is expired!")

token検証のサンプルコード全文は以下です。

イラスト入門した8ヶ月間の振り返り

0状態(自由帳に鉛筆でハム太郎ミルモを描いていた小1以来)からデジタルイラストを初めて9ヶ月が経ちました。

前回の記事で書き切れなかった現時点までの振り返りをします。

reiichii.hateblo.jp

初めたきっかけ

ここ数年、ずっと趣味を増やしたいと思っていました。新しいことを色々やってみたというよりは、やはり昔からアニメや漫画が好きだったので自分でも絵を描けるようになりたいと思いつつ、なかなか行動に移せていないだけでした。2年くらい前にipadを買って模写を始めてみたものの、3日しか続きませんでした。

そんな中、去年末にバズっていた以下のnoteに出会いました。

note.com

こんな風に100日練習続けられたら私もこのぐらい上手く描けるようになれないかな、なんて思ってしまったんですね。

思ってしまったにも関わらずすぐ絵を始めた訳ではなく、ぐずぐずしている時期もありました。そんなある時、本屋でnoteで取り上げられていた「ソッカの美術解剖学ノート」と出会ってしまいました。noteの中では入手困難だと言われていたのでこれはもう始めろと言うことではないのかと、勝手に盛り上がって5000円する分厚い本を買ってその勢いでイラストを始めました。

始められたもう一つの要因としては、この当時昔好きだった漫画に再熱していたのですが、当時は人気でも時の振るいにかけられて供給もまちまちになっていたので「なら自分が描くしかない」みたいな気持ちもありました。

年末年始というのあって、やたらモチベーションが高かったのだと思います😇

結果から言うと100日以上経っても燕禅さんみたいに上手くはなれていませんが、ただ前向きに続けられています。

やっていたこと

メモが残っていたので、それと合わせて思い出せる範囲で振り返ってみます。

2021.01

描いた絵:4枚(色まで塗ったのが2枚。人体のメモ書きは除く。)

noteを手本に練習を始めるも、同じやり方では結局1ヶ月も続きませんでした。

ipadにclip studioをインストールする

始めるにあたってまずは良い道具を揃えようと思ったのですが、そもそも全く描かない人間なので評判以外に良し悪しの判断基準がありませんでした。絵が上手い人と同じものを使おう、と思いパッと思い浮かんだのがしょこたんで、そのしょこたんはclip studioを「長年イラストを描いていた自分がずっと求めていた機能が揃っている」のようにべた褒めしていて実際に1ユーザーとしても愛用しているようだったので決めました。良いもので慣れれば、それ以降は自分でも必要な機能を判断できるようになれるはずだと。

ソッカ本で人体の練習をする

本の内容は一通り読み、書いて練習したりもしていたのですが、正直この練習方法は自分には合わなかったです。ただ筋肉とか骨とか内部の構造を意識しないと説得力のある絵は描けないんだなという学びはありました。モデルはずっと少佐でした。髪が短いから描きやすい&描いていてテンションが上がるからという理由だったのですが、全身義体(言わばサイボーグ)で人体を学ぶのなんか変だななんて思ったり。

人体描きながらクリスタの使い方を少しずつ覚えていく

ラスターレイヤーとベクターレイヤーの違いや、消しゴムの使い方(はみ出た線がうまく消せなかった。おそらく初期設定が柔らかい消しゴムになっていて筆圧が足りなかったのだと思う)などググっていました。

好きな漫画家の落書き(線画のみ)を模写する

イラストを始めるにあたって一番描きたいと思っていた好きな漫画家の絵を見ながら描こうと思いました。ただ漫画の絵はハードルが高く、その人のツイッターに時々アップされる線画のみのキャラクターの落書きを参考にするところがスタートラインでした。やっている時は楽しかったのですが、そもそもそんなに数が多くなかったので少ししたらネタに枯渇しました。それから模写しか描けない状態にうんざりしていました。

イラスト系Youtuberの動画を見始める

クリスタ道場という本を買ったりもしていたのですが、デジタルイラストにおいてはYoutubeが最高の教材でした。特にさいとうなおき先生やディープブリザード先生の動画にはいつもお世話になっています。瞳の塗り方などを動画で見て、色を塗る時に1影2影みたいな工程があるのを知りました。

こうして挫折しそうになりながらも1ヶ月続けて来たところ、夕飯食べたらイラストの時間(長くて21:00〜00:00)という生活習慣が身に付きました。

2021.02

描いた絵:4枚

模写で学び、それを参考にオリジナルを描く

最初の1ヶ月目で「楽しくないと続かない」という真理に行き着いたので、この頃は参考にしたい絵を見つけて模写して練習した後、同じ構図で違うキャラクターを描く、といった絵の描き方をしていました。今思えばそれが結果的に脱模写につながりました。

好きな絵師さんだけだと参考資料がすぐに枯渇したので(絵がうますぎて自分に真似できそうなものがそもそもそんなになかった)、だんだんツイッター開く時に描きたい絵のネタになりそうな絵をストックすることが習慣になりました。誰かからのいいねで表示されて知ったものもあれば、pixivで好きな絵師さんのツイッターを見つけてフォローするなどもしていました。

ツールの設定を見直す

用紙のサイズやペンの太さなど、そもそも今の設定で合っていないような気がするとふと思い、設定を確認しました。もっと早く気づくべきでした。

この頃動画で知識が増え、線画、色塗り、ツールの効果など覚えるべきことがたくさんあることを知り、その全てに気が回らなかったので「まずは線画を上手くなれるように集中しよう」と思っていました。(結局物足りなくて色塗りも手を出すようになりました)

2021.03

描いた絵:5枚

2月にやっていたような絵の描き方をしていたら、描きたい絵を描くやり方が少しずつ身に付いてきたので、その延長線上で描きたい絵をひたすら描いていました。

有名な構図や映画のポスターのパロディで描く

某キャラで攻殻立ちを描きたくて、画力を無視して一枚に6人以上の全身を描くといった暴挙に出てものすごく疲れました。ただ達成感は大きかったです。他にも某映画のパロディで某キャラを描いたりなど、結果的にテンションに任せて作画コストの高い絵ばかりを描いていました。

こうして振り返ってみるとここ最近よりも制作時間が短かったのが自分で驚きでした。おそらく描くイメージが固まっていた(キャラ、衣装、ポーズ、構図、背景が決まっていてそれを組み合わせるだけだったから)からだと思います。

2021.04

描いた絵:6枚

3月に頑張り過ぎた反動で、4月に描いた絵のほとんどが単色でしか塗られていませんでした。また少し行き詰まっていたのかもしれません、いつもと違う試みをしていました。

筆を使った日本画風のイラストを描いてみる

お手本のように上手く筆は使えなかったのですが、その割にはそんなに悪くないと思える仕上がりでした。いつもと違う描き方をするのはとても気分転換になります。これも少佐で描いていたのですが、今度は別のキャラで再チャレンジしてみたいです。

www.youtube.com

ワンドロに挑戦する

この時期にどハマりしていた某漫画のワンドロワンライ企画を今まで見ているだけだったのですが、挑戦してみました。1日(3h)でも完成させることができないのにワンドロなんてとんでもない。実際でき映えはひどいですが、それでも普段長考しがちな人間なので、あえて早さに振り切って描くのはそれはそれで新鮮な試みでした。お題が出てから1時間でネタを考え、参考資料を探して、1時間で線を引いて申し訳程度に色をつけるだけですが、仕事の時以上に(?)頭と集中力を使い、ぐったりしながら寝るといった具合でした。結局3回しか参加できていなかったのですが、また参加したいとは思っています...。

イベントのカウントダウン用イラストを描く

これまでは描いた絵をただツイッターにあげていくだけだったのですが(当然バズるなんてこともなく)、イラストを通じて初めて他者と関わる機会を作れました。好きな作品オンリーのオンライン同人イベントがあり、カウントダウンイラストの募集があったので思い切って手を挙げました。指定されたサイズの用紙に日付とキャラクターを描くものでしたが、用紙の形が普段描かない横長のものだったので構図にすごく悩みました。画力も全然ないのに、提出した時に主催の方がとても優しいコメントをくださって感動しました。 またハッシュタグを使って多くの人に見てもらえる機会にもなって、改めて上手くなりたいな、なんて思いました。

2021.05

描いた絵:5枚

ワンドロで描いた絵を清書する試みをしたり、好きなキャラの記念日で久しぶりに作画コストの高い絵を描いたりしていました。

初めて人間以外を描く

旦那の誕生日だったので、旦那の好きなsuicaペンギンをメッセージと合わせて描いてプレゼントしました。漫画のキャラクター以外を描いたのは初めてでした。

初めて漫画を描く

オンライン同人イベントに触発されて、初めて漫画(っぽいもの)を描いてみました。2枚合計6コマの非常に簡単なものです。子供の頃から漫画はそれなりに読んでいたにも関わらず、びっくりするくらい描き方が分からなくて衝撃を受けました笑。

2021.06

描いた絵:2枚

衣装や構図も完全オリジナルで絵を描きたくなって、資料集めに時間がかかって2枚しか描けませんでした。内1枚は線画にちょっと色をつけただけの簡単なものなので、実質1枚...。着られていない状態の服を、ポーズを取ったキャラクターに着せるのがとても大変でした。

板タブ(Wacom Intuos Pro Medium)を買う

ipadで作業するにあたって、姿勢が悪すぎて首に限界がきました。(壁に背中つけて体育座りして膝位にipadを載せて描いていた) 液タブが良いよと知り合いに勧められたりもしたのですが、結局液タブも首が痛くなりそうだなと思い、描きにくそうだけど一回板タブを試してみようと、wacomの板タブを購入しました。ちょうどボーナス月だったのもあり😌 新宿にある実店舗で試し描きができたのが良かったです。

2021.07

描いた絵:2枚

内1枚が作画コストの高い絵でした。板タブに乗り換えてから作画コストの高い絵は初でした。

板タブに慣れる

板タブに乗り換えてから、それまでタッチで済ませていた操作をほぼ全てショートカットキーで覚え直す羽目になりました。危うく挫折するところでした。よく使うものから順にキーを覚え、使いやすいところに割り当て直すことで、描いている内に最低限の操作を手になじませることができました。

また左手デバイスの購入も検討したのですが、結局良い感じのものが見つからなかったため、もともと使っていた分割キーボードの左側(と板タブのボタン)にショートカットキーを割り当てることで、ペンを持ち続けたまま作業できるようにする試みをしています。今の所はうまくいきそうな気配です。

あと雷エフェクトの描き方を覚えました😁

2021.08

描いた絵:3枚

内2枚はモノクロ、新聞の端に載っているような類の四コマ漫画です。ハガレンのおまけに影響されて描き始めました。ネタを考えるのが大変だし、面白い自信もそんなにないのですが、楽しかったのでまた描きたいです。

...こんな感じでざっと振り返ってみました。当初の計画性は何処へやら、8ヶ月間テンションに任せて突っ走ってきただけだったのを実感しました。自分の過去絵を見返すと、昔に比べたらだいぶ上達してきているようには見えるですが、とはいえひと月の完成枚数が減ってきているのが悲しいです。

やって良かったこと

とりわけ次の4つが大きかったです。

ipadの写真フォルダをイラスト特化型にした

ネットで見かけた素敵な絵や、描きたい絵の種になった写真、TLに流れてきたイラストの描き方のスクショや画像を写真フォルダに入れるようにしています。それ以外の日常の写真や関係ないスクショは入れないようにしています。自分のテンションが上がるものだけが詰まっていくので写真フォルダを見返すだけでイラストへのモチベーションが上がります😊

書きあげた絵を専用フォルダに溜める

「描いた絵」フォルダを作って、仕上げた絵はそこに溜めるようにしています。現時点で34枚です。下手だなと思うのですが、自分なりにこだわりを詰めて描いたものだからか、割と満足感も強いです。時々見返して自分偉いな、前と比べたら上手くなったな、これからもマイペースに続けていこう、みたいな気持ちになっています笑

イラスト系Youtuberの動画を見る

さいとうなおき先生、ディープブリザード先生、親切で良質なコンテンツをいつもありがとうございます。大事なことは全部動画から教わったといっても過言ではないです。これからもたくさんお世話になります。

練習よりも、自分が楽しく続けることを優先する

楽しいと思うやり方でないと続きませんでした。逆にほどほどにしようと思っていても、一度描き始めると満足いくまで修正を繰り返してしまうこともありました。翌日の仕事のことを考えるとなかなかブレーキをかけずに突っ走るというのは難しいのですが、理性の強い自分にもこんな一面があったのだなと再認識しました。

今後やりたいこと

  • 1日1絵
  • 厚塗り習得
  • ちゃんとした漫画描く
  • 斧を磨ぐ(クリスタの設定の調節)
  • 作画コスパの高い絵の見せ方を研究する
  • 定期的にポートフォリオとして少佐を描く

とりわけ最近の悩みは効率です。描き方が分からず調べながら描いているので時間がかかるのは当然なのですが、ツイッターを見ると凝っていなくても素敵だなと思う絵がたくさんあります。私は凝った美しい絵よりも、らくがきみたいなものでもいいから楽しい絵、生きているキャラクターをもっとたくさん描けるようになりたいです。自分の理想のイラスト生活に近づくためにも、選択と集中みたいなことをそろそろしても良さそうだなと思っています。

おわりに

私にとって文章は書いているだけで楽しいものですが、イラストに関しては、描くこと自体が楽しいというよりは、自分の頭の中にある願望・欲望をを形にできるところに楽しさを感じています。描くことが好きなわけではないのだな、ということは旦那との会話で自覚したことでした。

それはそれとして未だに分からないことがあって、時々「練習」と称して1枚の絵に同じキャラクターの線画をたくさん描いている人がいるのですが、そういう人たちにとってのその絵はどんな練習で、それをした結果何を習得できたのでしょうか...。私ももっと練習したいのですが、目標の決め方がいまいち分からず、もし言語化できる人がいたら教えて下さい。

イラスト9ヶ月目

今年の初めにデジタルイラスト入門し、平日夜や休日に絵を描く日々を送っていました。

色々落ち着いてきたので、現時点でのポートフォリオ的なものを残しておこうと描いた絵が以下です。

f:id:reiichii:20210912215832j:plain:w500

夏らしいことを特にせず夏が終わったので、夏納め的な。

制作過程の振り返り

TL

  • 08.31
    • 構図を考える
    • 最初から決まっていたものは「少佐」「浴衣」「りんご飴」のみ。
    • 紙に構図案を描きながら、浴衣の描き方が分からない、人体の描き方が分からない、参考資料探しに苦戦。
  • 09.02 21:00~23:30
    • ラフを描く
    • 色を簡単に載せる
    • 浴衣の柄と背景を探す
  • 09.04 12:40~13:00 + 21:00~00:10
    • 色を乗せる(続き)
    • 背景を探す(続き)
    • 浴衣のイラストを探す(続き)
  • 09.05 21:30~23:20
    • 色を乗せる(続き)
    • 浴衣の柄を描き直す
    • 全体の形を整える
  • 09.06 21:00~00:00
    • グレーで色をつける(2h)
    • 線画(1h)
  • 09.09 22:20~00:30
    • 瞳の着色
    • りんご飴の着色
  • 09.11 09:30~12:30
    • 肌の着色
    • 髪の着色
  • 09.11 14:30~17:00
    • 浴衣の柄描く
    • 浴衣着色
  • 09.11 20:40~23:00
    • 背景着色
    • 修正したいところを挙げる
  • 09.12 11:45~13:50
    • 昨日上げた点を直す
      • 書き忘れていた腕時計を付ける
      • 髪の色直す
      • 髪のハイライト直す
      • 帯のハイライトに筆を使って塗り直す
      • 背景と人物をなじませる影を付ける
      • はみ出た線画を消す
      • 色トレスする
      • 顔のパーツの線を直す

まとめ

  • 制作日数:8日
  • 制作時間:22時間55分

背景手を抜いた割には時間かかり過ぎかも、という印象。案出しにもそれなりに時間がかかっていたのでこの時間が妥当だったとしても、もう少し早く完成させられるサイクルで絵が描けるようになりたいものです...。

改善したいこと

  • 途中で線画が邪魔になるので、瞳は塗りフォルダで完結させたい
  • 効果レイヤーと塗りレイヤーを分ける
    • 最後の修正で線を消しこみたかったが、混ざってしまって思うように色を塗れなかったため
  • レイヤーに名前を付ける
    • めんどくさくなって名前をつけないことも多いが、やはり途中で分からなくなってしまう方が不便だった
  • 自分の中で作業工程のテンプレートを作る
    • 迷う時間を減らしたい
  • よく使う色のパレットを作る
    • 肌色でよく悩むため。褐色とかは別としても、イエベブルベ春夏秋冬4色あれば事足りる気がする
  • 使いやすい筆とサイズを決める
    • 地味に時間食っているところ。描きながら色々試していたのだけど、いまだにしっくりこない

とりわけ線を引くところ以外で考える時間を減らしたい所存。

【Python】jsonライブラリのベンチマークを測った

ソースコードの全体は以下です。

github.com

内容としては設定ファイル(settings.py)に比較したいPythonjsonライブラリと、読み込むjsonファイルのパス、テストする回数等を記載し、実行するだけです。

# settings.py

MODULES = ("json", "orjson", "rapidjson", "simplejson", "ujson")

COMPLEX_FILE = "./test_data/COMPLEX.json"
SIMPLE_FILE = "./test_data/SIMPLE.json"

REPEAT = 10
NUMBER = 10000

ベンチマークの部分は以下。

def benchmark_dumps(m, data: str, r: int, n: int):
    return min(repeat(lambda: m.dumps(data), repeat=r, number=n))


def benchmark_loads(m, data: str, r: int, n: int):
    return min(repeat(lambda: m.loads(data), repeat=r, number=n))

timeit.repeat()でr回(今回は10)メソッドの実行を行うことをn回(今回は10000)繰り返し、最小値を結果としています。

結果はcsv形式で出力されるので、excelスプレッドシートで開いて早かった順に並び替えたりグラフ化したりします。

検証結果

  • t2.micro
  • Ubuntu 18.04(ami-0fe22bffdec36361c)
  • Python 3.9.6

で実行した結果、以下のようになりました。

COMPLEX JSON

jsonデータはいずれも拾った適当なものです。

COMPLEX.json

f:id:reiichii:20210831082540p:plain

SIMPLE JSON

SIMPLE.json

f:id:reiichii:20210831082658p:plain

データの種類や処理に関係なく私の環境では以下の順に速かったです。

  1. orjson
  2. ujson
  3. rapidjson
  4. json
  5. simplejson

おわりに

もはや何番煎じみたいなネタですが、自分で検証できる手段は持っておきたかったのと、同じ処理を複数のライブラリで実行するコードを書きたかったという試みでした。(それに自分で検証するの大事)

今回は技術選定目的ではなかったのですが、実際にそれ目的で検証する機会があったときにデータ見直してこのコード使ってまたできたらなと。

参考

Benchmark of Python JSON libraries - Artem Krylysov

今回検証したjsonライブラリについて

標準のjsonモジュール

orjson

python-rapidjson

simplejson

ujson

【Python】 Requestsモジュールのコードを読む

プログラミングの設計を知りたく、ここ最近はOSSPythonのコードを読むなどしています。

その中でRequestsモジュールを選んだのは、おそらく一番広く使われていて量も少なく読みやすそうだったからです。

本記事ではRequestsモジュールの中で、HTTPステータスコードがどのように定義されているかを忘備録がてら解説します。

github.com

ステータスコードの参照

>>> import requests
>>> requests.codes['ok']
200
>>> requests.codes['OK']
200
>>> requests.codes['\o/']
200
>>> requests.codes.get('internal_server_error')
500
>>> requests.codes['hoge']
>>> 

codesという変数で名前からステータスコードが引けるようになっています。

[]とgetでアクセスできることから、一見ただの辞書のようですが、辞書と異なる点に次のような挙動があります。

  • 一部のもの(\o/など)を除き、大文字と小文字でどちらでも引ける
  • 存在していないキーを入れてもKeyErrorにはならず、Noneが返る

これらの挙動は次のようなコードで定義されています。

ステータスコードの定義

ブログでは最小限の箇所のみを取り上げています。実際のコードにコメントが丁寧に書いてあるので本家を見ることをお勧めします。

requets/requests/status_codes.py

from .structures import LookupDict

_codes = {  # 1
    200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', '✓'),
    201: ('created',),
}

codes = LookupDict(name='status_codes'). # 3

def _init():
    for code, titles in _codes.items(): 
        for title in titles:
            setattr(codes, title, code)  # 2
            if not title.startswith(('\\', '/')):
                setattr(codes, title.upper(), code)

    def doc(code):
        names = ', '.join('``%s``' % n for n in _codes[code])
        return '* %d: %s' % (code, names)

    global __doc__
    __doc__ = (__doc__ + '\n' +
               '\n'.join(doc(code) for code in sorted(_codes))
               if __doc__ is not None else None)

_init()
  1. まず_codes変数の中で、ステータスコードと対応する名前が100番代から500番代まで全て定義されています。

  2. それを_init()の中で、codesという実際に使う変数に、setattr()で属性値として大文字と小文字両方で定義しています。

  3. codesはLookupDictクラスインスタンスになります。

setattr(codes, title, code)codes.title = code と同義です。

LookupDictの定義は以下のファイルにあります。

requets/requests/structures.py

class LookupDict(dict):
    """Dictionary lookup object."""

    def __init__(self, name=None):
        self.name = name
        super(LookupDict, self).__init__() # 1

    def __repr__(self):  # 2
        return '<lookup \'%s\'>' % (self.name)

    def __getitem__(self, key):  # 3
        # We allow fall-through here, so values default to None

        return self.__dict__.get(key, None)

    def get(self, key, default=None): # 4
        return self.__dict__.get(key, default)

LookupDictクラスは辞書を継承したオブジェクトになります。

  1. 継承している親クラスの処理も行われるように定義されています。Python3ではsuper()の引数は省略できるのですが、Requestsモジュールは広く使われているからか、全体的にPython2系と互換性のある書き方がされている箇所が多くあり、ここもそうなっているようです。

  2. __repr__()repr(codes)をしたときの返り値を定義しています。ちなみにreprは「representation」の略称らしいですね。

  3. __getitem__()codes['ok'] の形で呼び出されたときの返り値を定義しています。

  4. get()codes.get('ok') での返り値を定義しています。この定義がないと、getで呼び出してもNoneが返ります。扱いは辞書だけど実際にはcodesの属性にステータスコードが定義されているだけで、codes自体はただの空の配列のためです。

おわりに

コードの読み方に関しては手探りです。とりあえずディレクトリの構造を把握しつつ、全体を軽く見て、基本的なところ(今回はgetリクエストをしたときの処理の部分)を読みつつ、気になった部分をメモしてさらに読んでいくみたいな流れで今は行っています。

ステータスコードの部分は短く分かりやすかったので取っ掛かりに丁度よかったです😃