0
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?

zerologでCloud Logging用の構造化ログを作る

Last updated at Posted at 2024-04-25

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!"}
0
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
0
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?