Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

Go言語のloggerをDefault1つで済ませる方法:slog Handlerがcontextの中身を見てよしなにするパターン

先日、kamakura.go #6にて、「slog登場に伴うloggerの取り回し手法の見直し」という題で登壇しました。Go Conference 2024にこの内容でproposalを出していて落選してしまったのですが、kamakura.go #6のテーマ「昔のGo、今のGo」とピッタリだったので応募して発表の機会を頂いたという経緯です。

speakerdeck.com

この発表は以下のエントリのrefinement版となる内容です。今度は机上論ではなく実践経験をもとに話を組み立てています。

blog.arthur1.dev

loggerの引き回しと、なぜそれが必要だったのか

あるアプリケーション・モジュールの構造化ログについて、常に特定のフィールドを付与したいというニーズがあります。例えばWebアプリケーションなら、ソースコードの関数・モジュール名、リクエストID、ユーザIDといったログの検索に有益な情報をログに付与したいでしょう。

その時に、ログの出力命令ごとにいちいちフィールドを指定しまくるのは大変です。単純にコード量が多いし、仮に付与したいフィールドが増えたときに一行一行変更して回らないといけません。ログに出したい変数を持って回る必要もあります。

log.WithFields(log.Fields{"module": "userService", "user_id": 1}).Info("validation passed")
...
log.WithFields(log.Fields{"module": "userService", "user_id": 1}).Info("succeeded")

(このあたりのサンプルコードは構造化ログのライブラリとしてhttps://github.com/sirupsen/logrusを使用したケースです。)

そこで、必要に応じてloggerインスタンスを作り、呼び出し先でも利用できるように引き回す手法があります。例えば、ユーザIDが確定した時点でユーザIDを常にログに含むようなloggerを作って、それ以降の処理ではこのloggerを利用してログを書くといったやり方です。

logger := log.WithFields(log.Fields{"module": "userService", "user_id": 1})
logger.Info("validation passed")
...
// loggerを作った命令とは別の関数の場合、loggerをここでも使えるように引き回す必要がある
logger.Info("succeeded")

loggerの引き回し手法はいくつかありますが、私個人としては以下の記事で推奨されている手法の1つである「contextにloggerを入れる」パターンが好みです。

zenn.dev

package mylog

import "context"

type key struct{}

func ContextWithLogger(ctx context.Context, logger Logger) context.Context {
    return context.WithValue(ctx, key{}, logger)
}

func FromContext(ctx context.Context) Logger {
    return ctx.Value(key{}).(Logger)
}
func A() {
    logger := log.WithFields(log.Fields{"user_id", 1})
    ctx := mylog.ContextWithLogger(context.Background(), logger)
    B(ctx)
}

func B(ctx context.Context) {
    logger := mylog.FromContext(ctx)
    logger.Info("Hello, world!")
}

ただし、この手法はloggerを利用したい関数でcontextからloggerを取り出す命令を書かなければいけないので、そこだけ若干煩雑でコードの美しさが損なわれると感じていました。

slogの特徴

さて、Go 1.21からはGoビルトインのlog/slogパッケージが登場しました。https://go.googlesource.com/proposal/+/master/design/56345-structured-logging.mdがプロポーザルの内容で、こちらを読むとどのような思想で設計されたのかを知ることができます。

Logger/Handler

LoggerとHandlerという2つのインタフェースに概念が分離されました。

Loggerのほうはフロントエンド・ファサードのような立ち位置で、たとえばloggerのInfo(msg string, args ...any)というメソッドを呼ぶことでINFOレベルのログが書けますというインタフェースが定義されています。

Handlerは実際にログのレコードを作って書き込むという処理を担います。text、jsonといった出力フォーマットを変えることができるのはもちろん、ログレコードに特定のフィールドを追加するといった処理も可能です。

Loggerのインタフェースとして、contextを引数に持つ関数が用意される

InfoContext(ctx context.Context, msg string, args ...any) のように、ログを出力する関数の引数にcontextを持つものが用意されました。Handlerは渡されたcontextの値を利用することができます。

デフォルトLogger

従来のlog packageと同様にslog.SetDefault(l *Logger)というデフォルトのloggerを登録しておける関数が用意されています。デフォルトのloggerを登録しておくと、loggerのインスタンスからログ出力関数を呼び出さなくてもslog.Info()のようにpackage名から直接ログを書き出すことができます。

実装としてはグローバル変数にloggerが格納されているのと大差ないのですが、複数のgoroutineから呼ばれことを考慮してsync/atomicが利用されています*1

contextの中身を見て自動でフィールドを追加するHandlerがあれば……

これらの特徴を見て私は「場所によって構造化ログに自動で入れたいフィールドを制御したいという理由だけで、リクエストごとloggerを作って引き回す必要はなくなったのでは」と思いました。具体的には、contextの値を読み取って自動でフィールドを追加するようなHandlerを作り、これを用いて作ったloggerをデフォルトとして登録してしまえば、リクエストごと作ったloggerを引き回す必要がなくなるという見立てです。

こういった方針で作ってみたのが以下のコードです。contextに含まれる特定のキーの値をHandlerがログレコードのフィールドに追加するようなHandlerができました。

package main

import (
    "context"
    "log/slog"
    "os"
)

var keys = []string{"user_id", "request_id"}

type MyLogHandler struct {
    slog.Handler
}

var _ slog.Handler = &MyLogHandler{}

func (h *MyLogHandler) Handle(ctx context.Context, r slog.Record) error {
    for _, key := range keys {
        if v := ctx.Value(key); v != nil {
            r.AddAttrs(slog.Attr{Key: string(key), Value: slog.AnyValue(v)})
        }
    }
    return h.Handler.Handle(ctx, r)
}

func main() {
    logger := slog.New(&MyLogHandler{slog.NewJSONHandler(os.Stderr, nil)})
    slog.SetDefault(logger)
    A()
}

func A() {
    ctx := context.WithValue(context.Background(), "user_id", 1)
    ctx = context.WithValue(ctx, "request_id", "000")
    B(ctx)
}

func B(ctx context.Context) {
    slog.InfoContext(ctx, "Hello, world!")
}

https://go.dev/play/p/pjs7A-WNptj

slog.InfoContext(ctx, "Hello, world!")という呼び出しで

{"time":"2009-11-10T23:00:00Z","level":"INFO","msg":"Hello, world!","user_id":1,"request_id":"000"}

というログが書き出されます。

任意のkeyでもログに書けるようなHandler

先ほどの手法では、ログに書き出したいcontextのフィールドのリストを定義しておく必要があります。しかし、ログに書き出したいものが増えるたびにリストを編集するのは大変だし、そもそもloggerという存在がアプリケーションの内部のことを知っている(たとえばrequest_idというログに書き出したい存在がいること)ように見えて微妙な感じがします。

contextにsync.Mapを持ち、その中身をログに書くHandler

ログに書き出したいcontextのkeyを1つ1つ管理しなくても良い手法として、contextにmapを持ってしまい、handlerとしてはmapの中身を全部書き出してしまうやり方があります。mapのキーだけ決めてしまえば良いです。

  • context
    • user_id <- このキーをログに出すと管理
    • request_id <- このキーをログに出すと管理

という構造だったのが、

  • context
    • logcontext (map) <- このキーをログに出すと管理
      • user_id
      • request_id
      • その他なんでも

という構造になるイメージです。

ただし、goroutine safeになるようにただのmapではなくsync.Mapを使うと良いでしょう。実際にこの指針で実装されたslog HandlerがGitHubで公開されています。

github.com

2024-05-22 追記:上記slog-contextの実装は、親のcontextに入っているmapを子から破壊する実装になっていました。issueとPRが出ていたのでそのうち直されるかも WithValue on contex with existing fields affects the parent context · Issue #5 · PumpkinSeed/slog-context

OpenTelemetry Baggageの中身をログに書くHandler

また、ログに書き出したいものの置き場としてOpenTelemetryのBaggageを利用するのも手です。

opentelemetry.io

Baggageはテレメトリのためのコンテキスト情報をspan(service)をまたいでも伝達するためのキーバリューストアです。Baggageの基本的な使い方として挙げられるのは、下流のサービスに上流のサービスのコンテキスト情報(何らかのIDとか)を受け渡し、下流のサービスではBaggageから値を取り出してspanのattributeに追加する、といった流れです。Baggageに入れるのはテレメトリに付与したい情報なので、その中身をログに書きだしても大抵は不都合がないでしょう。

OpenTelemetry Baggageをcontextから取り出してログに書くslog handlerについても、すでにサードパーティの実装があります。Baggageの中身だけでなくトレースIDもログレコードに付与してくれるので、ログとトレースを紐づけたいときにとても便利です。

github.com

まとめ

典型的なWebアプリケーションにおいて、「ここだけはJSONログにしたい」など複数のslog handlerを併用したいことは少ないと感じています。やりたいのは高々ログに付与する属性を制御したい程度でしょう。その役割はcontextの中身を見てよしなにするリッチなhandlerに任せることで、リクエストごとにloggerインスタンスを作る必要がなくなります。リクエストごとloggerが作られないのなら、頑張って引き回す必要もなくなり、デフォルトのloggerに登録しておけばどこでもslog packageから呼び出してシンプルにログを書き出すことができます。Webアプリケーションのloggerとしてはこの考え方で十分実用に適うと思います。

エクストラバトルでしばらく使っていたドラゴンルギアを紹介します

普段は技術記事やしょうもない日記を書いている当ブログですが、ポケモンカードの話をこのブログで書きたいなと思ったのでやります。

記念すべき第一弾として、エクストラバトルでしばらく使っていたドラゴンルギアを紹介します。このブログ自体はずっと前から用意していたけど、そろそろ旬が過ぎてしまうので仮完成の状態で出します。

ルギアを握っているのはルギアVが好きだから以上には特にないです。スタンでもルギア使いがち。

デッキ解説

こうして眺めてみるとCL2023優勝ルギアと大体同じですね。

note.com

構築思想

大会ではなくエクストラバトルで優勝できそうかという観点を重視しています。なぜならエクストラの資産をあんまり持っていなくて、プロモカードがいっぱいもらえると嬉しいからです。

エクストラバトルならルギア・レジドラゴ・うらこうさくリザードン・ミュウVmax・ミライドンex・テツノイバラexあたりが多いかな〜という気持ちで組みました。CL愛知2024の結果からエヴォコンも増えてきたので、カード解説ではエヴォコンにも触れています。

先攻/後攻

当初はジャンケンに勝ったら後攻を取っていましたが、構築の方向性的に先攻でも全然アリだな〜という感じです。上振れたらやっぱり後1で起動するのが強いので今のところ後攻を選びつつ、先攻になっても落ち込まない気持ちでいます。

どちらでも良いなら先攻後攻どちらを渡したくないかという観点で決めたいのですが、レジドラゴ、うらこうさくリザードンあたりは先行が欲しく、アオセグ、エレキレイン、エヴォコン、一般的なルギア、ミライドンあたりは後攻が欲しいという環境。なんとも言えないですが、先攻を意識したデッキが増えてきたとはいえまだまだ後攻を選びたい人が多そうな気がします。

ポケモン

ルギアV/ルギアVstar 3枚/3枚

これが立たないと話にならないので、採用枚数としては3-3か4-3の2択を取る人が多いんじゃないかなと思います。4-3にしておけば良かった~と後悔することがあんまりないので今のところ3-3でやってます。

アーケオス 3枚

先攻1ターン目や、後攻1ターン目にミツル→アッセンブルスターの流れが決まらない場合、アーケオスのトラッシュを先にしてしまうと、キリンリキにロストおくりされてしまう可能性があります。これをケアせずにプレイしたい人は4枚入れると良さそうです。ケアする場合には、対戦相手のデッキにキリンリキが入っていそうかエスパーし、入ってそうだなと思ったらバトコンを後回しにする(サイド落ちを確認する手段としてはクイックボールを先に使う)とか、1枚だけアーケオスを落としておくとか、うまいことやりましょう。

エヴォコン対面だと相手の盤面が整うまでにどれだけ頑張れるかゲー(いかにエネルギーを割ったり、グッズロックでサーチャーを縛ったりする余裕をなくすか)なので、1つアーケオス立てばなんとかなるかという気持ちでキリンリキを割り切るのも大事かもしれません。アーケオスよりグズマを送られる方が辛い。

ガブリアスV 1枚

あと10点あればなあと思うこともありますがまあ最強です。このデッキはHP280クラスのポケモンを一発で取る大きな打点が出せるようには組まれておらず、サイドは2-2-2と取るのを基本プランとしています。その戦略が成り立つのは、グズマに頼らずにベンチにいるサイド2枚のシステムポケモンを取れるガブリアスVの存在あってこそでしょう。

グズマの利用に困らない意味でも逃げエネ0なのは嬉しいポイントですが、それが仇となることもあります。ジージーエンド打ちたい盤面でダブドラエネを逃げで剥がしてスペシャルチャージで山に戻すということができないので、ダブドラエネがサイドにあるときは頭の片隅に入れておくと良いでしょう。

殴った後盤面にエネルギーが残らないというのも実は強いポイントで、エヴォコンのジラーチの無敵効果を使わせないプレイができます。当然不利な対面ではあるものの、グッズロックするラフレシアを取れる流れが運よく作れたら、その後に意識したい動きです。

アーゴヨン&アクジキングGX 1枚

2-2-2プランの最後をカオスオーダーGXで決めるのがメインの使い方。特性ぼうしょくでアーケオスをトラッシュすることもできます。

ガブリアス&ギラティナGX 1枚

後1ジージーエンドGXでイージーウィンを狙うのはもちろん、不慮の事故や不利対面時にも、ジージーエンドを打ちつつナンジャモ・マリィによる手札干渉を組み合わせると2ターン以上もらえる可能性がでてきます。テツノイバラexデッキに逆転して勝てたのはこいつのおかげです。アタッカーやシステムポケモンをトラッシュに送りエネルギーテンポや進化テンポをゼロにして、さらに後続が立ちそうならガブリアスVやグズマを利用して狩るといった流れですね。ミラー時にアーケオス2体をトラッシュさせるのもじゃんけん敗北時には定番です。むしろ後1ジージーエンドにこだわりすぎないぐらいの気持ちでやっています。

ドラピオンV 1枚

ミュウVmaxデッキはファンが多い印象があって、スタン落ち後もエクに持ってくる人多いだろうな~と思ってピン刺し。実際には1回も当たってないです。

ドラピオンを採用しても雑に投げると負けます。例えば、6→4→1とサイドを進める(Vポケモンを取ってから、ミュウVmaxをドラピオンVで取る)と、返しのN・ナンジャモで前のミュウVmaxを取る手段も避ける手段もなくて捲くられることがあります。2回目はガブリアスVで裏のサイド2枚のポケモンを取って(6→4→2)、ドラピオンとアゴアクジ両方のプランを取れるようにしつつ、手札干渉への耐性を少し上げておくのがよさそうです。手札2枚まで減らされた状態ならターン開始の1ドローで手札3枚になるので、パソコン通信やハイパーボールも当たり札になります。

そもそもミュウVmaxもこくばバドレックスVmaxも環境になかなかいないので、この枠は抜いてしまって別のアタッカーにしても良いかもしれません。

今自分が回しているリストではこの枠をハバタクカミにして、テツノイバラexやソーナンスへの解答を作ったりうらこうさくリザードンのプレイングミスを拾ったりしています。2点を後ろに置けるのも結構ちゃんと生きる印象です。

大会はさておきエクストラバトルで当たりがちなうらこうさくリザードンをちゃんと見るなら、次弾「ナイトワンダラー」で登場するアンチプラズマのキュレムを入れてみたいですね。

キリンリキ 1枚

ミラー対面で後1アッセンブルスター決められなかった相手のアーケオスをロストに送る、コントロールデッキのサポートを送る、サイド1枚のポケモンで1ターンしのぎつつ後ろを取られないようグズマを送る、レジドラゴのわざマシンもといポケモンを送る、などなどの使い方があります。でも冷静に考えると1ターンロスしている(特に280打点を出せる対面では1-2-3とサイドを取られてしまう)ので、普通に戦っても勝てる試合を別ルートに歩んだだけのことも多いです。レジドラゴについてもメタれているようで少し物足りないところもあります。まあエクストラ普通に疲れるのでミラーのイージーウィンのルートが増えているのは悪くないでしょう。

入ってると思われたら警戒され対策されるので、「ルギアにキリンリキ入ってるんだ~」と(実際に僕が)言われているうちが華でしょう。とはいえ入らないこともないでしょとは思っていますが。

マインドショック70点を使う展開もあるにはあるのですが、この技は弱点・抵抗力を計算しないことに注意してください。一回これでやらかしました。

カプ・テテフGX 2枚

序盤はミツルや博士、中盤以降はグズマにアクセスできるカードとして優秀なので2枚欲しいです。こいつがバトル場スタートすることがそれなりの確率であるのが残念ですが、最悪殴れるので良いということにしておきましょう。ただしこの技もキリンリキ同様に弱点・抵抗力の計算がないので以下略。

ガブリアスVの解説で触れた盤面にエネルギーを残さないプレイをするために、1枚ネオラントVにしてプリズムエネルギーを1枚採用するのも面白いかもしれません。サポートを持ってくるポケモン枠に2枚割いているなら、片方はソーナンスを割り切っても大丈夫な気がします。ネオラントVで120ダメージ当てておくと、ガブギラのカラミティエッジと合わせて360点見れるのも良いですね。

デデンネGX 1枚

デッキの1/10に当たる6枚引くことができるので、後攻の動きの再現性を高めるのに貢献します。終盤にサーチャーなどのキーパーツをサポ権なしで引くのに取っておけることもあります。

クロバットVが不採用の理由も書いておくと、ベンチ枠が足りないのと、そこまで後1アッセンブルスターに拘ってないというところがあります。ワンショットデッキみたいに無理やり回すより、一旦落ち着いて勝ち筋を探る方が好みです。ドラゴン型ルギアは手張り権だけで主要なポケモンが殴れたり、ジージーエンドからひっくり返したりできるので、意外と勝ちを拾えます。

グッズ

クイックボール/ハイパーボール 4枚/4枚

序盤の理想ムーブの実現に必要なポケモンが多いので4枚ずつの採用です。

ハイパーボールはルギアVstarが立ってアッセンブルスターした後は1枚多く山札圧縮できるぐらいのメリットでしかないので、中盤以降はバトルコンプレッサーで落としがちです。クイックボールはなんだかんだ残しておきたい展開が多い印象。

バトルコンプレッサー 4枚

序盤にアーケオスやバトルサーチャー経由で利用したいサポートを落とす、中盤以降は山札の圧のための利用します。序盤に安定してアクセスできる手段が少ないのが残念ポイント。ここが解消されるまで4枚から減らすことはないでしょう。

スペシャルチャージ 4枚

博士で落ちる分のケアや、ガブリアスやガブギラを安定して運用できるという面から4枚採用しています。序盤でデデチェンジ打つことが確定している時のクイックボールのコストにしがち。

フィールドブロアー 2枚

グッズロックされなければダストダスによる特性ロックを解除する手段になります。レジドラゴからグッズロックされたら気合いでキリンリキを用意してオンバーンGXをロストに送るか(盤面に余裕がありグッズロックされる流れになりそうならあらかじめ出しておくのも手かも)、運良く手札にあるザオボーを使います。それ以外だと、勝ち切る盤面で自分が貼ったサイレントラボを剥がすぐらいでしか出番がない(雪道ミュウのようなデッキとあまり対面しない)印象があります。もちろん雑にかるいし剥がせるのも強い。

レスキュータンカ 1枚

ガブリアスやデデンネテテフの再利用はもちろん、バトルサーチャーのポケモン版としての運用(バトコンで落としてから拾う)もできます。便利。

パソコン通信 1枚

説明不要のエーススペック枠。と書いていたものの、シークレットボックスの登場で話は変わってきたかもしれません。ハンド3枚切るのは結構しんどいデッキではあるので、今のところパソコン通信のままにしています。

バトルサーチャー 3枚

サイド落ちの枚数をちゃんと把握しておかないと痛い目を見ます。対面のデッキからどのサポートをいつ何回使いたいかをある程度ゲームプランとして思い描いておくと無駄使いを防げます。エクストラは座学も大事です。

Stall系のデッキ相手に対して入れ替え手段をグズマ以外に持っていないため、グズマをロストに送られないようにすぐサーチャーで回収する展開も覚えておくと役に立つかもしれません。

サポート

博士の研究 2枚

グッズロックされた際の動きが極端に鈍くならないように多投(少なくとも2枚)しておきたいです。モクナシ(ラフレシア)はあんまり見ないけど、オンバーンGXのディストーション・クワガノンVのパラライズボルトは意識したいです。

ゼイユという先行1ターン目に使える神サポートがやってきたので、博士かマリィを1枚ゼイユに置き換えても良いかもしれません。

マリィ 1枚

運悪く後攻アッセンブルセットが揃わなかった時にこれでお茶を濁しています。スタジアムに妨害系のサイレントラボを採用しているのと相性が良い。

ナンジャモ 1枚

Nとナンジャモの選択ですが、ナンジャモにしました。相手が手札に溜まったままの不要なカードを再度引くのを狙うより、自分が勝ちに行くためのパーツを引くことの方が相対的に大事なデッキかなあと考えたからです。

ミツル 1枚

2枚採用にしている人も多いかと思いますが、サイドに埋まっていてもなんとかなるデッキでしょの精神で1枚にしています。あとミツルSRを2枚揃える余裕がないです。

ザオボー 1枚

ダストダスのかるいしを剥がす、ダブルドラゴンエネルギーやダブル無色エネルギーに頼っているデッキのエネルギーテンポを失速させる、などの使い道があります。

グズマ 1枚

高級ポケモン入れ替え。グズマの運用については他のカードの解説で結構触れた気がするのでここでは特に書きません。

スタジアム

サイレントラボ 3枚

ミライドン系統に貼ってグズマハラがないことを祈ったり、テツノイバラexの特性を止めたり、カプ・テテフなどを使わせないようにしたり。エクストラバトルではうらこうさくリザードンに当たる率が高いなと感じており、うらこうさく軸デッキには全く刺さらないのが残念なところ。もはやスカイフィールド型にしても良いのですが、ウソッキーを採用しているデッキもそれなりに見かけるので現状はサイレントラボにしています。

エネルギー

ダブルドラゴンエネルギー 4枚

ジージーエンドラッキーWINを見ている以上ここが3枚になることはありません。

ダブル無色エネルギー 3枚

ずっと4枚でやっていたのですが、後述するエネルギーを採用したくなり1枚削りました。まあルギアで最後まで殴り続けることはあんまりないのでそこまで困らないです。

パワフル無色エネルギー 1枚

テツノカイナex・トドロクツキex・テツノイバラexなどの、HP230・240ライン(ルギアVstarで取れない)でかつ放置できないポケモンがエクストラに増えてきたので1枚だけ入れました。ただ、これを入れるよりはカイザータックルのコライドンexを入れた方が見れる範囲が広がって良いと思います。

ミストエネルギー 2枚

エヴォコンのエネルギー破壊(2枚つければザオボー打たれても耐える)やミラーのジージーエンドでアーケオス2枚飛ばされる動きを意識して2枚採用しました。

レシピには入ってないけど採用したいカード

採用したい理由などは各カード解説で触れたので、ここではリストアップに留めます。

とても採用したい

  • ハバタクカミ(特性:あんやのはばたき)
  • コライドンex(ワザ:カイザータックル)
  • キュレム(ワザ:トライフロスト)

あってもいいかも

  • ゼイユ
  • ネオラントV
  • プリズムエネルギー
  • シークレットボックス

OpenTelemetry MetricsのUnitに何を記述すべきか

OpenTelemetryにおいて、OTel CollectorのReceiverを開発するときやアプリケーションに計装をする際、MetricsのUnit(単位)にどんな文字列を指定すべきかよく分からなかったので調べてみました。

semconv

OpenTelemetryにはSemantic Conventions(semconv)と呼ばれる規約があります。あるデータに対して異なる実装間で共通の名前がついていると便利なので標準を取り決めましょうといった趣旨のものです。Metricsにおいてはメトリック名や属性、単位といったデータについて規約が定められています。

opentelemetry.io

semconvには一般的な規約と具体的な規約があります。具体というと、例えばHTTPに関する規約、AWSなどのクラウドサービスに関する規約、といった形です。「HTTPサーバのリクエストの継続時間を計装する際にはhttp.server.request.durationという名前にして、属性としてHTTPメソッドを必ず付与し、その属性名はhttp.request.methodにしましょう。またそのメトリックの単位はsとしましょう」といった内容がずらずらと並んでいる形です。

opentelemetry.io

一方で、一般的な規約というのは、具体的な規約を策定する際に指針となる、具体ケースに依存しない規約になります。この規約の単位に関する記述を読めば、今回の謎は解決しそうです。ということで該当箇所を読んでみると以下のような記述があります。

https://opentelemetry.io/docs/specs/semconv/general/metrics/#instrument-units

Units should follow the Unified Code for Units of Measure.

単位はUCUMと呼ばれる別のルールに従うべきだと書かれています。さて、新しい概念が出てきたので今度はそちらを追っていきましょう。

UCUM

UCUMは、数量とその単位を電子的な手段で正確に伝達するために生まれた符号システムです。先ほどの引用上のリンクにアクセスするとその仕様を閲覧することができますが、長大で理解するのが大変なので、簡単にここで紹介します。

電子的な伝達を目的としているため、UCUMにおいて単位はUS-ASCII文字だけで記述されます。残念ながら日本語文字や絵文字は使えません。

SI単位系のように原子単位とその組み合わせ(次元)で表現します。アノテーションといって、個数などの無次元の単位に意味を付与できる仕組みがあるのも特徴です。参考までに、Mackerelで現在使える単位をUCUMで表現してみました。

Mackerelの単位 OpenTelemetry(UCUM)の単位表記例
float 1
integer {request}
percentage %
seconds s
milliseconds ms
bytes By
bytes/sec By/s
bits/sec bit/s
iops {operation}/s

semconvで/sを見かけない件

ところで、/sとつくような単位はsemconvで見かけることがありません。従来IOPSとして監視していたようなメトリックも、OpenTelemetryのレイヤーではあくまでI/O Operationの数として扱う(単位は{operation})雰囲気になっています。

ある時間についての割合というのは計測値そのものではなく計算した結果であり、OpenTelemetryとしては生の計測値をできる限りそのまま取り扱おうという指針なのでしょう。SDKやCollector上で行われる集計(aggregation)を除いて、投稿したメトリックをユーザが扱いやすいように加工するのはExportする先のサービスの責務ということになります。OpenTelemetryの責任範囲(Backendに投稿するまで)やObservability(集計によってデータの解像度は不可逆に低下し、結果として可観測性も失われる)を鑑みると自然な価値観であると言えます。

監視サービスの作り手として、投稿したメトリックを自在に引き便利に取り扱うための、Observability Backendの機能設計や実装の難しさを日々痛感しています。

カンファレンス登壇者・スタッフにこそ知ってほしいマイクの使い方

オフラインの技術カンファレンス・イベントも徐々に復活し賑わいを見せつつある今日この頃、いかがお過ごしでしょうか。

多くの聴衆に等しく声を届けるための道具「マイク」はカンファレンスに欠かせないものとなっています。

普段はアプリケーションエンジニアとして働きつつ、休日にPAエンジニアやステージマネージャーをやっている身から、来場者が発表やコンテンツに集中できるようなマイクの扱い方を簡単にご紹介します。

叩かない

マイクを叩くと低い「ブォンブォン」という不愉快な音が発生するのはもちろん、スピーカーなどの機材が壊れる原因につながります。もちろん意図的に叩いている人はいないと思うのですが、以下のようなケースでつい叩いてしまう人を見かけます:

  • 拍手をする、拍手を煽る際にマイクを持ちながら手を叩く
  • マイクがONになっているか確認するために叩く

マイクを手に持っている際に拍手をする際には、手と手の代わりに前腕部を叩いて、マイクへの衝撃を軽減し風音が入らないようにするといった工夫ができます。マイクのスイッチを一瞬切ったりミュートにしたりするのも良いのですが、音響業者が準備している際には、利用者が勝手にスイッチを切る行為が推奨されないことがあるので確認しましょう*1

マイクがONになっているか調べるには、大人しく発声するのが一番です。またカンファレンススタッフ目線では、登壇者ときちんとコミュニケーションを取り、「こちらで登壇者紹介をした後に勝手にマイクがONになります」などと伝えるのも効果的です。

声の出る方向とマイクの位置・向きを合わせる

カンファレンスでの発表時に使うマイクには、多くの場合単一指向性と呼ばれる特性のマイクが選ばれます。これは、特定の方向の音だけを捉えやすいという性質です。発表者が喋りその他の人が聞くというスタイルが多いカンファレンスで、発表者の声だけを拾い多くの聴衆に届けるのにピッタリのマイクです。

そのため、マイクの軸から口が左右にずれてしまうと、一気に音量が小さくなってしまいます。マイクのグリル(先端の丸いあみあみの部分)の方向の延長に自分の口があるかを確認しましょう。また、位置だけでなく向きも重要です。手でマイクを持つ場合には声の出る方向に合わせてマイクの向きを調整しましょう。

小さな卓上スタンドにマイクを立てている形式の場合、立ちながら話している登壇者の口の位置に対してマイクが極端に低く、結果として音を拾えていないケースがよくあります。

マイクスタンドには高さと向きが調整できる機能が備わっているので、登壇前の時間に以下の動画のように、自分の身長や姿勢に合わせて調整しましょう。

www.youtube.com

グリル部分を掴まない

マイクのグリル(先端の丸いあみあみの部分)の中には、マイクが実際に収音し電気信号に変えるための部品が入っています。

この部分を握ってしまうと、音を拾えなかったり、声がこもってしまう原因になります。グリルのすぐ下の部分を持つと良いでしょう。

ちょうどこの前の勉強会での自分のマイクの持ち方の例があったので載せておきます

ある程度の声量で喋る

マイクの向き・位置・持ち方が良くても、肝心の声が小さすぎると多くの聴衆に聞きやすく発表を届けることはできません。はっきりと大きな声で話せるようにしておきましょう。むしろ、元々声が小さい人ほど、先ほど紹介した方法でなんとかマイクに伝わる信号の大きさを稼ぐべきです。

さて、これまで紹介してきた内容のうち、

  • 声の出る方向とマイクの位置・向きを合わせる
  • グリル部分を掴まない
  • ある程度の声量で喋る

これら3つはどれも、マイクが音をちゃんと拾って大きくクリアな音で聴衆に声を届けるための方法となります。しかし、マイクを通した声のボリュームを上げる方法が違った軸でもう1つあります。それは、スタッフがミキサーやアンプでボリュームを上げるという方法です。なぜこれではいけないのでしょうか。

マイクがとらえた信号を増幅させる度合いをどんどん高めていくと、発表者の声だけでなく今まで拾えていなかったような環境音などもマイクが拾って、増幅されてスピーカーから流れます。ここで、スピーカーから流れた音が再度マイクに拾われるとどうなるでしょうか。音がどんどん増幅されて「キーン」という大きな不快な音が発生してしまいます。ハウリングというやつですね。

適切なマイクの使い方をすることで、過度に音の信号を増幅しなくて済むようになり、結果としてハウリングを防ぐことができるのです。


せっかくのいい発表が聴衆に届かない、あるいは雑音が聞こえて集中できないのはもったいないことだと思っています。よろしければ以上の内容を意識して、より伝わりやすい発表にしてみてはいかがでしょうか。

*1:業者の責任範囲外で音が出なくなるのを防ぐためです。こういった現場では、PAエンジニアがマイクを叩くのを察知してシュッとボリュームを下げてくれることも多いですが、人間の手作業によるものですのでそこに頼りまくるのも良くないです。

登壇ふりかえり:Hatena Engineer Seminar 個人開発回 & Scalaわいわい勉強会 #2

2024年に入って2件登壇しましたので、こちらのエントリで振り返っていきます。

Hatena Engineer Seminar #28 「技術習得を支え続けた私の個人開発ヒストリー」

2024年1月30日に、「個人開発」をテーマとしたHatena Engineer Seminar #28にて登壇しました。

developer.hatenastaff.com

学生の頃からずっと続けてきた個人開発を通して、モノづくりを続けながら扱える技術を次々に増やしていった話と、最近の個人開発で自分の責務を減らすことを目的に進めているモダナイズの話をしました。さらに、企画・開発の両面にフルコミットする個人開発の経験がプロダクトエンジニアと呼ばれつつあるエンジニアの一つのあり方でいることに深く貢献している、だとか、個人開発がエンジニアリングの美学を学ぶ庭になる、というメッセージも伝えました。

話の構成として、自分のこれまでの個人開発の歴史や当時の自分・社会の状況を振り返りながら進めていき、その上で一般化した結論を最後に提示していきました。これに関して社内でも「キーノートっぽい」などという感想をいただいたのですが、まさに狙った効果でした。YAPC::Kyoto 2023の id:onishi さんのキーノートにだいぶ影響されてますね。

あと、発表の最後にMackerel DrinkUpというオフラインイベントの宣伝をしたのですが、これを見て来ましたと言ってくれた方がいて嬉しかったです。

動画はこちら:

www.youtube.com

資料はこちら:

speakerdeck.com

Scalaわいわい勉強会 #2「見せ算をScalaで実装してみた」

Scalaわいわい勉強会 #2にてLTをしました。前回のScalaわいわい勉強会でもLTをさせてもらいました。

scala-tokyo.connpass.com

今回は、M-1 2023の決勝で物議を醸したさや香の見せ算をScalaで実装したという話をしました。

この漫才を見た瞬間から、これScalaで実装しようと思っていました。同じようにプログラミングで実装したいと思った人はいたらしく、Pythonなどで実装した記事がいくつか上がっていますね。でもなぜ僕はScalaを選んだのかというと、以下の画像をご覧ください。

あたかも四則演算に加えてmiseという演算子が本当にあるかのように見えるではありませんか。これは5則目が追加されたという事実の表現として結構大事なことだと思うんですよね。Scalaでは演算子を独自に作成できるので、それを実現する2つの言語仕様を紹介しました。

これだけですとかなり初心者向けの内容になるので、来場者に楽しんで聞いてもらえるかというのがネックではありました。しかし、Scalaわいわい勉強会は初級者から上級者まで楽しめるようにと企画されているものですので、そこは割り切ることにしました。では実際どうだったかというと、以下の記事のように初心者の方もいらしていたようで、反応を見るにビギナー向けのトークができて良かったと強く思いました。

a5size.hatenablog.com

中級者向けにはScala 3でのアップデートに触れる・型レベルプログラミングでの実装を紹介するなどして、うまくバランスが取れたかなあと思っています。ただしScalaの人はM-1を見ないらしい。

資料はこちら:

speakerdeck.com


次の予定は特に決まっていないのですが、Go Conference 2024にはProposalを出す予定です。よろしくお願いします。

あんまり元気じゃないです

今更ですが2月中旬に新型コロナに罹って丸々1週間ほど寝込んでいました。高熱(39度以上が3日間)、咽頭痛、味覚・嗅覚障害あたりが主な症状でした。高熱で何も頭が働かないのが一番しんどかったです。テイクアウトで近所の美味しいラーメンを頼んだら、スープが塩水としか感じられなかったのはおもしろ体験でした。

実はこの期間にいろんな予定を入れてしまっていて、仕事のお休み以外にもいろいろご迷惑をおかけしました。そのうちの1つが従姉妹の結婚式でした。その人にとって人生で1回しかないであろうイベントに参加できなかったのはかなりショックだったのと、いろいろな予定の再調整がしんどくて、身体より精神的苦痛の方が大きかったです。SNSもしばらく見たり書いたりしないようにしていました。

そんな状況かつ、しばらく家に引きこもって誰とも交流していなかったこともあってか、家の外にいるだけで動悸や冷や汗がひどく、あんまりまともに行動できてないです。特に電車ではノイズキャンセリングイヤホンがあっても厳しくて、とにかく端っこにいないと落ち着かないです。見た目は若者なのでシートに座るのも躊躇われる故、居場所がないです。

最近は多少症状がマシになってきたかと思ってポケモンカードの店舗大会に出てみたんですが、手が震えてシャッフルに困りました。よそから見たらめちゃくちゃ挙動不審な感じだったと思います。ただ戦績はびっくりするほど良かったので不思議なものですね。

ということでフットワークの軽さだけが取り柄でしたが、しばらく外出の予定は控えめにさせてもらいます。少しずつリハビリしていければと思うのでよろしくお願いします。

と言ったところでこんな話をするのもアレですが、明日はScalaわいわい勉強会 #2(すでに満席です)のLTで物理登壇予定です。面白い話になるように絶賛仕込み中です。

scala-tokyo.connpass.com