Google Cloud上でアプリケーションを動かす際に、Cloud Loggingが求める構造を使うと、Cloud TraceやError Reportingなどと相性がいいので、Cloud Logging用にログの形式を設定する。
logger側
package log
import (
"context"
"fmt"
"os"
"path"
"runtime"
"strconv"
"cloud.google.com/go/compute/metadata"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"go.opentelemetry.io/otel/trace"
)
// Google Cloud Project ID
var projectID string
// init Loggerの初期設定を行う。
func init() {
projectID, _ = metadata.ProjectID()
log.Logger = zerolog.New(os.Stdout).
Hook(zerolog.HookFunc(withTrace)).
With().Timestamp().
Logger()
zerolog.LevelFieldName = "severity"
zerolog.LevelWarnValue = "warning"
zerolog.TimestampFieldName = "timestamp"
zerolog.ErrorStackFieldName = "stack_trace"
zerolog.ErrorStackMarshaler = func(err error) any {
return fmt.Sprintf("%+v", err)
}
}
// InfoCtx Infoレベルのログイベントを生成する
func InfoCtx(ctx context.Context) *zerolog.Event {
return withSourceLocation(log.Info().Ctx(ctx))
}
// WarnCtx Warningレベルのログイベントを生成する
func WarnCtx(ctx context.Context) *zerolog.Event {
return withSourceLocation(log.Warn().Ctx(ctx))
}
// ErrorCtx Errorレベルのログイベントを生成する
func ErrorCtx(ctx context.Context, err error) *zerolog.Event {
return withSourceLocation(log.Error().Stack().Ctx(ctx)).Err(err)
}
// withSourceLocation ログにソースコードの情報を追加する
// event.Caller()は、オブジェクトではなく、文字列しか書き込めないため利用しない
func withSourceLocation(event *zerolog.Event) *zerolog.Event {
if pc, file, line, ok := runtime.Caller(2); ok {
return event.Dict("logging.googleapis.com/sourceLocation", zerolog.Dict().
Str("file", file).
Str("line", strconv.Itoa(line)).
Str("function", runtime.FuncForPC(pc).Name()),
)
}
return event
}
// withTrace ログにTrace情報を追加する
func withTrace(event *zerolog.Event, _ zerolog.Level, _ string) {
if sc := trace.SpanContextFromContext(event.GetCtx()); sc.IsValid() {
trace.SpanFromContext(event.GetCtx())
var tracePrefix string
if projectID != "" {
tracePrefix = path.Join("projects", projectID, "traces")
}
event.
Str("logging.googleapis.com/spanId", sc.SpanID().String()).
Str("logging.googleapis.com/trace", path.Join(tracePrefix, sc.TraceID().String())).
Bool("logging.googleapis.com/trace_sampled", sc.IsSampled())
}
}
呼び出し側
package main
import (
"context"
"example/log"
"github.com/cockroachdb/errors"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
var tracer trace.Tracer
func init() {
otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
tp := sdktrace.NewTracerProvider()
otel.SetTracerProvider(tp)
tracer = tp.Tracer("example")
}
func main() {
ctx := context.Background()
demo(ctx)
}
func demo(ctx context.Context) {
ctx, span := tracer.Start(ctx, "call demo")
defer span.End()
log.InfoCtx(ctx).Msg("OK!")
log.ErrorCtx(ctx).Err(errors.New("demo error")).Msg("Oops!")
}
実行結果
{"severity":"info","logging.googleapis.com/sourceLocation":{"file":"/path/to/file.go","line":"24","func":"main.demo"},"logging.googleapis.com/spanId":"9f36d9191b99a8e7","logging.googleapis.com/trace":"4c7141227918cb6973472e205d2a6d03","logging.googleapis.com/trace_sampled":"true","timestamp":"2024-04-26T00:29:48+09:00","message":"OK!"}
{"severity":"error","logging.googleapis.com/sourceLocation":{"file":"/path/to/file.go","line":"25","func":"main.demo"},"stack_trace":"demo error\n(1) attached stack trace\n -- stack trace:\n | main.demo\n | \t/path/to/file.go:25\n | main.main\n | \t/path/to/file.go:18\n | (略)","error":"demo error","logging.googleapis.com/spanId":"9f36d9191b99a8e7","logging.googleapis.com/trace":"4c7141227918cb6973472e205d2a6d03","logging.googleapis.com/trace_sampled":"true","timestamp":"2024-04-26T00:29:48+09:00","message":"Oops!"}