diff --git a/kubernetes-manifests/checkoutservice.yaml b/kubernetes-manifests/checkoutservice.yaml index 3e244c4..ef784ba 100644 --- a/kubernetes-manifests/checkoutservice.yaml +++ b/kubernetes-manifests/checkoutservice.yaml @@ -51,10 +51,18 @@ spec: value: "currencyservice:7000" - name: CART_SERVICE_ADDR value: "cartservice:7070" + - name: NEW_RELIC_APP_NAME + value: checkout-service + - name: NEW_RELIC_LICENSE_KEY + value: MY_LICENSE_KEY + - name: NEW_RELIC_DISTRIBUTED_TRACING_ENABLED + value: "true" + - name: NEW_RELIC_LABELS + value: clusterName:GKE # - name: DISABLE_STATS # value: "1" - # - name: DISABLE_TRACING - # value: "1" + - name: DISABLE_TRACING + value: "1" # - name: DISABLE_PROFILER # value: "1" # - name: JAEGER_SERVICE_ADDR diff --git a/src/checkoutservice/Gopkg.lock b/src/checkoutservice/Gopkg.lock index a46ae2c..1129da9 100644 --- a/src/checkoutservice/Gopkg.lock +++ b/src/checkoutservice/Gopkg.lock @@ -25,11 +25,11 @@ [[projects]] branch = "master" - digest = "1:a85b0dc359de4812d383ee6670f0dae595cfcb8b13ff6b872bdb013a18c37b07" + digest = "1:9d3a867627f4c2ed7530c68672ce7b5381fbd36419743dfa01ccff501a7995e5" name = "git.apache.org/thrift.git" packages = ["lib/go/thrift"] pruneopts = "UT" - revision = "286eee16b147a302ddc7b10740c5e5401ebbec17" + revision = "aec555aac89ec9634e99ce0f4f0aec5799e6e3d8" source = "github.com/apache/thrift" [[projects]] @@ -40,11 +40,11 @@ "src/checkoutservice/money", ] pruneopts = "UT" - revision = "27df445fc20f048c1c31e429f6c29075119fe454" - version = "v0.1.1" + revision = "d66cbdd27f73bb1ffa7479a3527f55dec070baa0" + version = "v0.1.4" [[projects]] - digest = "1:1d3ad0f6a57c08e2168089a64c34313930571fcbe5359d71c608a97ce504f7ca" + digest = "1:56b3126ac15d478fa10928937c085b50be29cb29c32f069dcceeceac9423a327" name = "github.com/golang/protobuf" packages = [ "proto", @@ -58,16 +58,30 @@ "ptypes/wrappers", ] pruneopts = "UT" - revision = "b5d812f8a3706043e23a9cd5babf2e5423744d30" - version = "v1.3.1" + revision = "5d5b4c10bd43f85e63bd9e4a3fa9b1ea2ef88af2" + version = "v1.3.4" + +[[projects]] + digest = "1:0aeda02073125667ac6c9df50c7921cb22c08a4accdc54589c697a7e76be65c2" + name = "github.com/google/go-cmp" + packages = [ + "cmp", + "cmp/internal/diff", + "cmp/internal/flags", + "cmp/internal/function", + "cmp/internal/value", + ] + pruneopts = "UT" + revision = "5a6f75716e1203a923a78c9efb94089d857df0f6" + version = "v0.4.0" [[projects]] branch = "master" - digest = "1:dcb1edb161b1b1cac9aedf2a17b9b2c7829e242624968875a3c1b97dd9f4ef00" + digest = "1:1f1aa5c5a82ee4d757145920b88601a4c80aa0979a349a21c877a588cc0fe03c" name = "github.com/google/pprof" packages = ["profile"] pruneopts = "UT" - revision = "54271f7e092ff31b10b7626fee166cbc6304e350" + revision = "4ac0da889f5869b4ce048f66afcd9cce809409e7" [[projects]] digest = "1:582b704bebaa06b48c29b0cec224a6058a09c86883aaddabde889cd1a5f73e1b" @@ -93,6 +107,25 @@ revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e" version = "v1.0.2" +[[projects]] + digest = "1:dba348ae1fda55681897582516890a382c34d9da292790a4473fe0cdfbcfcff5" + name = "github.com/newrelic/go-agent" + packages = [ + "v3/integrations/logcontext", + "v3/integrations/logcontext/nrlogrusplugin", + "v3/integrations/nrgrpc", + "v3/internal", + "v3/internal/cat", + "v3/internal/jsonx", + "v3/internal/logger", + "v3/internal/sysinfo", + "v3/internal/utilization", + "v3/newrelic", + ] + pruneopts = "UT" + revision = "de0daeceb386cd36dffa4b0d288886f672402196" + version = "v3.3.0" + [[projects]] digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976" name = "github.com/sirupsen/logrus" @@ -128,7 +161,7 @@ [[projects]] branch = "master" - digest = "1:2f357867bf425774d35beca5be718402a4488b8b23b1563ce8c5bb91d09285a7" + digest = "1:bdb79e93c04bf9406eb285f6d3d85b884f71e73d68950d036be78c1b0c039689" name = "golang.org/x/net" packages = [ "context", @@ -141,11 +174,11 @@ "trace", ] pruneopts = "UT" - revision = "da137c7871d730100384dbcf36e6f8fa493aef5b" + revision = "0de0cce0169b09b364e001f108dc0399ea8630b3" [[projects]] branch = "master" - digest = "1:31e33f76456ccf54819ab4a646cf01271d1a99d7712ab84bf1a9e7b61cd2031b" + digest = "1:79edde3241bb55de9f4143d5083bfcff722e550c3cb8db94084eab50d0e440b5" name = "golang.org/x/oauth2" packages = [ ".", @@ -155,7 +188,7 @@ "jwt", ] pruneopts = "UT" - revision = "0f29369cfe4552d0e4bcddc57cc75f4d7e672a33" + revision = "bf48bf16ab8d622ce64ec6ce98d2c98f916b6303" [[projects]] branch = "master" @@ -163,15 +196,15 @@ name = "golang.org/x/sync" packages = ["semaphore"] pruneopts = "UT" - revision = "112230192c580c3556b8cee6403af37a4fc5f28c" + revision = "cd5d95a43a6e21273425c7ae415d3df9ea832eeb" [[projects]] branch = "master" - digest = "1:730ba27cd66db3b98ec8f51a6f20d45ec277d490cca36b1f54e31d3fcaf4840e" + digest = "1:578046baf093df15df2904bbb0fe06f3f2385bad59987532201007754aa7e1d5" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" - revision = "04f50cda93cbb67f2afa353c52f342100e80e625" + revision = "d5e6a3e2c0ae16fc7480523ebcb7fd4dd3215489" [[projects]] digest = "1:8d8faad6b12a3a4c819a3f9618cb6ee1fa1cfc33253abeeea8b55336721e3405" @@ -200,7 +233,7 @@ [[projects]] branch = "master" - digest = "1:bc06b12d8436550fccc0212037e9281a7e4d53db25c2349eb3cc6c3457e0406b" + digest = "1:223d8f4aae8092b14cdce7d897c7c9bf0865307c392e10892f53bc1d98348eb3" name = "google.golang.org/api" packages = [ "googleapi/transport", @@ -209,15 +242,16 @@ "option", "support/bundler", "transport", + "transport/cert", "transport/grpc", "transport/http", "transport/http/internal/propagation", ] pruneopts = "UT" - revision = "aae1d1b89c27132abe4fa22731a2a61e7089079c" + revision = "2ab33c207318199ace1a47b541fe24c0b6e2b0b3" [[projects]] - digest = "1:2c26b1c47556c0e5e73cdb05d8361c463737eee4baac35d38b40c728c3074a94" + digest = "1:c98e9b93e6d178378530b920fe6e1aa4b3dd4972872111e83827746aa1f33ded" name = "google.golang.org/appengine" packages = [ ".", @@ -234,12 +268,12 @@ "urlfetch", ] pruneopts = "UT" - revision = "b2f4a3cf3c67576a2ee09e1fe62656a5086ce880" - version = "v1.6.1" + revision = "971852bfffca25b069c31162ae8f247a3dba083b" + version = "v1.6.5" [[projects]] branch = "master" - digest = "1:cb0f37e3cdf50a27abbf33a48797b30786239d4fd69dbfbbc63cfa19d400d3ce" + digest = "1:bf950fbb11c183c2c5d3918402e89a76fa8d7e4d13873c3c91b04a942e7e0048" name = "google.golang.org/genproto" packages = [ "googleapis/api", @@ -253,16 +287,19 @@ "googleapis/monitoring/v3", "googleapis/rpc/errdetails", "googleapis/rpc/status", + "googleapis/type/calendarperiod", "protobuf/field_mask", ] pruneopts = "UT" - revision = "3bdd9d9f5532d75d09efb230bd767d265245cfe5" + revision = "46b91f19d98c8de158f0bac277af31327b3443d4" [[projects]] - digest = "1:f379776e36e55e5b5cbf7187ea58280812785071de53046230006e47894650b6" + digest = "1:6595b4790062c71ba85f3a816172ce800beffe392bd351d511b29ea25a303b80" name = "google.golang.org/grpc" packages = [ ".", + "attributes", + "backoff", "balancer", "balancer/base", "balancer/grpclb", @@ -290,10 +327,13 @@ "internal/backoff", "internal/balancerload", "internal/binarylog", + "internal/buffer", "internal/channelz", "internal/envconfig", "internal/grpcrand", "internal/grpcsync", + "internal/resolver/dns", + "internal/resolver/passthrough", "internal/syscall", "internal/transport", "keepalive", @@ -301,16 +341,14 @@ "naming", "peer", "resolver", - "resolver/dns", - "resolver/passthrough", "serviceconfig", "stats", "status", "tap", ] pruneopts = "UT" - revision = "1d89a3c832915b2314551c1d2a506874d62e53f7" - version = "v1.22.0" + revision = "f495f5b15ae7ccda3b38c53a1bfcde4c1a58a2bc" + version = "v1.27.1" [solve-meta] analyzer-name = "dep" @@ -322,6 +360,9 @@ "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/money", "github.com/golang/protobuf/proto", "github.com/google/uuid", + "github.com/newrelic/go-agent/v3/integrations/logcontext/nrlogrusplugin", + "github.com/newrelic/go-agent/v3/integrations/nrgrpc", + "github.com/newrelic/go-agent/v3/newrelic", "github.com/sirupsen/logrus", "go.opencensus.io/exporter/jaeger", "go.opencensus.io/plugin/ocgrpc", diff --git a/src/checkoutservice/Gopkg.toml b/src/checkoutservice/Gopkg.toml index abac03c..71efd87 100644 --- a/src/checkoutservice/Gopkg.toml +++ b/src/checkoutservice/Gopkg.toml @@ -53,6 +53,10 @@ branch = "master" name = "golang.org/x/net" +[[constraint]] + name = "github.com/newrelic/go-agent" + version = "3.3.0" + [prune] go-tests = true - unused-packages = true + unused-packages = true \ No newline at end of file diff --git a/src/checkoutservice/main.go b/src/checkoutservice/main.go index 891d10c..b438f38 100644 --- a/src/checkoutservice/main.go +++ b/src/checkoutservice/main.go @@ -15,6 +15,10 @@ package main import ( + "github.com/sirupsen/logrus" + "github.com/newrelic/go-agent/v3/integrations/logcontext/nrlogrusplugin" + "github.com/newrelic/go-agent/v3/newrelic" + "context" "fmt" "net" @@ -24,7 +28,6 @@ import ( "cloud.google.com/go/profiler" "contrib.go.opencensus.io/exporter/stackdriver" "github.com/google/uuid" - "github.com/sirupsen/logrus" "go.opencensus.io/exporter/jaeger" "go.opencensus.io/plugin/ocgrpc" "go.opencensus.io/stats/view" @@ -32,6 +35,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "github.com/newrelic/go-agent/v3/integrations/nrgrpc" pb "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/genproto" money "github.com/GoogleCloudPlatform/microservices-demo/src/checkoutservice/money" @@ -43,19 +47,30 @@ const ( usdCurrency = "USD" ) +var app *newrelic.Application var log *logrus.Logger func init() { + app, _ = newrelic.NewApplication( + func(config *newrelic.Config) { + // add more specific configuration of the agent within a custom ConfigOption + config.CrossApplicationTracer.Enabled = false + config.DistributedTracer.Enabled = true + }, + newrelic.ConfigAppName(os.Getenv("NEW_RELIC_APP_NAME")), + newrelic.ConfigLicense(os.Getenv("NEW_RELIC_LICENSE_KEY"))) + log = logrus.New() log.Level = logrus.DebugLevel - log.Formatter = &logrus.JSONFormatter{ - FieldMap: logrus.FieldMap{ - logrus.FieldKeyTime: "timestamp", - logrus.FieldKeyLevel: "severity", - logrus.FieldKeyMsg: "message", - }, - TimestampFormat: time.RFC3339Nano, - } + // log.Formatter = &logrus.JSONFormatter{ + // FieldMap: logrus.FieldMap{ + // logrus.FieldKeyTime: "timestamp", + // logrus.FieldKeyLevel: "severity", + // logrus.FieldKeyMsg: "message", + // }, + // TimestampFormat: time.RFC3339Nano, + // } + log.SetFormatter(nrlogrusplugin.ContextFormatter{}) log.Out = os.Stdout } @@ -106,7 +121,11 @@ func main() { var srv *grpc.Server if os.Getenv("DISABLE_STATS") == "" { log.Info("Stats enabled.") - srv = grpc.NewServer(grpc.StatsHandler(&ocgrpc.ServerHandler{})) + srv = grpc.NewServer( + // Add the New Relic gRPC server instrumentation + grpc.UnaryInterceptor(nrgrpc.UnaryServerInterceptor(app)), + grpc.StreamInterceptor(nrgrpc.StreamServerInterceptor(app)), + grpc.StatsHandler(&ocgrpc.ServerHandler{})) } else { log.Info("Stats disabled.") srv = grpc.NewServer() @@ -208,6 +227,7 @@ func mustMapEnv(target *string, envKey string) { } func (cs *checkoutService) Check(ctx context.Context, req *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "Check").End() return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil } @@ -216,17 +236,46 @@ func (cs *checkoutService) Watch(req *healthpb.HealthCheckRequest, ws healthpb.H } func (cs *checkoutService) PlaceOrder(ctx context.Context, req *pb.PlaceOrderRequest) (*pb.PlaceOrderResponse, error) { + txn := newrelic.FromContext(ctx) + defer txn.StartSegment("PlaceOrder").End() log.Infof("[PlaceOrder] user_id=%q user_currency=%q", req.UserId, req.UserCurrency) + txn.AddAttribute("email", req.Email) + orderID, err := uuid.NewUUID() if err != nil { + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "status": false, + "step": "Create Order ID"}) + txn.AddAttribute("status", "failed to generate order uuid") return nil, status.Errorf(codes.Internal, "failed to generate order uuid") } + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "status": true, + "step": "Create Order ID"}) prep, err := cs.prepareOrderItemsAndShippingQuoteFromCart(ctx, req.UserId, req.UserCurrency, req.Address) if err != nil { + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "status": false, + "step": "Prepare Order Items"}) + txn.AddAttribute("status", "failed to prepare order") return nil, status.Errorf(codes.Internal, err.Error()) } + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "status": true, + "step": "Prepare Order Items"}) total := pb.Money{CurrencyCode: req.UserCurrency, Units: 0, @@ -238,14 +287,46 @@ func (cs *checkoutService) PlaceOrder(ctx context.Context, req *pb.PlaceOrderReq txID, err := cs.chargeCard(ctx, &total, req.CreditCard) if err != nil { + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "status": false, + "step": "Charge Credit Card"}) + txn.AddAttribute("status", "failed to charge card") return nil, status.Errorf(codes.Internal, "failed to charge card: %+v", err) } + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "paymentId": txID, + "status": true, + "step": "Charge Credit Card"}) + + txn.AddAttribute("status", "payment went through") + txn.AddAttribute("paymentId", txID) log.Infof("payment went through (transaction_id: %s)", txID) shippingTrackingID, err := cs.shipOrder(ctx, req.Address, prep.cartItems) if err != nil { + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "paymentId": txID, + "status": false, + "step": "Process Shipment"}) + txn.AddAttribute("status", "shipping error") return nil, status.Errorf(codes.Unavailable, "shipping error: %+v", err) } + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "paymentId": txID, + "status": true, + "step": "Process Shipment"}) _ = cs.emptyUserCart(ctx, req.UserId) @@ -258,8 +339,24 @@ func (cs *checkoutService) PlaceOrder(ctx context.Context, req *pb.PlaceOrderReq } if err := cs.sendOrderConfirmation(ctx, req.Email, orderResult); err != nil { + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "paymentId": txID, + "status": false, + "step": "Send Confirmation"}) + txn.AddAttribute("feedback", "failed to send order confirmation") log.Warnf("failed to send order confirmation to %q: %+v", req.Email, err) } else { + txn.Application().RecordCustomEvent( + "BuyingFlow", map[string]interface{}{ + "email": req.Email, + "orderId": orderID.String(), + "paymentId": txID, + "status": true, + "step": "Send Confirmation"}) + txn.AddAttribute("feedback", "order confirmation email sent") log.Infof("order confirmation email sent to %q", req.Email) } resp := &pb.PlaceOrderResponse{Order: orderResult} @@ -273,6 +370,7 @@ type orderPrep struct { } func (cs *checkoutService) prepareOrderItemsAndShippingQuoteFromCart(ctx context.Context, userID, userCurrency string, address *pb.Address) (orderPrep, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "PrepareOrderItems").End() var out orderPrep cartItems, err := cs.getUserCart(ctx, userID) if err != nil { @@ -298,6 +396,7 @@ func (cs *checkoutService) prepareOrderItemsAndShippingQuoteFromCart(ctx context } func (cs *checkoutService) quoteShipping(ctx context.Context, address *pb.Address, items []*pb.CartItem) (*pb.Money, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "QuoteShipping").End() conn, err := grpc.DialContext(ctx, cs.shippingSvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) @@ -317,6 +416,7 @@ func (cs *checkoutService) quoteShipping(ctx context.Context, address *pb.Addres } func (cs *checkoutService) getUserCart(ctx context.Context, userID string) ([]*pb.CartItem, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "GetUserCart").End() conn, err := grpc.DialContext(ctx, cs.cartSvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) if err != nil { return nil, fmt.Errorf("could not connect cart service: %+v", err) @@ -331,6 +431,7 @@ func (cs *checkoutService) getUserCart(ctx context.Context, userID string) ([]*p } func (cs *checkoutService) emptyUserCart(ctx context.Context, userID string) error { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "EmptyUserCart").End() conn, err := grpc.DialContext(ctx, cs.cartSvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) if err != nil { return fmt.Errorf("could not connect cart service: %+v", err) @@ -344,6 +445,7 @@ func (cs *checkoutService) emptyUserCart(ctx context.Context, userID string) err } func (cs *checkoutService) prepOrderItems(ctx context.Context, items []*pb.CartItem, userCurrency string) ([]*pb.OrderItem, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "PrepareOrderItems").End() out := make([]*pb.OrderItem, len(items)) conn, err := grpc.DialContext(ctx, cs.productCatalogSvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) @@ -370,6 +472,7 @@ func (cs *checkoutService) prepOrderItems(ctx context.Context, items []*pb.CartI } func (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, toCurrency string) (*pb.Money, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "ConvertCurrency").End() conn, err := grpc.DialContext(ctx, cs.currencySvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) if err != nil { return nil, fmt.Errorf("could not connect currency service: %+v", err) @@ -385,6 +488,7 @@ func (cs *checkoutService) convertCurrency(ctx context.Context, from *pb.Money, } func (cs *checkoutService) chargeCard(ctx context.Context, amount *pb.Money, paymentInfo *pb.CreditCardInfo) (string, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "ChargeCard").End() conn, err := grpc.DialContext(ctx, cs.paymentSvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) if err != nil { return "", fmt.Errorf("failed to connect payment service: %+v", err) @@ -401,6 +505,7 @@ func (cs *checkoutService) chargeCard(ctx context.Context, amount *pb.Money, pay } func (cs *checkoutService) sendOrderConfirmation(ctx context.Context, email string, order *pb.OrderResult) error { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "SendOrderConfirmation").End() conn, err := grpc.DialContext(ctx, cs.emailSvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) if err != nil { return fmt.Errorf("failed to connect email service: %+v", err) @@ -413,6 +518,7 @@ func (cs *checkoutService) sendOrderConfirmation(ctx context.Context, email stri } func (cs *checkoutService) shipOrder(ctx context.Context, address *pb.Address, items []*pb.CartItem) (string, error) { + defer newrelic.StartSegment(newrelic.FromContext(ctx), "ShipOrder").End() conn, err := grpc.DialContext(ctx, cs.shippingSvcAddr, grpc.WithInsecure(), grpc.WithStatsHandler(&ocgrpc.ClientHandler{})) if err != nil { return "", fmt.Errorf("failed to connect email service: %+v", err)