Skip to content

Commit a1993e2

Browse files
committed
Fix logging
1 parent 156e4b3 commit a1993e2

File tree

7 files changed

+271
-26
lines changed

7 files changed

+271
-26
lines changed

api/go.mod

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.18
44

55
require (
66
firebase.google.com/go v3.13.0+incompatible
7+
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.8.3
78
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
89
github.com/cheggaaa/pb/v3 v3.0.8
910
github.com/cloudevents/sdk-go/v2 v2.10.0
@@ -20,8 +21,10 @@ require (
2021
github.com/swaggo/swag v1.8.2
2122
github.com/thedevsaddam/govalidator v1.9.10
2223
go.opentelemetry.io/otel v1.7.0
24+
go.opentelemetry.io/otel/exporters/jaeger v1.7.0
25+
go.opentelemetry.io/otel/sdk v1.7.0
2326
go.opentelemetry.io/otel/trace v1.7.0
24-
google.golang.org/api v0.73.0
27+
google.golang.org/api v0.74.0
2528
gorm.io/datatypes v1.0.6
2629
gorm.io/driver/postgres v1.3.7
2730
gorm.io/gorm v1.23.5
@@ -33,19 +36,23 @@ require (
3336
cloud.google.com/go/firestore v1.6.1 // indirect
3437
cloud.google.com/go/iam v0.1.1 // indirect
3538
cloud.google.com/go/storage v1.21.0 // indirect
39+
cloud.google.com/go/trace v1.2.0 // indirect
40+
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.32.3 // indirect
3641
github.com/KyleBanks/depth v1.2.1 // indirect
3742
github.com/VividCortex/ewma v1.1.1 // indirect
3843
github.com/andybalholm/brotli v1.0.4 // indirect
3944
github.com/fatih/color v1.10.0 // indirect
45+
github.com/go-logr/logr v1.2.3 // indirect
46+
github.com/go-logr/stdr v1.2.2 // indirect
4047
github.com/go-openapi/jsonpointer v0.19.5 // indirect
4148
github.com/go-openapi/jsonreference v0.20.0 // indirect
4249
github.com/go-openapi/spec v0.20.6 // indirect
4350
github.com/go-openapi/swag v0.21.1 // indirect
4451
github.com/go-sql-driver/mysql v1.6.0 // indirect
45-
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
52+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
4653
github.com/golang/protobuf v1.5.2 // indirect
4754
github.com/google/go-cmp v0.5.7 // indirect
48-
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
55+
github.com/googleapis/gax-go/v2 v2.2.0 // indirect
4956
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
5057
github.com/jackc/pgconn v1.12.1 // indirect
5158
github.com/jackc/pgio v1.0.0 // indirect
@@ -71,8 +78,8 @@ require (
7178
github.com/valyala/fasthttp v1.37.0 // indirect
7279
github.com/valyala/tcplisten v1.0.0 // indirect
7380
go.opencensus.io v0.23.0 // indirect
74-
go.uber.org/atomic v1.6.0 // indirect
75-
go.uber.org/multierr v1.5.0 // indirect
81+
go.uber.org/atomic v1.7.0 // indirect
82+
go.uber.org/multierr v1.8.0 // indirect
7683
go.uber.org/zap v1.13.0 // indirect
7784
golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect
7885
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
@@ -83,9 +90,9 @@ require (
8390
golang.org/x/tools v0.1.10 // indirect
8491
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
8592
google.golang.org/appengine v1.6.7 // indirect
86-
google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6 // indirect
93+
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect
8794
google.golang.org/grpc v1.45.0 // indirect
88-
google.golang.org/protobuf v1.27.1 // indirect
95+
google.golang.org/protobuf v1.28.0 // indirect
8996
gopkg.in/yaml.v2 v2.4.0 // indirect
9097
gorm.io/driver/mysql v1.3.2 // indirect
9198
)

api/go.sum

Lines changed: 38 additions & 12 deletions
Large diffs are not rendered by default.

api/pkg/di/container.go

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,18 @@ import (
77
"strconv"
88
"time"
99

10+
"go.opentelemetry.io/otel"
11+
"go.opentelemetry.io/otel/attribute"
12+
"go.opentelemetry.io/otel/propagation"
13+
"go.opentelemetry.io/otel/sdk/resource"
14+
semconv "go.opentelemetry.io/otel/semconv/v1.10.0"
15+
1016
"firebase.google.com/go/messaging"
17+
cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace"
1118
"github.com/hirosassa/zerodriver"
1219
"github.com/rs/zerolog"
20+
"go.opentelemetry.io/otel/exporters/jaeger"
21+
"go.opentelemetry.io/otel/sdk/trace"
1322

1423
firebase "firebase.google.com/go"
1524
"firebase.google.com/go/auth"
@@ -51,6 +60,8 @@ func NewContainer(projectID string) (container *Container) {
5160
logger: logger(3).WithService(fmt.Sprintf("%T", container)),
5261
}
5362

63+
container.InitializeTraceProvider("0.0.1", os.Getenv("GCP_PROJECT_ID"))
64+
5465
container.RegisterMessageListeners()
5566
container.RegisterMessageRoutes()
5667

@@ -86,6 +97,8 @@ func (container *Container) App() (app *fiber.App) {
8697
app.Use(fiberLogger.New())
8798
}
8899

100+
app.Use(middlewares.OtelTraceContext(container.Tracer(), container.Logger(), "X-Cloud-Trace-Context", os.Getenv("GCP_PROJECT_ID")))
101+
89102
// Default config
90103
app.Use(cors.New())
91104

@@ -564,11 +577,73 @@ func (container *Container) UserRepository() repositories.UserRepository {
564577
)
565578
}
566579

580+
// InitializeTraceProvider initializes the open telemetry trace provider
581+
func (container *Container) InitializeTraceProvider(version string, namespace string) func() {
582+
if isLocal() {
583+
return container.initializeJaegerTraceProvider(version, namespace)
584+
}
585+
return container.initializeGoogleTraceProvider(version, namespace)
586+
}
587+
588+
func (container *Container) initializeGoogleTraceProvider(version string, namespace string) func() {
589+
container.logger.Debug("initializing google trace provider")
590+
591+
exporter, err := cloudtrace.New(cloudtrace.WithProjectID(os.Getenv("GCP_PROJECT_ID")))
592+
if err != nil {
593+
container.logger.Fatal(stacktrace.Propagate(err, "cannot create cloud trace exporter"))
594+
}
595+
596+
tp := trace.NewTracerProvider(
597+
trace.WithBatcher(exporter),
598+
trace.WithSampler(trace.AlwaysSample()),
599+
trace.WithResource(container.InitializeOtelResources(version, namespace)),
600+
)
601+
602+
otel.SetTracerProvider(tp)
603+
604+
return func() {
605+
_ = exporter.Shutdown(context.Background())
606+
}
607+
}
608+
609+
func (container *Container) initializeJaegerTraceProvider(version string, namespace string) (flush func()) {
610+
container.logger.Debug("initializing jaeger trace provider")
611+
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(os.Getenv("JAEGER_COLLECTOR_ENDPOINT"))))
612+
if err != nil {
613+
container.logger.Error(stacktrace.Propagate(err, "could not create jaeger exporter"))
614+
}
615+
tp := trace.NewTracerProvider(
616+
// Always be sure to batch in production.
617+
trace.WithBatcher(exp),
618+
trace.WithSampler(trace.AlwaysSample()),
619+
// Record information about this application in a resource.
620+
trace.WithResource(container.InitializeOtelResources(version, namespace)),
621+
)
622+
623+
otel.SetTracerProvider(tp)
624+
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
625+
626+
return func() {
627+
_ = exp.Shutdown(context.Background())
628+
}
629+
}
630+
631+
// InitializeOtelResources initializes open telemetry resources
632+
func (container *Container) InitializeOtelResources(version string, namespace string) *resource.Resource {
633+
return resource.NewWithAttributes(
634+
semconv.SchemaURL,
635+
semconv.ServiceNameKey.String(namespace),
636+
semconv.ServiceNamespaceKey.String(namespace),
637+
semconv.ServiceVersionKey.String(version),
638+
semconv.ServiceInstanceIDKey.String(hostName()),
639+
attribute.String("service.environment", os.Getenv("ENV")),
640+
)
641+
}
642+
567643
func logger(skipFrameCount int) telemetry.Logger {
568-
hostname, _ := os.Hostname()
569644
fields := map[string]string{
570645
"pid": strconv.Itoa(os.Getpid()),
571-
"hostname": hostname,
646+
"hostname": hostName(),
572647
}
573648

574649
return telemetry.NewZerologLogger(
@@ -612,6 +687,14 @@ func jsonLogger(skipFrameCount int) *zerodriver.Logger {
612687
return &zerodriver.Logger{Logger: &zl}
613688
}
614689

690+
func hostName() string {
691+
h, err := os.Hostname()
692+
if err != nil {
693+
h = strconv.Itoa(os.Getpid())
694+
}
695+
return h
696+
}
697+
615698
func consoleLogger(skipFrameCount int) *zerodriver.Logger {
616699
l := zerolog.New(
617700
zerolog.ConsoleWriter{
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package middlewares
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
"strings"
8+
9+
"github.com/NdoleStudio/http-sms-manager/pkg/telemetry"
10+
"github.com/gofiber/fiber/v2"
11+
"github.com/palantir/stacktrace"
12+
"go.opentelemetry.io/otel"
13+
"go.opentelemetry.io/otel/attribute"
14+
"go.opentelemetry.io/otel/trace"
15+
)
16+
17+
const (
18+
clientVersionHeader = "X-Client-Version"
19+
)
20+
21+
// OtelTraceContext adds a trace for an HTTP request
22+
func OtelTraceContext(tracer telemetry.Tracer, logger telemetry.Logger, header string, namespace string) fiber.Handler {
23+
return func(c *fiber.Ctx) error {
24+
// Get the Context from the request
25+
spanContext, errors := spanContextFromHeader(c.Get(header))
26+
if len(errors) != 0 {
27+
logger.Error(stacktrace.NewError(strings.Join(errors, "\n")))
28+
}
29+
30+
if !spanContext.IsValid() {
31+
if c.Get(header) != "" {
32+
logger.Error(stacktrace.NewError("invalid trace context %s creating new context", c.Get(header)))
33+
}
34+
otelTracer := otel.Tracer(namespace)
35+
_, span := otelTracer.Start(context.Background(), fmt.Sprintf("%s %s", c.Method(), c.OriginalURL()))
36+
defer span.End()
37+
spanContext = span.SpanContext()
38+
}
39+
40+
logger.WithSpan(spanContext).
41+
WithString("http.method", c.Method()).
42+
WithString("client.version", c.Get(clientVersionHeader)).
43+
Trace(c.OriginalURL())
44+
45+
newCtx, span := otel.Tracer(namespace).Start(trace.ContextWithRemoteSpanContext(context.Background(), spanContext), "middlewares.OtelTraceContext")
46+
defer span.End()
47+
48+
ctxLogger := tracer.CtxLogger(logger, span)
49+
traceID := spanContext.TraceID().String()
50+
span.SetAttributes(attribute.Key("traceID").String(traceID))
51+
span.SetAttributes(attribute.Key("SpanID").String(span.SpanContext().SpanID().String()))
52+
span.SetAttributes(attribute.Key("traceFlags").String(spanContext.TraceFlags().String()))
53+
span.SetAttributes(attribute.Key("clientVersion").String(c.Get(clientVersionHeader)))
54+
55+
c.Locals(telemetry.TracerContextKey, trace.ContextWithSpan(newCtx, span))
56+
ctxLogger.Trace(fmt.Sprintf("context set key [%s] url[%s] method [%s]", telemetry.TracerContextKey, c.OriginalURL(), c.Method()))
57+
58+
// Go to next middleware:
59+
response := c.Next()
60+
61+
statusCode := c.Response().StatusCode()
62+
span.AddEvent(fmt.Sprintf("finished handling request with traceID: %s, statusCode = %d", traceID, statusCode))
63+
64+
if statusCode >= 300 && len(c.Request().Body()) > 0 {
65+
ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("http.status [%b], body [%s]", statusCode, string(c.Request().Body()))))
66+
}
67+
68+
return response
69+
}
70+
}
71+
72+
func spanContextFromHeader(parentContext string) (trace.SpanContext, []string) {
73+
result := trace.NewSpanContext(trace.SpanContextConfig{
74+
TraceID: trace.TraceID{},
75+
SpanID: trace.SpanID{},
76+
TraceState: trace.TraceState{},
77+
Remote: true,
78+
})
79+
80+
parts := strings.Split(parentContext, "/")
81+
var errors []string
82+
if len(parts) == 2 {
83+
traceID, err := trace.TraceIDFromHex(parts[0])
84+
if err != nil {
85+
errors = append(errors, fmt.Sprintf("could not get trace id %v", err))
86+
return result, errors
87+
}
88+
result = result.WithTraceID(traceID)
89+
90+
spanParts := strings.Split(parts[1], ";")
91+
if len(spanParts) == 1 {
92+
spanParts = append(spanParts, "")
93+
}
94+
95+
if len(spanParts) == 2 {
96+
val, err := strconv.ParseUint(spanParts[0], 10, 64)
97+
if err != nil {
98+
errors = append(errors, fmt.Sprintf("could not get trace id %v", err))
99+
return result, errors
100+
}
101+
102+
spanID, err := trace.SpanIDFromHex(fmt.Sprintf("%016x", val))
103+
if err != nil {
104+
errors = append(errors, fmt.Sprintf("could not get span trace id %v", err))
105+
return result, errors
106+
}
107+
result = result.WithSpanID(spanID)
108+
109+
if spanParts[1] == "o=1" {
110+
result = result.WithTraceFlags(trace.FlagsSampled)
111+
}
112+
}
113+
}
114+
115+
return result, errors
116+
}

api/pkg/telemetry/otel_tracer.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package telemetry
22

33
import (
44
"context"
5+
"fmt"
56
"runtime"
67
"strings"
78

9+
"github.com/palantir/stacktrace"
10+
811
"github.com/gofiber/fiber/v2"
912
"go.opentelemetry.io/otel/attribute"
1013
"go.opentelemetry.io/otel/codes"
@@ -24,10 +27,15 @@ func NewOtelLogger(projectID string, logger Logger) Tracer {
2427
}
2528
}
2629

27-
func (tracer *otelTracer) StartFromFiberCtx(_ *fiber.Ctx, name ...string) (context.Context, trace.Span) {
28-
ctx, span := trace.NewNoopTracerProvider().Tracer(tracer.projectID).Start(context.Background(), getName(name...))
29-
defer span.End()
30-
return tracer.Start(ctx, getName(name...))
30+
func (tracer *otelTracer) StartFromFiberCtx(c *fiber.Ctx, name ...string) (context.Context, trace.Span) {
31+
parentCtx, ok := c.Locals(TracerContextKey).(context.Context)
32+
if !ok {
33+
tracer.logger.Error(stacktrace.NewError(fmt.Sprintf("could not get trace from context with key [%s] url[%s] method [%s]", TracerContextKey, c.OriginalURL(), c.Method())))
34+
ctx, span := trace.NewNoopTracerProvider().Tracer("").Start(context.Background(), "")
35+
defer span.End()
36+
parentCtx = ctx
37+
}
38+
return tracer.Start(parentCtx, getName(name...))
3139
}
3240

3341
func (tracer *otelTracer) CtxLogger(logger Logger, span trace.Span) Logger {

api/pkg/telemetry/tracer.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import (
77
"go.opentelemetry.io/otel/trace"
88
)
99

10+
const (
11+
// TracerContextKey stores the fiber trace context
12+
TracerContextKey = "tracer.context.key"
13+
)
14+
1015
// Tracer is used for tracing
1116
type Tracer interface {
1217
// StartFromFiberCtx creates a spanContext and a context.Context containing the newly-created spanContext.

api/pkg/telemetry/zerolog_logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (logger *zerologLogger) Fatal(err error) {
7575

7676
// Error logs an error
7777
func (logger *zerologLogger) Error(err error) {
78-
logger.decorateEvent(logger.zerolog.Fatal()).Err(err).Send()
78+
logger.decorateEvent(logger.zerolog.Error()).Err(err).Send()
7979
}
8080

8181
// WithSpan adds a spanContext to a logger

0 commit comments

Comments
 (0)