1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Go ログの一部をマスクする

Last updated at Posted at 2021-06-12

Go言語でログの一部をマスクしたい。

例えば ログの一部にパスワードが書かれているとき などに
別の文字に置き換えておきたい(マスクしたい)ときがあります。

変更前
[LOG] ID  : hiro
[LOG] PASS: password
[LOG] exec: hoge.exe --id hiro --pass password

↓ マスクする

変更後
[LOG] ID  : hiro
[LOG] PASS: ********
[LOG] exec: hoge.exe --id hiro --pass ********

そんなときは io.Writer をラップしたものを作るのがお手軽です。
また io.Writer 準拠で作っておくと 汎用性がある・テストしやすい のでオススメです。

io.Writerinterface です。
なので以下の 指定されたメソッドを実装してあげる 必要があります。

type Writer interface {
    Write(p []byte) (n int, err error)
}

https://golang.org/pkg/io/#Writer

ということでサクッと作ります。

実装

今回は マスクしたい文字列 を貯めておく実装にしてみました。

mask.go
package main

import (
	"io"
	"strings"
)

// MaskWriter : マスク用のWriter
type MaskWriter struct {
	writer     io.Writer // マスクした後に書き込む先
	keywords   []string  // マスク対象の文字列
	maskString string    // 置き替える文字
}

// NewMaskWriter : マスク用のWriterを作成する
func NewMaskWriter(w io.Writer) MaskWriter {
	m := MaskWriter{
		writer:     w,
		keywords:   []string{},
		maskString: "********",
	}
	return m
}

// AddMaskWord: マスク対象の文字列を追加する
func (m *MaskWriter) AddMaskWord(word string) {
	m.keywords = append(m.keywords, word)
}

// Write : 出力処理
func (m *MaskWriter) Write(p []byte) (n int, err error) {
	// 置換用のリストを作成する
	replaceList := []string{}
	for _, word := range m.keywords {
		// list = append(list, マスク対象の文字列, 置き換える文字)
		replaceList = append(replaceList, word, m.maskString)
	}

	replacer := strings.NewReplacer(replaceList...)

	// 文字を置換して書き込む
	return m.writer.Write([]byte(replacer.Replace(string(p))))
}

上の実装だと、
マスクしたくない場所に同じ文字列があったときにもマスクされてしまうので
そういうケースがある場合はもう少し工夫が必要です。

念のためにテストも。
bytes.Buffer を使えばオンメモリでテストできます。

mask_test.go
package main

import (
	"bytes"
	"testing"
)

func TestMaskWriter(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		mask    []string
		output  string
		wantErr bool
	}{
		{
			name:    `mask (1 word)`,
			input:   `hogefugapiyo`,
			mask:    []string{`fuga`},
			output:  `hoge********piyo`,
			wantErr: false,
		},
		{
			name:    `mask (2 words)`,
			input:   `hogefugapiyo`,
			mask:    []string{`fuga`, `piyo`},
			output:  `hoge****************`,
			wantErr: false,
		},
		{
			name:    `mask (multiline)`,
			input:   "hoge\nfugapiyo",
			mask:    []string{`fuga`},
			output:  "hoge\n********piyo",
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := &bytes.Buffer{}
			ml := NewMaskWriter(got)

			for _, w := range tt.mask {
				ml.AddMaskWord(w)
			}

			_, err := ml.Write([]byte(tt.input))
			if (err != nil) != tt.wantErr {
				t.Errorf("MaskWriter.Write() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if got.String() != tt.output {
				t.Errorf("MaskWriter.Write() = %v, want %v", got.String(), tt.output)
			}
		})
	}
}

あとは先ほど作ったものを使うだけ。

main.go
package main

import (
	"log"
	"os"
)

func main() {
	id := `hiro`
	pass := `password`

	// ログファイル
	logFile, err := os.Create(`output.log`)
	if err != nil {
		log.Fatal(err)
	}
	defer logFile.Close()

	// マスク用Writerに変更
	maskLogFile := NewMaskWriter(logFile)
	maskLogFile.AddMaskWord(pass)

	// ログ出力先設定
	log.SetOutput(&maskLogFile)

	// フォーマット設定
	log.SetPrefix(`[LOG] `)
	log.SetFlags(0)

	// 出力
	log.Printf(`ID  : %s`, id)
	log.Printf(`PASS: %s`, pass)
	log.Printf(`exec: hoge.exe --id %s --pass %s`, id, pass)
}

output.log
[LOG] ID  : hiro
[LOG] PASS: ********
[LOG] exec: hoge.exe --id hiro --pass ********

できました。

出力をわける

標準出力されるログは マスクしない で ログファイルだけマスクする 場合は、
io.MultiWriter() を使って まとめたWriter を使えばシンプルなコードにできます。

main.go
package main

import (
	"io"
	"log"
	"os"
)

func main() {
	id := `hiro`
	pass := `password`

	// ログファイル
	logFile, err := os.Create(`output.log`)
	if err != nil {
		log.Fatal(err)
	}
	defer logFile.Close()

	// マスク用Writerに変更
	maskLogFile := NewMaskWriter(logFile)
	maskLogFile.AddMaskWord(pass)

	// 両方に出力する
	mw := io.MultiWriter(os.Stdout, &maskLogFile)

	// ログ出力先設定
	log.SetOutput(mw)

	// フォーマット設定
	log.SetPrefix(`[LOG] `)
	log.SetFlags(0)

	// 出力
	log.Printf(`ID  : %s`, id)
	log.Printf(`PASS: %s`, pass)
	log.Printf(`exec: hoge.exe --id %s --pass %s`, id, pass)
}

標準出力
[LOG] ID  : hiro
[LOG] PASS: password
[LOG] exec: hoge.exe --id hiro --pass password
output.log
[LOG] ID  : hiro
[LOG] PASS: ********
[LOG] exec: hoge.exe --id hiro --pass ********

ね、簡単でしょう?(ボブ・ロス)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?