소스 검색

add telemetry to preview environment deployment handlers (#3066)

Adding telemetry to preview environment creation handlers so we can start to get a sense for errors that are blocking deployments.

This is an initial introduction of telemetry that captures the request parameters and errors that cause us to exit. If we exit for a reason that is not our fault (like a PR being closed), then I add the attribute unsuccessful-exit-reason to the span. Future commits should extend the telemetry into the functions being called.
d-g-town 3 년 전
부모
커밋
9289808839

+ 38 - 14
api/server/handlers/environment/create_deployment_by_cluster.go

@@ -1,11 +1,12 @@
 package environment
 
 import (
-	"context"
 	"errors"
 	"fmt"
 	"net/http"
 
+	"github.com/porter-dev/porter/internal/telemetry"
+
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -33,21 +34,43 @@ func NewCreateDeploymentByClusterHandler(
 }
 
 func (c *CreateDeploymentByClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
-	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	ctx, span := telemetry.NewSpan(r.Context(), "serve-create-deployment-by-cluster")
+	defer span.End()
+
+	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: project.ID},
+		telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
+	)
 
 	request := &types.CreateDeploymentRequest{}
 
 	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		err := telemetry.Error(ctx, span, nil, "could not decode and validate request")
+		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "repo-owner", Value: request.RepoOwner},
+		telemetry.AttributeKV{Key: "repo-name", Value: request.RepoName},
+		telemetry.AttributeKV{Key: "namespace", Value: request.Namespace},
+		telemetry.AttributeKV{Key: "pull-request-id", Value: request.PullRequestID},
+		telemetry.AttributeKV{Key: "pr-name", Value: request.GitHubMetadata.PRName},
+		telemetry.AttributeKV{Key: "commit-sha", Value: request.GitHubMetadata.CommitSHA},
+		telemetry.AttributeKV{Key: "pr-branch-from", Value: request.GitHubMetadata.PRBranchFrom},
+		telemetry.AttributeKV{Key: "pr-branch-into", Value: request.GitHubMetadata.PRBranchInto},
+	)
+
 	// read the environment to get the environment id
 	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(
 		project.ID, cluster.ID, request.RepoOwner, request.RepoName,
 	)
-
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error reading environment by owner repo name")
+
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(
 				fmt.Errorf("error creating deployment: %w", errEnvironmentNotFound)),
@@ -61,21 +84,24 @@ func (c *CreateDeploymentByClusterHandler) ServeHTTP(w http.ResponseWriter, r *h
 
 	// create deployment on GitHub API
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
-
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error getting github client from environment")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	// add a check for Github PR status
 	prClosed, err := isGithubPRClosed(client, request.RepoOwner, request.RepoName, int(request.PullRequestID))
-
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error checking if github pr is closed")
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
 		return
 	}
 
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "pr-closed", Value: prClosed})
+
 	if prClosed {
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "unsuccessful-exit-reason", Value: "pr is closed"})
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 			fmt.Errorf("attempting to create deployment for a closed github PR"), http.StatusConflict,
 		))
@@ -83,8 +109,8 @@ func (c *CreateDeploymentByClusterHandler) ServeHTTP(w http.ResponseWriter, r *h
 	}
 
 	ghDeployment, err := createGithubDeployment(client, env, request.PRBranchFrom, request.ActionID)
-
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error creating github deployment object")
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusConflict))
 		return
 	}
@@ -103,20 +129,18 @@ func (c *CreateDeploymentByClusterHandler) ServeHTTP(w http.ResponseWriter, r *h
 		PRBranchFrom:   request.GitHubMetadata.PRBranchFrom,
 		PRBranchInto:   request.GitHubMetadata.PRBranchInto,
 	})
-
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error creating github deployment object")
 		// try to delete the GitHub deployment
-		_, err = client.Repositories.DeleteDeployment(
-			context.Background(),
+		_, deleteErr := client.Repositories.DeleteDeployment(
+			ctx,
 			env.GitRepoOwner,
 			env.GitRepoName,
 			ghDeployment.GetID(),
 		)
 
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("%v: %w", errGithubAPI, err),
-				http.StatusConflict))
-			return
+		if deleteErr != nil {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "delete-err", Value: deleteErr.Error()})
 		}
 
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error creating deployment: %w", err)))

+ 46 - 9
api/server/handlers/environment/enable_pull_request.go

@@ -14,6 +14,7 @@ import (
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/telemetry"
 	"gorm.io/gorm"
 )
 
@@ -34,17 +35,39 @@ func NewEnablePullRequestHandler(
 }
 
 func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
-	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	ctx, span := telemetry.NewSpan(r.Context(), "serve-enable-pull-request")
+	defer span.End()
+
+	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: project.ID},
+		telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
+	)
 
 	request := &types.PullRequest{}
 
 	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		_ = telemetry.Error(ctx, span, nil, "could not decode and validate request")
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "title", Value: request.Title},
+		telemetry.AttributeKV{Key: "number", Value: request.Number},
+		telemetry.AttributeKV{Key: "repo-ower", Value: request.RepoOwner},
+		telemetry.AttributeKV{Key: "repo-name", Value: request.RepoName},
+		telemetry.AttributeKV{Key: "branch-from", Value: request.BranchFrom},
+		telemetry.AttributeKV{Key: "branch-into", Value: request.BranchInto},
+		telemetry.AttributeKV{Key: "created-at", Value: request.CreatedAt},
+		telemetry.AttributeKV{Key: "updated-at", Value: request.UpdatedAt},
+	)
+
 	env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(project.ID, cluster.ID, request.RepoOwner, request.RepoName)
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error reading environment by owner repo name")
+
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("environment not found in cluster and project")))
 			return
@@ -67,6 +90,8 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		}
 
 		if !found {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "unsuccessful-exit-reason", Value: "cannot find branch-into in git repo branches"})
+
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("base branch '%s' is not enabled for this preview environment, please enable it "+
 					"in the settings page to continue", request.BranchInto), http.StatusBadRequest,
@@ -84,6 +109,8 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		}
 
 		if found {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "unsuccessful-exit-reason", Value: "cannot find branch-from in git deploy branches"})
+
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf("head branch '%s' is enabled for branch deploys for this preview environment, "+
 					"please disable it in the settings page to continue", request.BranchInto), http.StatusBadRequest,
@@ -94,26 +121,32 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	client, err := getGithubClientFromEnvironment(c.Config(), env)
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error getting github client from environment")
+
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
 
 	// add an extra check that the installation has permission to read this pull request
-	pr, _, err := client.PullRequests.Get(r.Context(), env.GitRepoOwner, env.GitRepoName, int(request.Number))
+	pr, _, err := client.PullRequests.Get(ctx, env.GitRepoOwner, env.GitRepoName, int(request.Number))
 	if err != nil {
+		_ = telemetry.Error(ctx, span, err, "error getting pull request")
+
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("%v: %w", errGithubAPI, err),
 			http.StatusConflict))
 		return
 	}
 
 	if pr.GetState() == "closed" {
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "unsuccessful-exit-reason", Value: "pr is closed"})
+
 		c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("cannot enable deployment for closed PR"),
 			http.StatusConflict))
 		return
 	}
 
 	ghResp, err := client.Actions.CreateWorkflowDispatchEventByFileName(
-		r.Context(), env.GitRepoOwner, env.GitRepoName, fmt.Sprintf("porter_%s_env.yml", env.Name),
+		ctx, env.GitRepoOwner, env.GitRepoName, fmt.Sprintf("porter_%s_env.yml", env.Name),
 		github.CreateWorkflowDispatchEventRequest{
 			Ref: request.BranchFrom,
 			Inputs: map[string]interface{}{
@@ -124,9 +157,16 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 			},
 		},
 	)
+	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error creating workflow dispatch event")
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
+		return
+	}
 
 	if ghResp != nil {
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "github-status-code", Value: ghResp.StatusCode})
 		if ghResp.StatusCode == 404 {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "unsuccessful-exit-reason", Value: "bad github status code"})
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf(
 					"please make sure the preview environment workflow files are present in PR branch %s and are up to"+
@@ -135,6 +175,7 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 			)
 			return
 		} else if ghResp.StatusCode == 422 {
+			telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "unsuccessful-exit-reason", Value: "bad github status code"})
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 				fmt.Errorf(
 					"please make sure the workflow files in PR branch %s are up to date with the default branch",
@@ -145,11 +186,6 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		}
 	}
 
-	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
-		return
-	}
-
 	// create the deployment
 	depl, err := c.Repo().Environment().CreateDeployment(&models.Deployment{
 		EnvironmentID: env.ID,
@@ -163,6 +199,7 @@ func (c *EnablePullRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		PRBranchInto:  request.BranchInto,
 	})
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "error creating deployment in repo")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}

+ 16 - 3
api/server/handlers/environment/get_environment.go

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"net/http"
 
+	"github.com/porter-dev/porter/internal/telemetry"
+
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
 	"github.com/porter-dev/porter/api/server/shared/apierrors"
@@ -29,18 +31,29 @@ func NewGetEnvironmentHandler(
 }
 
 func (c *GetEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
-	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	ctx, span := telemetry.NewSpan(r.Context(), "serve-get-environment")
+	defer span.End()
 
-	envID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
+	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: project.ID},
+		telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
+	)
 
+	envID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
 	if reqErr != nil {
+		_ = telemetry.Error(ctx, span, reqErr, "could not get environment id from url")
 		c.HandleAPIError(w, r, reqErr)
 		return
 	}
 
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "environment-id", Value: envID})
+
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, envID)
 	if err != nil {
+		_ = telemetry.Error(ctx, span, err, "could not read environment by id")
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such environment with ID: %d", envID)))
 			return

+ 33 - 2
api/server/handlers/environment/update_environment_settings.go

@@ -8,6 +8,8 @@ import (
 	"reflect"
 	"strings"
 
+	"github.com/porter-dev/porter/internal/telemetry"
+
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
 	"github.com/porter-dev/porter/api/server/shared"
@@ -36,24 +38,44 @@ func NewUpdateEnvironmentSettingsHandler(
 }
 
 func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
-	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
+	ctx, span := telemetry.NewSpan(r.Context(), "serve-update-environment-settings")
+	defer span.End()
+
+	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
+	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
+
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "project-id", Value: project.ID},
+		telemetry.AttributeKV{Key: "cluster-id", Value: cluster.ID},
+	)
 
 	envID, reqErr := requestutils.GetURLParamUint(r, "environment_id")
 
 	if reqErr != nil {
+		_ = telemetry.Error(ctx, span, reqErr, "could not get environment id from url")
 		c.HandleAPIError(w, r, reqErr)
 		return
 	}
 
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "environment-id", Value: envID})
+
 	request := &types.UpdateEnvironmentSettingsRequest{}
 
 	if ok := c.DecodeAndValidate(w, r, request); !ok {
+		_ = telemetry.Error(ctx, span, nil, "could not decode and validate request")
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "mode", Value: request.Mode},
+		telemetry.AttributeKV{Key: "git-repo-branches", Value: request.GitRepoBranches},
+		telemetry.AttributeKV{Key: "git-deploy-branches", Value: request.GitDeployBranches},
+	)
+
 	env, err := c.Repo().Environment().ReadEnvironmentByID(project.ID, cluster.ID, envID)
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "could not read environment by id")
+
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such environment with ID: %d", envID)))
 			return
@@ -91,10 +113,13 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 
 	changed = !reflect.DeepEqual(env.ToEnvironmentType().GitDeployBranches, newBranches)
 
+	telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "changed", Value: changed})
+
 	if changed {
 		// let us check if the webhook has access to the "push" event
 		client, err := getGithubClientFromEnvironment(c.Config(), env)
 		if err != nil {
+			err = telemetry.Error(ctx, span, err, "could not get github client")
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}
@@ -103,6 +128,7 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 			context.Background(), env.GitRepoOwner, env.GitRepoName, env.GithubWebhookID,
 		)
 		if err != nil {
+			err = telemetry.Error(ctx, span, err, "could not get hook")
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}
@@ -116,6 +142,8 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 			}
 		}
 
+		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "found", Value: found})
+
 		if !found {
 			hook.Events = append(hook.Events, "push")
 
@@ -123,6 +151,7 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 				context.Background(), env.GitRepoOwner, env.GitRepoName, env.GithubWebhookID, hook,
 			)
 			if err != nil {
+				err = telemetry.Error(ctx, span, err, "could not edit hook")
 				c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 				return
 			}
@@ -140,6 +169,7 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 					errString += ": " + e.Error()
 				}
 
+				_ = telemetry.Error(ctx, span, errors.New(errString), "could not auto deploy branch")
 				c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
 					fmt.Errorf("error auto deploying preview branches: %s", errString), http.StatusConflict),
 				)
@@ -178,6 +208,7 @@ func (c *UpdateEnvironmentSettingsHandler) ServeHTTP(w http.ResponseWriter, r *h
 		env, err = c.Repo().Environment().UpdateEnvironment(env)
 
 		if err != nil {
+			err = telemetry.Error(ctx, span, err, "could not update environment")
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return
 		}

+ 14 - 0
api/server/handlers/gitinstallation/list_branches.go

@@ -5,6 +5,8 @@ import (
 	"net/http"
 	"sync"
 
+	"github.com/porter-dev/porter/internal/telemetry"
+
 	"github.com/google/go-github/v41/github"
 	"github.com/porter-dev/porter/api/server/authz"
 	"github.com/porter-dev/porter/api/server/handlers"
@@ -30,14 +32,24 @@ func NewGithubListBranchesHandler(
 }
 
 func (c *GithubListBranchesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	ctx, span := telemetry.NewSpan(r.Context(), "serve-list-github-branches")
+	defer span.End()
+
 	owner, name, ok := commonutils.GetOwnerAndNameParams(c, w, r)
 
 	if !ok {
+		_ = telemetry.Error(ctx, span, nil, "could not get owner and name from request")
 		return
 	}
 
+	telemetry.WithAttributes(span,
+		telemetry.AttributeKV{Key: "owner", Value: owner},
+		telemetry.AttributeKV{Key: "name", Value: name},
+	)
+
 	client, err := GetGithubAppClientFromRequest(c.Config(), r)
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "could not get github app client")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
@@ -49,6 +61,7 @@ func (c *GithubListBranchesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 		},
 	})
 	if err != nil {
+		err = telemetry.Error(ctx, span, err, "could not list branches")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}
@@ -104,6 +117,7 @@ func (c *GithubListBranchesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	wg.Wait()
 
 	if workerErr != nil {
+		err = telemetry.Error(ctx, span, workerErr, "worker error listing github branches")
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}

+ 0 - 3
api/server/handlers/release/create.go

@@ -51,9 +51,6 @@ func NewCreateReleaseHandler(
 }
 
 func (c *CreateReleaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	tracer, _ := telemetry.InitTracer(r.Context(), c.Config().TelemetryConfig)
-	defer tracer.Shutdown()
-
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
 	cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
 	namespace := r.Context().Value(types.NamespaceScope).(string)

+ 0 - 3
api/server/handlers/stacks/create_porter_app.go

@@ -40,9 +40,6 @@ func NewCreatePorterAppHandler(
 }
 
 func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	tracer, _ := telemetry.InitTracer(r.Context(), c.Config().TelemetryConfig)
-	defer tracer.Shutdown()
-
 	ctx := r.Context()
 	project, _ := ctx.Value(types.ProjectScope).(*models.Project)
 	cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)

+ 6 - 0
cmd/app/main.go

@@ -3,6 +3,7 @@
 package main
 
 import (
+	"context"
 	"errors"
 	"flag"
 	"fmt"
@@ -10,6 +11,8 @@ import (
 	"net/http"
 	"os"
 
+	"github.com/porter-dev/porter/internal/telemetry"
+
 	"github.com/porter-dev/porter/api/server/router"
 	"github.com/porter-dev/porter/api/server/shared/config"
 	"github.com/porter-dev/porter/api/server/shared/config/loader"
@@ -61,6 +64,9 @@ func main() {
 		IdleTimeout:  config.ServerConf.TimeoutIdle,
 	}
 
+	tracer, _ := telemetry.InitTracer(context.Background(), config.TelemetryConfig)
+	defer tracer.Shutdown()
+
 	if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
 		config.Logger.Fatal().Err(err).Msg("Server startup failed")
 	}

+ 0 - 8
ee/api/server/handlers/invite/create.go

@@ -4,7 +4,6 @@
 package invite
 
 import (
-	"context"
 	"fmt"
 	"net/http"
 	"time"
@@ -36,13 +35,6 @@ func NewInviteCreateHandler(
 }
 
 func (c *InviteCreateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	tracer, _ := telemetry.InitTracer(context.Background(), c.Config().TelemetryConfig)
-	defer tracer.Shutdown()
-
-	// just for demonstration purposes
-	ctx, span := telemetry.NewSpan(r.Context(), "serve-create-invite")
-	defer span.End()
-
 	user, _ := r.Context().Value(types.UserScope).(*models.User)
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 

+ 9 - 0
internal/telemetry/span.go

@@ -6,6 +6,8 @@ import (
 	"errors"
 	"fmt"
 	"runtime"
+	"strings"
+	"time"
 
 	"github.com/google/uuid"
 	"go.opentelemetry.io/otel"
@@ -62,6 +64,8 @@ func WithAttributes(span trace.Span, attrs ...AttributeKV) {
 				span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String()))
 			case string:
 				span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val))
+			case []string:
+				span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), strings.Join(val, ", ")))
 			case sql.NullString:
 				if val.Valid {
 					span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String))
@@ -80,6 +84,11 @@ func WithAttributes(span trace.Span, attrs ...AttributeKV) {
 				span.SetAttributes(attribute.Float64(prefixSpanKey(string(attr.Key)), val))
 			case bool:
 				span.SetAttributes(attribute.Bool(prefixSpanKey(string(attr.Key)), val))
+			case time.Time:
+				span.SetAttributes(attribute.String(prefixSpanKey(string(attr.Key)), val.String()))
+				zone, offset := val.Zone()
+				span.SetAttributes(attribute.String(prefixSpanKey(fmt.Sprintf("%s-timezone", string(attr.Key))), zone))
+				span.SetAttributes(attribute.Int(prefixSpanKey(fmt.Sprintf("%s-offset", string(attr.Key))), offset))
 			}
 		}
 	}