Go 로그의 json 파일 내보내기
개시하다
광활한 인터넷 바다에 작은 방해 수색을 추가하다.
모티프
개인 개발 프로젝트의 운용에서 일지를 어떻게 보관하고 관리할 것인가를 고려하기 시작했다.
sentry에 의거하여zeroolog와logrus 등 좋은 포장을 사용해도 괜찮지만, 공부를 하면 어떻게든 고의 표준 포장으로 자제할 수 있다고 생각합니다.
goo의 장점은 역시 표준 포장이 딱 좋다.
파일에 로그 쓰기
나중에 읽을 수 있도록 서류 보관을 고려했다.
이것은 로그 포장으로 매우 간단합니다.
logger.go
package domain
import (
".../configs"
".../tools"
"encoding/json"
"fmt"
"net/http"
"os"
"runtime"
"time"
)
const (
/******************
probrem
******************/
// urgency (at once)
LogAlert = "ALT"
// urgency (at many times)
LogCritical = "CRT"
// need fix without urgency (at many times)
LogWarn = "WAN"
/******************
no probrem
******************/
LogInfo = "INF"
/******************
no probrem
******************/
LogDebug = "DBG"
)
// log出力汎用関数
func ErrorLogger(err error, mode string) {
if IsProduction() {
today := time.Now().Format("20060102")
lFile, _ := os.OpenFile(fmt.Sprintf("%s/log_%s.log", configs.ErrorLogDirectory, today), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
defer lFile.Close()
// get called place
_, file, line, _ := runtime.Caller(1)
log.SetOutput(lFile) // 出力先の変更
log.SetFlags(log.LstdFlags) // 不要、フラグを付け足すときにはここにパイプで追加
// set prefix
var level string
switch mode {
case "":
level = LogInfo
default:
level = mode
}
log.SetPrefix(level+" ")
log.Println(fmt.Sprintf("%s:%d: %v", file, line, err))
} else {
log.Println(err)
}
}
제가 설명해 드릴게요.runtime를 사용하여 호출 원본의 위치를 찾습니다.
_, file, line, _ := runtime.Caller(1)
SetOutput 방법으로 로그의 출력 위치를 변경할 수 있습니다.log.SetOutput(lFile) // 出力先の変更
Laavel의 Log fathod와 같은 형식으로 로그 레벨을 접두사 문자로 제공합니다.// set prefix
var level string
switch mode {
case "":
level = LogInfo
default:
level = mode
}
log.SetPrefix(level)
이렇게 하면 인쇄 파일이 이런 느낌으로 출력된다.WAN 2021/07/28 08:05:56 /<秘密>/internal/pkg/interfaces/database/audience_repository.go:121: sql: Rows are closed
WAN 2021/07/28 08:05:58 /<秘密>/internal/pkg/interfaces/database/audience_repository.go:121: sql: Rows are closed
json 파일에 로그 쓰기
검색성을 높이고 차원화 가능성을 결정하는 json.
파일 출력
그때는 먼저 json 파일의 시작에 공을 들여야 하기 때문에 공유합니다.
json 파일을 출력하는 방법으로 두 가지를 고려했습니다.
1의 방법과 시험과 결과를 마지막으로 보충하여 기록한다.
2의 방법(추기법)
logger.go
func writeJsonFile(fileName string, object interface{}) {
file, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0600)
defer file.Close()
fi, _ := file.Stat()
leng := fi.Size()
json_, _ := json.Marshal(object)
if leng == 0 {
file.Write([]byte(fmt.Sprintf(`[%s]`, json_)))
} else {
file.WriteAt([]byte(fmt.Sprintf(`,%s]`, json_)), leng-1)
}
}
우선 쓰고 싶은 것을 json화해라.다음에 파일 길이를 가져옵니다.
새 파일인 경우 직접 내보내기
그렇지 않으면 마지막 문자
]
가 ,<追加分>]
문자열로 덮어씁니다.오류 로그를 json으로 설정하여 파일에 쓰기
아까 writeJson () 을 사용하면 이렇게 쓸 수 있습니다.
전문이 단숨에 완성되다.
logger.go
package domain
import (
"animar/v1/configs"
"animar/v1/internal/pkg/tools/tools"
"encoding/json"
"fmt"
"net/http"
"os"
"runtime"
"time"
)
const (
/******************
probrem
******************/
// urgency (at once)
LogAlert = "ALT"
// urgency (at many times)
LogCritical = "CRT"
// need fix without urgency
LogWarn = "WAN"
/******************
no probrem
******************/
LogInfo = "INF"
/******************
no probrem
******************/
LogDebug = "DBG"
)
// Logの基底構造体
type Log struct {
Kind string `json:"kind"`
Time time.Time `json:"time"`
Level string `json:"level"`
}
// Error用ログの構造体
type LogE struct {
Log
Content string `json:"content"` // エラー内容
Place string `json:"place"` // エラー発生場所
}
// Access用ログの構造体
type LogA struct {
Log
Address string `json:"address"` // IPアドレス
Method string `json:"method"` // request method
Path string `json:"path"` // path
}
func NewAccessLog() *LogA {
// startのlogをここで作る
alog := &LogA{
Log: Log{
Kind: "access",
},
}
return alog
}
func newErrorLog() *LogE {
eLog := &LogE{
Log: Log{
Kind: "error",
},
}
return eLog
}
func ErrorAlert(err error) {
e := newErrorLog()
e.Logging(err, LogAlert)
}
func ErrorCritical(err error) {
e := newErrorLog()
e.Logging(err, LogCritical)
}
func ErrorWarn(err error) {
e := newErrorLog()
e.Logging(err, LogWarn)
}
func (e *LogE) Logging(err error, level string) {
if tools.IsProductionEnv() {
e.write(err, level)
} else {
e.write(err, level)
}
}
func (a *LogA) Logging(r *http.Request) {
if tools.IsProductionEnv() {
a.write(r)
} else {
a.write(r)
}
}
func (a *LogA) write(r *http.Request) {
today := time.Now().Format("20060102")
a.Level = LogInfo
a.Time = time.Now()
a.Address = r.RemoteAddr
a.Method = r.Method
a.Path = r.URL.Path
writeJsonFile(fmt.Sprintf("%s/log_%s.json", configs.ErrorLogDirectory, today), a)
}
func (e *LogE) write(err error, level string) {
today := time.Now().Format("20060102")
e.Level = level
e.Content = err.Error()
// auto
_, file, line, _ := runtime.Caller(3))
e.Place = fmt.Sprintf("%s:%d", file, line)
e.Time = time.Now()
writeJsonFile(fmt.Sprintf("%s/log_%s.json", configs.ErrorLogDirectory, today), e)
}
// 上で解説したjson file書き込み関数
func writeJsonFile(fileName string, object interface{}) {
file, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0600)
defer file.Close()
fi, _ := file.Stat()
leng := fi.Size()
json_, _ := json.Marshal(object)
if leng == 0 {
file.Write([]byte(fmt.Sprintf(`[%s]`, json_)))
} else {
file.WriteAt([]byte(fmt.Sprintf(`,%s]`, json_)), leng-1)
}
}
func HttpLog(h http.Handler, l *LogA) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l.Logging(r)
h.ServeHTTP(w, r)
})
}
상세히 설명하다.오류 로그나 액세스 로그에 Log 구성체가 포함된 구성체를 정의합니다.
// Logの基底構造体
type Log struct {
Kind string `json:"kind"`
Time time.Time `json:"time"`
Level string `json:"level"`
}
// Error用ログの構造体
type LogE struct {
Log
Content string `json:"content"` // エラー内容
Place string `json:"place"` // エラー発生場所
}
// Access用ログの構造体
type LogA struct {
Log
Address string `json:"address"` // IPアドレス
Method string `json:"method"` // request method
Path string `json:"path"` // path
}
를 초기화할 때,kind는 기본값을 가지고 있습니다.func NewAccessLog() *LogA {
// startのlogをここで作る
alog := &LogA{
Log: Log{
Kind: "access",
},
}
return alog
}
func newErrorLog() *LogE {
eLog := &LogE{
Log: Log{
Kind: "error",
},
}
return eLog
}
오류 로그는 이렇게 하면 바로 호출할 수 있습니다.func ErrorAlert(err error) {
e := newErrorLog()
e.Logging(err, LogAlert)
}
func ErrorCritical(err error) {
e := newErrorLog()
e.Logging(err, LogCritical)
}
func ErrorWarn(err error) {
e := newErrorLog()
e.Logging(err, LogWarn)
}
func (e *LogE) Logging(err error, level string) {
if tools.IsProductionEnv() {
e.write(err, level)
} else {
e.write(err, level)
}
}
func (e *LogE) write(err error, level string) {
today := time.Now().Format("20060102")
e.Level = level
e.Content = err.Error()
// auto
_, file, line, _ := runtime.Caller(2)
e.Place = fmt.Sprintf("%s:%d", file, line)
e.Time = time.Now()
// 前章で解説してたjsonファイル書き込み関数
writeJsonFile(fmt.Sprintf("%s/log_%s.json", configs.ErrorLogDirectory, today), e)
}
다른 한편, 방문 로그는 매 세션의 사용 대상의 형식으로 사용된다.func (a *LogA) Logging(r *http.Request) {
if tools.IsProductionEnv() {
a.write(r)
} else {
a.write(r)
}
}
func (a *LogA) write(r *http.Request) {
today := time.Now().Format("20060102")
a.Level = LogInfo
a.Time = time.Now()
a.Address = r.RemoteAddr
a.Method = r.Method
a.Path = r.URL.Path
// 前章で解説してたjsonファイル書き込み関数
writeJsonFile(fmt.Sprintf("%s/log_%s.json", configs.ErrorLogDirectory, today), a)
}
// アクセスに関するログを吐き出す関数
func HttpLog(h http.Handler, l *LogA) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
l.Logging(r)
h.ServeHTTP(w, r)
})
}
는 실제로 이렇게 사용한다.main.go
package main
import (
"net/http"
".../domain"
)
func main() {
router := http.NewServeMux()
l := domain.NewAccessLog()
router.Handle(...)
...
// リクエストごとにHttpLogが発動します。
if err := http.ListenAndServe(":8000", domain.HttpLog(router, l)); err != nil {
domain.ErrorAlert(err)
}
}
출력 결과는 이런 느낌입니다.(jq)[
{
"kind": "error",
"time": "2021-08-05T07:38:54.051566671+09:00",
"level": "WAN",
"content": "sql: Rows are closed",
"place": "<秘密>/internal/pkg/interfaces/database/review_repository.go:74"
},
{
"kind": "access",
"time": "2021-08-05T07:41:00.609925853+09:00",
"level": "INF",
"address": "52.00.000.000",
"method": "GET",
"path": "/db/anime/"
}
]
총결산
goo에서 로그 파일을 출력하는 방법을 소개합니다.
json 형식으로 로그를 출력하는 방법을 한층 더 소개했다.
앞으로 방문 로그를 추가하여 등급이 높을 때 슬랙 알림을 즉시 발송하는 기능으로 일괄 처리를 통해 정기적으로 회수하는 로그를 sentry처럼 화면에서 볼 수 있습니다.
결국 sentry를 사용하기로 결정해도 이렇게 스스로 만들어 보면서 많은 것을 배우는 것이 재미있다.
이렇게 하면 자유롭게 실험을 할 수 있다는 것이 개인 개발의 장점이다.
보충 덮어쓰기와 기준 비교
덮어쓰기 방법
원래 쓰는 법 자체가 묘한 것 같아요.
좀 더 깔끔하게 쓸 수 있을 것 같아요.
overrite.go
func overWrite(fileName string, object interface{}) {
file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
fmt.Print(err)
}
defer file.Close()
buf := make([]byte, 1024*1024)
for {
n, err := file.Read(buf)
if n == 0 {
break
}
if err != nil {
panic(err)
}
}
// resize
idx := bytes.IndexByte(buf, 0)
buf = buf[:idx]
var lst []interface{}
json.Unmarshal(buf, &lst)
lst = append(lst, object)
json_, _ := json.Marshal(lst)
file.WriteAt(json_, 0)
}
현재 하고 있는 일로 파일에 적힌 부분을 json에서 슬라이스로 바꾸어 새로운 요소를 적용합니다.그리고 그것을 json으로 만들어서 파일에 덮어씁니다.이런 방법.
왠지 멋있어요.
데이텀 측정의 결과도 아래에 나열됩니다.
큰 차이는 없지만 새 파일에도 기록을 추가하는 방법이 성능이 더 좋은 것 같다.
jsonwrite_test.go
package jsonwrite_test
import (
"bytes"
"encoding/json"
"fmt"
"os"
"testing"
)
// 要素を追加 (既存)
func BenchmarkAppend_AppendJson(b *testing.B) {
b.ResetTimer()
p := &Sample{Name: "Jhone Doe", Old: 20, Explain: "aaaa aaaa"}
appendJson(filePath, p)
}
// まるごと書き換え (既存)
func BenchmarkAppend_Overwrite(b *testing.B) {
b.ResetTimer()
p := &Sample{Name: "Jhone Doe", Old: 20, Explain: "aaaa aaaa"}
overWrite(filePath2, p)
}
// 要素を追加 (新規)
func BenchmarkAppend_AppendJsonNew(b *testing.B) {
b.ResetTimer()
p := &Sample{Name: "Jhone Doe", Old: 20, Explain: "aaaa aaaa"}
appendJson(noFilePath, p)
}
// まるごと書き換え (新規)
func BenchmarkAppend_OverwriteNew(b *testing.B) {
b.ResetTimer()
p := &Sample{Name: "Jhone Doe", Old: 20, Explain: "aaaa aaaa"}
overWrite(noFilePath2, p)
}
// delete file
func BenchmarkAppend_DeleteFile(b *testing.B) {
b.ResetTimer()
os.Remove(noFilePath)
os.Remove(noFilePath2)
}
const filePath = "./sample.json"
const noFilePath = "./no.json"
const filePath2 = "./sample2.json"
const noFilePath2 = "./no2.json"
type Sample struct {
Name string `json:"name"`
Old int `json:"old,omitempty"`
Explain string `json:"explain"`
}
func appendJson(fileName string, object interface{}) {
file, _ := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0600)
defer file.Close()
fi, _ := file.Stat()
leng := fi.Size()
json_, _ := json.Marshal(object)
if leng == 0 {
file.Write([]byte(fmt.Sprintf(`[%s]`, json_)))
} else {
file.WriteAt([]byte(fmt.Sprintf(`,%s]`, json_)), leng-1)
}
}
func overWrite(fileName string, object interface{}) {
file, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0600)
if err != nil {
fmt.Print(err)
}
defer file.Close()
buf := make([]byte, 1024*1024)
for {
n, err := file.Read(buf)
if n == 0 {
break
}
if err != nil {
panic(err)
}
}
// resize
idx := bytes.IndexByte(buf, 0)
buf = buf[:idx]
var lst []interface{}
json.Unmarshal(buf, &lst)
lst = append(lst, object)
json_, _ := json.Marshal(lst)
file.WriteAt(json_, 0)
}
// result
/******************************
BenchmarkAppend_AppendJson-8 1000000000 0.001882 ns/op 0 B/op 0 allocs/op
BenchmarkAppend_Overwrite-8 1000000000 0.004287 ns/op 0 B/op 0 allocs/op
BenchmarkAppend_AppendJsonNew-8 1000000000 0.002100 ns/op 0 B/op 0 allocs/op
BenchmarkAppend_OverwriteNew-8 1000000000 0.003463 ns/op 0 B/op 0 allocs/op
******************************/
참고 자료 등
Reference
이 문제에 관하여(Go 로그의 json 파일 내보내기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/maru44/articles/51da79a367b0ca텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)