【Golang】Unicode上複数コードからなる文字をruneで扱う場合の挙動
検証環境
Mac OS Catalina 10.15.6
Go1.15.6
tl;dr
Unicode上でn個のコードからなる1文字を []rune
に変換すると、[]rune
スライス長は n
になります。
前置き
Goにおける文字列型 string
の値は、 []byte
スライスの値としても扱うことができます。
例えば以下のように string
を []byte
にキャスト可能な他、
s := "こんにちは、Golang" b := []byte(s) // []byte配列用にmemory allocate され、バイト列もcopyされる
string
の値に要素アクセスすると対応する位置の byte
値を取得します。
s := "こんにちは、Golang" b := s[0] // 227
文字列の長さを len
関数で取得すると、 []byte
スライスとしての長さが返ってきます。
s := "こんにちは、Golang" length := len(s) // 24
しかし実際には、本来の文字列一文字一文字についてアクセスしたい場合が多いと思います。(例えば こんにちは、Golang
を12文字の「意味のある文字の配列」として扱いたい、など)
Golangでは rune
型を使用し、この問題を解消します。
rune
文字列を []byte
スライスとして扱う場合、それは元の各文字に割り当てられたUnicode(ISO 10646)をそれぞれUTF-8 encodingに従いバイト列に変換したものになります。
s := "こんにちは、Golang" b := s[0] fmt.Println(b) // -> 227 fmt.Printf("%v\n", []byte("こ")) // -> [227 129 147] fmt.Printf("%+q\n", "こ") // -> "\u3053" (Unicode) fmt.Printf("%q\n", "こ") // -> "12371" (Unicode code point)
Unicodeの単位で文字列の各文字にアクセスするには、文字列を []rune
スライスにキャストします。
s := "こんにちは、Golang" r := []rune(s) fmt.Println(r[0]) // -> 12371 fmt.Println(string(r[0])) // -> こ
あるいは for
で文字列に対して range
による要素アクセスを実行すると、文字列要素に rune
単位でアクセスすることができます。
s := "こんにちは、Golang" for _, r := range s { fmt.Print(string(r)) } // -> こんにちは、Golang
複数コードからなる文字をruneとして扱う場合
先程「Unicodeの単位」と述べましたが、Unicodeには複数のcodeで一つの文字を表現するパターンもあります。 tech.sanwasystem.com
例えば、日本語の漢字のうち微妙に表現が異なる漢字郡は「異体字セレクタ」として扱われます。異体字セレクタでは同じ意味の似た文字に対して同じコードを与え、さらにそれらを識別するためのコードも与えることで(計2つ)、一意な文字を表現します。
以下の例は2つのcodeからなる漢字 邊󠄆
( U+908A U+E0106
)を []rune
に変換したものです。この漢字、右上の「自」が「白」になっているパターンの「わた」なんですがブログ上の表記ではわからないかもです(上記文献を参照)。
wata := "邊󠄄" rWata := []rune(wata) fmt.Printf("%+q\n", rWata) // -> ['\u908a' '\U000e0104'] fmt.Println(len(rWata)) // -> 2
出力例のように、異体字セレクタ文字列を []rune
に変換すると。長さ2となることがわかります。
fmt.Println(rWata[0]) // 37002 fmt.Println(string(rWata[0])) // -> 邊󠄄 fmt.Println(string(rWata[1])) // -> (空文字) fmt.Printf("%+q\n",rWata[0]) // -> '\u908a'
先頭の文字を出力すると、ベースとなる文字が出現します。
このように文字列を rune
で扱う場合も、Unicode上で複数codeからなる文字を扱う場合には想定する文字列長と異なってしまうので注意が必要です。
参考文献
自前のブログからはてなブログに戻しました
あけましておめでとうございます。
2020年初めにブログを(半)自前運用1に移行したのですが、諸々面倒だったのでブログをはてなに戻すことにしました。
自前運用ブログの記事もこちらに移しました。
ついでにデザインテーマも変更しました。
自前運用のブログで面倒だったところ
以下面倒だった点です。
ちなみにブログはHugoでブログテンプレートを作り、Netlifyにホスティングする形式をとっていました。
画像挿入をMarkdownに記述+指定ディレクトリに置かなければいけない
Hugoにはブログ記事を編集するGUIもありましたが、はてブのほうがストレスが少なかったです。
プレビューが実際の内容と一致しない
エディタでmarkdownプレビュー見ても実際どう描画されるか不明なため、結局何回もdeployしてました。
ローカルで記事見るにもテンプレート使ったbuildが必要でした。
デプロイがたまにコケる
これはしょうがないです。Netlifyも無償枠だったので。。。
他マシン環境で執筆環境を作るのが少し面倒
ブログ用のリポジトリをクローンして、書いて、pushして・・・と、ちょっと面倒でした。
といったところです。見事にSaaSを使わないことによるデメリットを享受しています。
運用する前に上記面倒のいくつかは想定はしていたし、当初は自前ブログやってみるぞ意欲があったのでそのノリで初めてはみたのですが、思ったよりしんどいなと思ったので戻そうと思いました。
HugoとNetlify自体は良いツール・サービスでした。Hugoはブログテンプレートを多くの有志が作成した中から無料で使えるし、Netlifyは無償でホストを提供してくれる上にGithubと連携して勝手にCIしてくれます。それでも自前運用自体に上位のような手間があったので、やっぱりブログを細かくカスタマイズしたり、フロントを完全に自前で作りたい人などが自前運用に適しているのかもしれません。
今後
幸いにもはてなの方の拙ブログ(?)を今も読んでくださっている方がいらっしゃるようで、平日の多いときには400viewくらいにはなっています。
一年も放置したんだから流石にがっつり減るだろうと思っていたのですが、思ったよりview数の減りが少なくてびっくりしました(100くらいになると思ってた)。
ちなみに2年前に毎日ブログ更新していたときは平日概ね500-600viewくらいでした。
自分はブログをあくまで自分の学びのきっかけや備忘録のために書く多いのですが、それでも多くの人の役に立つような記事を残せたらなあと思う次第です。
ということで2021年もポツポツと記事を書いていくと思いますが、今年もどうぞよろしくお願いします。
【OpenAPI】PrismでOpenAPIドキュメントからモックサーバーを起動する
Prism
Stoplight 社が提供する1OpenAPI ドキュメントからモックサーバーを起動するツール。format は OpenAPI2/3 をサポートする。
Usage
Install
Prism は node 上で実行されるため、node 実行環境を用意してインストールする。
Docker
node サーバーコンテナを立ててその中にインストールする例( 一応 Prism コンテナイメージをそのまま起動することはできる)
FROM node:14.14 WORKDIR /openapi COPY ./openapi.yml . # install stoplight/prism RUN npm install -g @stoplight/prism-cli # exec - specify host as 0.0.0.0 to connect from docker host. CMD ["prism", "mock", "openapi.yml", "--host", "0.0.0.0"]
Prism インストール後、 prism mock [openapi doc]
でモックサーバーを起動する。
OpenAPI ドキュメント上で定義されたパスのうち、Path Parameter の指定はランダムに変化する。
$ docker build -t rennnosuke/prism-test . $ docker run -it -p 4010:4010 rennnosuke/prism-test [10:57:16 AM] › [CLI] … awaiting Starting Prism… [10:57:16 AM] › [CLI] ℹ info POST http://0.0.0.0:4010/pet [10:57:16 AM] › [CLI] ℹ info PUT http://0.0.0.0:4010/pet [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/pet/findByStatus?status=pending,sold,available,available,sold,sold,pending,sold,pending,available,sold,pending,sold,available,available,available,pending,sold,available,pending [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/pet/findByTags?tags=doloribus,consequuntur,expedita,tempora,temporibus,nemo,adipisci,molestiae,reprehenderit,voluptatibus,laboriosam,sed,pariatur,culpa,dicta,illum,qui,repellat,adipisci,incidunt [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/pet/762 [10:57:16 AM] › [CLI] ℹ info POST http://0.0.0.0:4010/pet/447 [10:57:16 AM] › [CLI] ℹ info DELETE http://0.0.0.0:4010/pet/951 [10:57:16 AM] › [CLI] ℹ info POST http://0.0.0.0:4010/pet/576/uploadImage [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/store/inventory [10:57:16 AM] › [CLI] ℹ info POST http://0.0.0.0:4010/store/order [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/store/order/4 [10:57:16 AM] › [CLI] ℹ info DELETE http://0.0.0.0:4010/store/order/141 [10:57:16 AM] › [CLI] ℹ info POST http://0.0.0.0:4010/user [10:57:16 AM] › [CLI] ℹ info POST http://0.0.0.0:4010/user/createWithArray [10:57:16 AM] › [CLI] ℹ info POST http://0.0.0.0:4010/user/createWithList [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/user/login?username=non&password=provident [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/user/logout [10:57:16 AM] › [CLI] ℹ info GET http://0.0.0.0:4010/user/placeat [10:57:16 AM] › [CLI] ℹ info PUT http://0.0.0.0:4010/user/qui [10:57:16 AM] › [CLI] ℹ info DELETE http://0.0.0.0:4010/user/ex [10:57:16 AM] › [CLI] ▶ start Prism is listening on http://0.0.0.0:4010
$ curl -XGET -s -D "/dev/stderr" -H "Content-type: application/json" http://0.0.0.0:4010/store/inventory HTTP/1.1 200 OK Access-Control-Allow-Origin: * Access-Control-Allow-Headers: * Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: * Content-type: application/json Content-Length: 29 Date: Sun, 18 Oct 2020 11:01:52 GMT Connection: keep-alive Keep-Alive: timeout=5 {"property1":0,"property2":0}
tips
動的レスポンス生成
Prism ではレスポンスの値を動的に生成できる。
x-faker
プロパティを Schema プロパティ上に定義することで、レスポンス中の値がランダム生成されるようになる。
指定方法は Faker.js
に従う。
(公式より引用)
Pet: type: object properties: id: type: integer format: int64 name: type: string x-faker: name.firstName example: doggie photoUrls: type: array items: type: string x-faker: image.imageUrl
$ curl http://127.0.0.1:4010/pets/123 -H "Prefer: dynamic=true" { "id": 12608726, "name": "Addison", "photoUrls": [ "http://lorempixel.com/640/480", "http://lorempixel.com/640/480", "http://lorempixel.com/640/480", "http://lorempixel.com/640/480" ] }
Definition Engine
Prism モックサーバーがリクエストを受け取ったときのレスポンス決定ルール。 Mock をより精密に動作させるために、また OpenAPI ドキュメントの項目を充実するために使える。
参考文献
-
OpenAPI ドキュメントの編集ツールstoplight studioも便利↩
【AWS】IAMユーザーにAWS上のリソースに対するSSL/TLS通信を強制する
メモ。
特定のユーサーに AWS 上のリソースへの操作権限を与えたいが、操作のためのリクエストは SSL/TLS 通信に限定したい場合がある。例えば、S3 への API 経由のファイルアップロードなどをユーザーに許可する際、それを HTTPS に限定したいときなどがある。
SSL/TLS 通信制約は、IAM ポリシーに下記のような条件 Condition
を追加することで達成できる。
{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "iam:*AccessKey*", "Resource": "arn:aws:iam::ACCOUNT-ID-WITHOUT-HYPHENS:user/*", "Condition": { "Bool": { "aws:SecureTransport": "true" } } } }
Condition.Bool["aws:SecureTransport"]
プロパティを true
にすると、ユーザーからの暗号化されていないプレーンな HTTP によるアクセスが拒否される。
参考文献
【Golang】Goで形態素解析する - mecab-golang
MeCab とは
MeCab: Yet Another Part-of-Speech and Morphological Analyzer
自然言語処理界隈では ChaSen と並び有名な形態素解析エンジン。
形態素解析とは、テキストデータを文法や単語の品詞情報(辞書)を元に言語の最小単位(形態素)へと分割し、各形態素の品詞などを判別すること。
ex. 裾野は長し赤城山
裾野 名詞,一般,*,*,*,*,裾野,スソノ,スソノ は 助詞,係助詞,*,*,*,*,は,ハ,ワ 長し 形容詞,自立,*,*,形容詞・アウオ段,文語基本形,長い,ナガシ,ナガシ 赤城山 名詞,固有名詞,地域,一般,*,*,赤城山,アカギヤマ,アカギヤマ
mecab-golang
Go から mecab を使用するライブラリ。mecab
の go wrapper。
なので使用する場合は mecab-golang
とは別に、MeCab や辞書をインストールする必要がある。
Install
Mecab のインストール
mecab
をインストール。
mecab
を扱うには、MeCab が形態素解析に使用する辞書が別途必要になる。
mecab-ipadic
はその MeCab 用の辞書の一つであり、日本語の単語情報(読み方や品詞など)を掲載している。1
$ brew install mecab $ brew install mecab-ipadic
mecab-ipadic-NEologd
のインストール
Web 上から逐一新語を登録してくれる辞書 mecab-ipanic では判断できない新語を(全てではないが)サポートする。 ↓ こんなかんじに、例えば名詞をより細かい品詞に分割して解釈してくれる。
default system dictionary | mecab-ipadic-NEologd ピット 星 人 | ピット 星人 この 世界 の 片隅 に | この世界の片隅に 辛 坊 | 辛坊 東海大 市原 望 洋 | 東海大市原望洋 方言 ラジオ 体操 | 方言 ラジオ体操 コイ キング | コイキング はだし の ゲン | はだしのゲン あさ パラ | あさパラ 雪 組 | 雪組
-n
で最新 ver 辞書をインストール
$ git clone git@github.com:neologd/mecab-ipadic-neologd.git $ cd mecab-ipadic-neologd $ ./bin/install-mecab-ipadic-neologd -n
mecab-golang
のインストール
$ export CGO_LDFLAGS="-L/{libフォルダへのパス}/lib -lmecab -lstdc++" $ export CGO_CFLAGS="-I/{includeフォルダへのパス}/include" $ go get github.com/bluele/mecab-golang
Usage
bluele/mecab-golang)サンプルコードを参考にした。
package main import ( "fmt" "strings" "github.com/bluele/mecab-golang" ) const BOSEOS = "BOS/EOS" func parseToNode(m *mecab.MeCab, text string) error { tg, err := m.NewTagger() if err != nil { return err } defer tg.Destroy() lt, err := m.NewLattice(text) if err != nil { return err } defer lt.Destroy() node := tg.ParseToNode(lt) for { features := strings.Split(node.Feature(), ",") if features[0] != BOSEOS { fmt.Printf("%s %s\n",node.Surface(), node.Feature()) } if node.Next() != nil { break } } return nil } func main() { m, err := mecab.New("-Owakati") if err != nil { panic(err) } defer m.Destroy() err = parseToNode(m, "すもももももももものうち") if err != nil { panic(err) } }
解説
func main()
mecab.New()
で MeCab モデルオブジェクトを生成する。
引数には元々の MeCab 実行バイナリの引数に指定できるものを渡すことができる。ここでは形態素解析の結果(後述の Tagger::Parse
で取得)を分かち書きで出力させるオプション -Owakati
を指定している。
他のオプションはここを参照。
parseToNode()
は第 2 引数にとる文字列を形態素解析・その結果を標準出力へ書き込む。
func parseToNode()
引数に取る MeCab
モデルオブジェクトと文字列から、形態素解析の結果を出力する。
以下で説明する関数やメソッドは MeCab c++ ライブラリ準拠のもののため、MeCab 本家の C/C++ライブラリを読めば問題なさそう。2
MeCab::NewTagger()
辞書オブジェクト Tagger
を生成する。
MeCab::NewLattice()
解析に必要なローカル変数を含むオブジェクト Lattice
を生成する。
生成時に解析対象となる文字列を渡す。
Tagger::ParseToNode()
Lattice
を引数に取ることで、 Lattice
が保持する文字列を解析し、木構造のオブジェクト Node
として解析結果を返す。
ちなみに parseToNode()
内では BOS/EOS( BOS は beginning of sentence で文頭、EOS は end of sentence で文末)
品詞タグのついた形態素情報以外を出力するようにしている。
Node::Feature()
Node
の解析結果を出力する。
例えば分かち書き指定の場合こんな感じ。
名詞,一般,*,*,*,*,すもも,スモモ,スモモ
Node::Surface()
単語の品詞を出力する。
実行結果
すもも 名詞,一般,*,*,*,*,すもも,スモモ,スモモ も 助詞,係助詞,*,*,*,*,も,モ,モ もも 名詞,一般,*,*,*,*,もも,モモ,モモ も 助詞,係助詞,*,*,*,*,も,モ,モ もも 名詞,一般,*,*,*,*,もも,モモ,モモ の 助詞,連体化,*,*,*,*,の,ノ,ノ うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
独自フォーマットで形態素解析の結果を取得する
Tagger::ParseToNode
で取得する Node
からは特定のフォーマットで結果が出力できるが、 mecab.New()
でフォーマットを指定することで、 Tagger::Parse
の文字列結果を変更することができる。
分かち書き
m, err := mecab.New("-Owakati" )
すもも も もも も もも の うち
フォーマット
-F
オプションを使用すると、下記の出力フォーマットを使用して自由に出力を切り替えられる。
出力フォーマット
ex.
m, err := mecab.New("-F%m(素性ID:%h)\\n")
すもも(素性ID:38) も(素性ID:16) もも(素性ID:38) も(素性ID:16) もも(素性ID:38) の(素性ID:24) うち(素性ID:66) EOS
参考文献
MeCab: Yet Another Part-of-Speech and Morphological Analyzer
mecab-ipadic-neologd/README.ja.md at master · neologd/mecab-ipadic-neologd
MeCab: Yet Another Japanese Dependency Structure Analyzer
bluele/mecab-golang: A golang wrapper for mecab.
IPADIC(IPA 辞書)とはなにものか? - ぱらめでぃうす
-
元々は別の形態素解析エンジンである ChaSen に使用された辞書を、MeCab 用に更に変更を加えたもの。ipa とついているのは、その品詞フォーマットが情報処理振興事業協会(IPA)で設定された IPA 品詞体系(THiMCO97)に基づいているため。参考:http://parame.mwj.jp/blog/0209↩
-
この C/C++ライブラリドキュメントでは Tagger によるシングルスレッド環境向けサンプルと Tagger/Model(NewMecab 返戻値型に対応)/Lattice によるマルチスレッド環境向けサンプルがある。
mecab-golang
では Go を使用する以上、goroutine によるマルチスレッド環境の可能性を前提にライブラリが組まれていると思われ、そのため Tagger::Parse/ParseToNode は Lattice を引数に取っている。↩
【Golang】Golang:logパッケージを読む
log
パッケージを読みました。
src/log/log.go - The Go Programming Language
log
Go の log
はロギング処理に使用するパッケージ。
log
パッケージはテストコードを除けば log.go
のみで構成されており、log.go
も 400 数行からなるシンプルな構成となっている。
( syslog
が log
パッケージ配下に置かれているが、それとは分けて話す )
log
パッケージ概要
// Package log implements a simple logging package. It defines a type, Logger, // with methods for formatting output. It also has a predefined 'standard' // Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and // Panic[f|ln], which are easier to use than creating a Logger manually. // That logger writes to standard error and prints the date and time // of each logged message. // Every log message is output on a separate line: if the message being // printed does not end in a newline, the logger will add one. // The Fatal functions call os.Exit(1) after writing the log message. // The Panic functions call panic after writing the log message. package log
- ロギングに使用する Logger 型と、それに紐づくメソッド(Formatter など)を定義している
- Logger を明示的に生成してロギングするか、ヘルパ=関数経由で標準 Logger によるロギングができる
- ヘルパー関数を使用すると、標準 Logger がログメッセージに日付・時刻を付加する
- Logger 経由のロギングは以下の特徴を持つ
- 1 ログにつき 1 行(カスタム Logger でなければ)
- Fatal 系関数はロギング後 os.Exit(1)を呼ぶ
- Panic 系関数はロギング後 panic を呼ぶ
Logger 構造体
// A Logger represents an active logging object that generates lines of // output to an io.Writer. Each logging operation makes a single call to // the Writer's Write method. A Logger can be used simultaneously from // multiple goroutines; it guarantees to serialize access to the Writer. type Logger struct { mu sync.Mutex // ensures atomic writes; protects the following fields prefix string // prefix on each line to identify the logger (but see Lmsgprefix) flag int // properties out io.Writer // destination for output buf []byte // for accumulating text to write }
Logger
構造体は log
パッケージが提供するロギング機能を実現するための構造体。
mu
:sync.Mutex
値。goroutine による並行処理上でも排他制御しつつロギングするために使用する。prefix
: ログ行の先頭に付与する文字列。ただし、Lmsgprefix
をflag
に指定するとメッセージの先頭に付与される。flag
: ログの接頭辞を制御するフラグ。指定できるフラグは以下。ビットフラグになっており、 OR 演算で結合して複数指定できる。 ex.Ldate | Lmicroseconds
out
:io.Writer
実装型の値。ログの出力先。buf
: ログ書き込み文字列のバッファ。
const ( Ldate = 1 << iota // ローカルタイムゾーンの日時を次のフォーマットで表示: 2009/01/23 Ltime // ローカルタイムゾーンの時刻を次のフォーマットで表示: 01:23:23 Lmicroseconds // ローカルタイムゾーンの時刻を次のフォーマットで表示: 01:23:23.123123. assumes Ltime. Llongfile // パス/ファイル名と行数を表示: /a/b/c/d.go:23 Lshortfile // ファイル名と行数を表示、Llongfileを上書きする: d.go:23. LUTC // Ldate, Ltimeが設定されていた場合、タイムゾーンをUTCとする Lmsgprefix // prefixをログ先頭ではなくメッセージ先頭に移す LstdFlags = Ldate | Ltime )
Logger を明示的に作成して使用する
log
パッケージには Logger 値参照を取得するヘルパー関数 New
が提供されている。
引数の詳細は上記参照。
// New creates a new Logger. The out variable sets the // destination to which log data will be written. // The prefix appears at the beginning of each generated log line, or // after the log header if the Lmsgprefix flag is provided. // The flag argument defines the logging properties. func New(out io.Writer, prefix string, flag int) *Logger { return &Logger{out: out, prefix: prefix, flag: flag} }
Logger public メソッド
l := log.New(os.Stdout, "prefix: ", log.Ldate | log.Lmicroseconds | log.LUTC) l.Println("Hello, Logger.")
Logger
が使用できる出力系関数は以下。
func (l *Logger) Printf(format string, v ...interface{}) func (l *Logger) Print(v ...interface{}) func (l *Logger) Println(v ...interface{}) func (l *Logger) Fatal(v ...interface{}) func (l *Logger) Fatalf(format string, v ...interface{}) func (l *Logger) Fatalln(v ...interface{}) func (l *Logger) Panic(v ...interface{}) func (l *Logger) Panicf(format string, v ...interface{}) func (l *Logger) Panicln(v ...interface{})
これらの関数全ては、内部で Logger::Output
関数を呼び出している。
Logger::Output
Logger::Output
は、各種出力系関数から呼ばれる汎用出力関数。
呼び出すごとに1行のログを出力する。
// Output writes the output for a logging event. The string s contains // the text to print after the prefix specified by the flags of the // Logger. A newline is appended if the last character of s is not // already a newline. Calldepth is used to recover the PC and is // provided for generality, although at the moment on all pre-defined // paths it will be 2. func (l *Logger) Output(calldepth int, s string) error { // ... }
Logger
の sync.Mutex
を使用して排他制御をしているため、ロギングはスレッドセーフ(goroutine セーフ?)。
l.mu.Lock() defer l.mu.Unlock() // ... _, err := l.out.Write(l.buf) return err
Logger.flag
に Lshortfile
Llongfile
が設定されている場合、ファイル名・行数を取得するため、ロックを外して runtime.Caller
を呼ぶ。
if l.flag&(Lshortfile|Llongfile) != 0 { // Release lock while getting caller info - it's expensive. l.mu.Unlock() var ok bool _, file, line, ok = runtime.Caller(calldepth) if !ok { file = "???" line = 0 } l.mu.Lock() }
Caller
は runtime
パッケージの関数で、ゴルーチンスタック中の関数呼び出し情報を取得できる。この関数呼び出しの情報の中にロギング時のファイル・行数情報が含まれていて、 Output
ではそれを使う。
package runtime import "runtime/internal/sys" // Caller reports file and line number information about function invocations on // the calling goroutine's stack. The argument skip is the number of stack frames // to ascend, with 0 identifying the caller of Caller. (For historical reasons the // meaning of skip differs between Caller and Callers.) The return values report the // program counter, file name, and line number within the file of the corresponding // call. The boolean ok is false if it was not possible to recover the information. func Caller(skip int) (pc uintptr, file string, line int, ok bool) { rpc := make([]uintptr, 1) n := callers(skip+1, rpc[:]) if n < 1 { return } frame, _ := CallersFrames(rpc).Next() return frame.PC, frame.File, frame.Line, frame.PC != 0 }
フラグ指定による接頭辞を付与するために、 formatHeader
を呼ぶ。
l.buf = l.buf[:0]
l.formatHeader(&l.buf, now, file, line)
formatHeader
は Logger.flags
ビットフラグに従い接頭辞を指定する。
接頭辞は以下の優先順位で付与される。
Logger.prefix
に指定した文字列- 日時・時刻
- ファイル名・行数
- (
Lmsgprefix
をLogger.prefix
に設定した場合 )Logger.prefix
に指定した文字列
// formatHeader writes log header to buf in following order: // * l.prefix (if it's not blank and Lmsgprefix is unset), // * date and/or time (if corresponding flags are provided), // * file and line number (if corresponding flags are provided), // * l.prefix (if it's not blank and Lmsgprefix is set). func (l *Logger) formatHeader(buf *[]byte, t time.Time, file string, line int) { // ... }
あとは Logger.out
に文字列を書き込む。
文字列末尾に改行が含まれていなければ追加する。
l.buf = append(l.buf, s...) if len(s) == 0 || s[len(s)-1] != '\n' { l.buf = append(l.buf, '\n') } _, err := l.out.Write(l.buf) return err
Logger をヘルパー関数経由で使用する
log
パッケージクライアントは Logger
型の値を生成しなくても、下記関数でロギング機能を使用することはできる。
func Printf(format string, v ...interface{}) func Print(v ...interface{}) func Println(v ...interface{}) func Fatal(v ...interface{}) func Fatalf(format string, v ...interface{}) func Fatalln(v ...interface{}) func Panic(v ...interface{}) func Panicf(format string, v ...interface{}) func Panicln(v ...interface{})
これらの関数は共通のローカル Logger
値である std
を使用する。
std
の出力先は os.Stderr
であり、接頭辞として日付・時刻がつく。
var std = New(os.Stderr, "", LstdFlags)
上記関数群は内部で std.Output
を呼び、ロギング処理を実行する。
std
経由で Logger
メソッドを呼び出さない理由は不明(ユースケース別にあえて分けた?)。
// Printf calls Output to print to the standard logger. // Arguments are handled in the manner of fmt.Printf. func Printf(format string, v ...interface{}) { std.Output(2, fmt.Sprintf(format, v...)) }
参考文献
【AWS】DynamoDB local でローカル上にAWS DynamoDB 環境を作る
DynamoDB local
AWS は、ローカル環境上で DynamoDB を使用したアプリケーションを開発・テストするための DynamoDB local
を提供している。
DynamoDB local
は以下の手順でデプロイ可能。
- DynamoDB local をローカルにインストールする
- Apache Maven リポジトリを追加して DynamoDB をデプロイする
- DynamoDB Docker イメージをインストールする
今回は Docker イメージをインストールして検証した。
Usage
Dynamo DB local
docker-compose
でコンテナをデプロイする。
docker-compose.yml
version: '3.7' services: dynamodb-local: image: amazon/dynamodb-local:latest container_name: dynamodb-local ports: - '8000:8000'
$ ls docker-compose.yml $ docker-compose up -d
これで Dynamo DB local
コンテナデプロイは完了。
ホスト OS にポート 8000
を開けているので、 http://localhost:8000/shell
をブラウザで開くと、ローカルの DynamoDB に対してコード経由で処理を実行できるエディタが開ける。
エディタは JS に対応している。
Code(JS)
AWS.config.endpoint = new AWS.Endpoint('http://localhost:8000'); let dynanodb = new AWS.DynamoDB(); let region = 'us-west-2'; AWS.config.update({ region: region, }); let tableName = 'Articles'; let log = (err, data) => { if (err) { console.log(err); } else { console.log(data); } }; // create table let createTable = () => { let tableParams = { TableName: tableName, KeySchema: [{ AttributeName: 'ID', KeyType: 'HASH' }], AttributeDefinitions: [{ AttributeName: 'ID', AttributeType: 'N' }], ProvisionedThroughput: { ReadCapacityUnits: 10, WriteCapacityUnits: 10, }, }; dynamodb.createTable(tableParams, log); }; // check table existence let checkTableParams = { TableName: tableName, }; dynamodb.describeTable(checkTableParams, function (err, data) { if (err) { createTable(); } }); // PUT Item let putParams = { TableName: tableName, Item: { ID: { N: '1' }, Title: { S: 'title' }, Content: { S: 'content' }, }, }; dynamodb.putItem(putParams, log); // GET Item let getParams = { TableName: tableName, Key: { ID: { N: '1' }, }, }; dynamodb.getItem(getParams, log);
=> {} {"Item":{"Title":{"S":"title"},"Content":{"S":"content"},"ID":{"N":"1"}}}