Monorepo for Tangled tangled.org

appview: instrument all the things

anirudh.fi df653304 5c8b5c1f

verified
+31 -6
appview/db/issues.go
··· 1 1 package db 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 5 6 "time" 6 7 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 + "go.opentelemetry.io/otel" 10 + "go.opentelemetry.io/otel/attribute" 8 11 "tangled.sh/tangled.sh/core/appview/pagination" 9 12 ) 10 13 ··· 103 106 return ownerDid, err 104 107 } 105 108 106 - func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) { 109 + func GetIssues(ctx context.Context, e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) { 110 + ctx, span := otel.Tracer("db").Start(ctx, "GetIssues") 111 + defer span.End() 112 + 113 + span.SetAttributes( 114 + attribute.String("repo_at", repoAt.String()), 115 + attribute.Bool("is_open", isOpen), 116 + attribute.Int("page.offset", page.Offset), 117 + attribute.Int("page.limit", page.Limit), 118 + ) 119 + 107 120 var issues []Issue 108 121 openValue := 0 109 122 if isOpen { 110 123 openValue = 1 111 124 } 112 125 113 - rows, err := e.Query( 126 + rows, err := e.QueryContext( 127 + ctx, 114 128 ` 115 129 with numbered_issue as ( 116 130 select ··· 139 153 body, 140 154 open, 141 155 comment_count 142 - from 156 + from 143 157 numbered_issue 144 - where 158 + where 145 159 row_num between ? and ?`, 146 160 repoAt, openValue, page.Offset+1, page.Offset+page.Limit) 147 161 if err != nil { 162 + span.RecordError(err) 148 163 return nil, err 149 164 } 150 165 defer rows.Close() ··· 155 170 var metadata IssueMetadata 156 171 err := rows.Scan(&issue.OwnerDid, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount) 157 172 if err != nil { 173 + span.RecordError(err) 158 174 return nil, err 159 175 } 160 176 161 177 createdTime, err := time.Parse(time.RFC3339, createdAt) 162 178 if err != nil { 179 + span.RecordError(err) 163 180 return nil, err 164 181 } 165 182 issue.Created = createdTime ··· 169 186 } 170 187 171 188 if err := rows.Err(); err != nil { 189 + span.RecordError(err) 172 190 return nil, err 173 191 } 174 192 193 + span.SetAttributes(attribute.Int("issues.count", len(issues))) 175 194 return issues, nil 176 195 } 177 196 ··· 256 275 return issues, nil 257 276 } 258 277 259 - func GetIssue(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) { 278 + func GetIssue(ctx context.Context, e Execer, repoAt syntax.ATURI, issueId int) (*Issue, error) { 279 + ctx, span := otel.Tracer("db").Start(ctx, "GetIssue") 280 + defer span.End() 281 + 260 282 query := `select owner_did, created, title, body, open from issues where repo_at = ? and issue_id = ?` 261 283 row := e.QueryRow(query, repoAt, issueId) 262 284 ··· 276 298 return &issue, nil 277 299 } 278 300 279 - func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) { 301 + func GetIssueWithComments(ctx context.Context, e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) { 302 + ctx, span := otel.Tracer("db").Start(ctx, "GetIssueWithComments") 303 + defer span.End() 304 + 280 305 query := `select owner_did, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?` 281 306 row := e.QueryRow(query, repoAt, issueId) 282 307
+29 -3
appview/db/profile.go
··· 1 1 package db 2 2 3 3 import ( 4 + "context" 4 5 "fmt" 5 6 "time" 7 + 8 + "go.opentelemetry.io/otel/attribute" 9 + "go.opentelemetry.io/otel/codes" 10 + "go.opentelemetry.io/otel/trace" 6 11 ) 7 12 8 13 type RepoEvent struct { ··· 83 88 84 89 const TimeframeMonths = 7 85 90 86 - func MakeProfileTimeline(e Execer, forDid string) (*ProfileTimeline, error) { 91 + func MakeProfileTimeline(ctx context.Context, e Execer, forDid string) (*ProfileTimeline, error) { 92 + span := trace.SpanFromContext(ctx) 93 + defer span.End() 94 + 95 + span.SetAttributes( 96 + attribute.String("forDid", forDid), 97 + ) 98 + 87 99 timeline := ProfileTimeline{ 88 100 ByMonth: make([]ByMonth, TimeframeMonths), 89 101 } ··· 92 104 93 105 pulls, err := GetPullsByOwnerDid(e, forDid, timeframe) 94 106 if err != nil { 107 + span.RecordError(err) 108 + span.SetStatus(codes.Error, "error getting pulls by owner did") 95 109 return nil, fmt.Errorf("error getting pulls by owner did: %w", err) 96 110 } 97 111 112 + span.SetAttributes(attribute.Int("pulls.count", len(pulls))) 113 + 98 114 // group pulls by month 99 115 for _, pull := range pulls { 100 116 pullMonth := pull.Created.Month() ··· 112 128 113 129 issues, err := GetIssuesByOwnerDid(e, forDid, timeframe) 114 130 if err != nil { 131 + span.RecordError(err) 132 + span.SetStatus(codes.Error, "error getting issues by owner did") 115 133 return nil, fmt.Errorf("error getting issues by owner did: %w", err) 116 134 } 117 135 136 + span.SetAttributes(attribute.Int("issues.count", len(issues))) 137 + 118 138 for _, issue := range issues { 119 139 issueMonth := issue.Created.Month() 120 140 ··· 129 149 *items = append(*items, &issue) 130 150 } 131 151 132 - repos, err := GetAllReposByDid(e, forDid) 152 + repos, err := GetAllReposByDid(ctx, e, forDid) 133 153 if err != nil { 154 + span.RecordError(err) 155 + span.SetStatus(codes.Error, "error getting all repos by did") 134 156 return nil, fmt.Errorf("error getting all repos by did: %w", err) 135 157 } 158 + 159 + span.SetAttributes(attribute.Int("repos.count", len(repos))) 136 160 137 161 for _, repo := range repos { 138 162 // TODO: get this in the original query; requires COALESCE because nullable 139 163 var sourceRepo *Repo 140 164 if repo.Source != "" { 141 - sourceRepo, err = GetRepoByAtUri(e, repo.Source) 165 + sourceRepo, err = GetRepoByAtUri(ctx, e, repo.Source) 142 166 if err != nil { 167 + span.RecordError(err) 168 + span.SetStatus(codes.Error, "error getting repo by at uri") 143 169 return nil, err 144 170 } 145 171 }
+111 -25
appview/db/pulls.go
··· 1 1 package db 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 5 6 "fmt" 6 7 "log" ··· 10 11 11 12 "github.com/bluekeyes/go-gitdiff/gitdiff" 12 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 + "go.opentelemetry.io/otel/attribute" 15 + "go.opentelemetry.io/otel/trace" 13 16 "tangled.sh/tangled.sh/core/api/tangled" 14 17 "tangled.sh/tangled.sh/core/patchutil" 15 18 "tangled.sh/tangled.sh/core/types" ··· 234 237 return patches 235 238 } 236 239 237 - func NewPull(tx *sql.Tx, pull *Pull) error { 240 + func NewPull(ctx context.Context, tx *sql.Tx, pull *Pull) error { 241 + span := trace.SpanFromContext(ctx) 242 + defer span.End() 243 + 244 + span.SetAttributes( 245 + attribute.String("repo.at", pull.RepoAt.String()), 246 + attribute.String("owner.did", pull.OwnerDid), 247 + attribute.String("title", pull.Title), 248 + attribute.String("target_branch", pull.TargetBranch), 249 + ) 250 + span.AddEvent("creating new pull request") 251 + 238 252 defer tx.Rollback() 239 253 240 254 _, err := tx.Exec(` ··· 242 256 values (?, 1) 243 257 `, pull.RepoAt) 244 258 if err != nil { 259 + span.RecordError(err) 245 260 return err 246 261 } 247 262 ··· 253 268 returning next_pull_id - 1 254 269 `, pull.RepoAt).Scan(&nextId) 255 270 if err != nil { 271 + span.RecordError(err) 256 272 return err 257 273 } 258 274 259 275 pull.PullId = nextId 260 276 pull.State = PullOpen 261 277 278 + span.SetAttributes(attribute.Int("pull.id", pull.PullId)) 279 + span.AddEvent("assigned pull ID") 280 + 262 281 var sourceBranch, sourceRepoAt *string 263 282 if pull.PullSource != nil { 264 283 sourceBranch = &pull.PullSource.Branch ··· 284 303 sourceRepoAt, 285 304 ) 286 305 if err != nil { 306 + span.RecordError(err) 287 307 return err 288 308 } 309 + 310 + span.AddEvent("inserted pull record") 289 311 290 312 _, err = tx.Exec(` 291 313 insert into pull_submissions (pull_id, repo_at, round_number, patch, source_rev) 292 314 values (?, ?, ?, ?, ?) 293 315 `, pull.PullId, pull.RepoAt, 0, pull.Submissions[0].Patch, pull.Submissions[0].SourceRev) 294 316 if err != nil { 317 + span.RecordError(err) 295 318 return err 296 319 } 320 + 321 + span.AddEvent("inserted initial pull submission") 297 322 298 323 if err := tx.Commit(); err != nil { 324 + span.RecordError(err) 299 325 return err 300 326 } 301 327 328 + span.AddEvent("transaction committed successfully") 302 329 return nil 303 330 } 304 331 305 - func GetPullAt(e Execer, repoAt syntax.ATURI, pullId int) (syntax.ATURI, error) { 306 - pull, err := GetPull(e, repoAt, pullId) 332 + func GetPullAt(ctx context.Context, e Execer, repoAt syntax.ATURI, pullId int) (syntax.ATURI, error) { 333 + pull, err := GetPull(ctx, e, repoAt, pullId) 307 334 if err != nil { 308 335 return "", err 309 336 } ··· 316 343 return pullId - 1, err 317 344 } 318 345 319 - func GetPulls(e Execer, repoAt syntax.ATURI, state PullState) ([]*Pull, error) { 346 + func GetPulls(ctx context.Context, e Execer, repoAt syntax.ATURI, state PullState) ([]*Pull, error) { 347 + span := trace.SpanFromContext(ctx) 348 + defer span.End() 349 + 350 + span.SetAttributes( 351 + attribute.String("repoAt", repoAt.String()), 352 + attribute.String("state", state.String()), 353 + ) 354 + span.AddEvent("querying pulls") 355 + 320 356 pulls := make(map[int]*Pull) 321 357 322 - rows, err := e.Query(` 358 + rows, err := e.QueryContext(ctx, ` 323 359 select 324 360 owner_did, 325 361 pull_id, ··· 336 372 where 337 373 repo_at = ? and state = ?`, repoAt, state) 338 374 if err != nil { 375 + span.RecordError(err) 339 376 return nil, err 340 377 } 341 378 defer rows.Close() ··· 357 394 &sourceRepoAt, 358 395 ) 359 396 if err != nil { 397 + span.RecordError(err) 360 398 return nil, err 361 399 } 362 400 363 401 createdTime, err := time.Parse(time.RFC3339, createdAt) 364 402 if err != nil { 403 + span.RecordError(err) 365 404 return nil, err 366 405 } 367 406 pull.Created = createdTime ··· 373 412 if sourceRepoAt.Valid { 374 413 sourceRepoAtParsed, err := syntax.ParseATURI(sourceRepoAt.String) 375 414 if err != nil { 415 + span.RecordError(err) 376 416 return nil, err 377 417 } 378 418 pull.PullSource.RepoAt = &sourceRepoAtParsed ··· 382 422 pulls[pull.PullId] = &pull 383 423 } 384 424 425 + span.AddEvent("querying pull submissions") 426 + span.SetAttributes(attribute.Int("pull_count", len(pulls))) 427 + 385 428 // get latest round no. for each pull 386 429 inClause := strings.TrimSuffix(strings.Repeat("?, ", len(pulls)), ", ") 387 430 submissionsQuery := fmt.Sprintf(` ··· 400 443 args[idx] = p.PullId 401 444 idx += 1 402 445 } 403 - submissionsRows, err := e.Query(submissionsQuery, args...) 446 + submissionsRows, err := e.QueryContext(ctx, submissionsQuery, args...) 404 447 if err != nil { 448 + span.RecordError(err) 405 449 return nil, err 406 450 } 407 451 defer submissionsRows.Close() ··· 414 458 &s.RoundNumber, 415 459 ) 416 460 if err != nil { 461 + span.RecordError(err) 417 462 return nil, err 418 463 } 419 464 ··· 423 468 } 424 469 } 425 470 if err := rows.Err(); err != nil { 471 + span.RecordError(err) 426 472 return nil, err 427 473 } 474 + 475 + span.AddEvent("querying pull comments") 428 476 429 477 // get comment count on latest submission on each pull 430 478 inClause = strings.TrimSuffix(strings.Repeat("?, ", len(pulls)), ", ") ··· 443 491 for _, p := range pulls { 444 492 args = append(args, p.Submissions[p.LastRoundNumber()].ID) 445 493 } 446 - commentsRows, err := e.Query(commentsQuery, args...) 494 + commentsRows, err := e.QueryContext(ctx, commentsQuery, args...) 447 495 if err != nil { 496 + span.RecordError(err) 448 497 return nil, err 449 498 } 450 499 defer commentsRows.Close() ··· 456 505 &pullId, 457 506 ) 458 507 if err != nil { 508 + span.RecordError(err) 459 509 return nil, err 460 510 } 461 511 if p, ok := pulls[pullId]; ok { ··· 463 513 } 464 514 } 465 515 if err := rows.Err(); err != nil { 516 + span.RecordError(err) 466 517 return nil, err 467 518 } 519 + 520 + span.AddEvent("sorting pulls by date") 468 521 469 522 orderedByDate := []*Pull{} 470 523 for _, p := range pulls { ··· 474 527 return orderedByDate[i].Created.After(orderedByDate[j].Created) 475 528 }) 476 529 530 + span.SetAttributes(attribute.Int("result_count", len(orderedByDate))) 477 531 return orderedByDate, nil 478 532 } 479 533 480 - func GetPull(e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) { 534 + func GetPull(ctx context.Context, e Execer, repoAt syntax.ATURI, pullId int) (*Pull, error) { 535 + span := trace.SpanFromContext(ctx) 536 + defer span.End() 537 + 538 + span.SetAttributes(attribute.String("repoAt", repoAt.String()), attribute.Int("pull.id", pullId)) 539 + span.AddEvent("query pull metadata") 540 + 481 541 query := ` 482 542 select 483 543 owner_did, ··· 496 556 where 497 557 repo_at = ? and pull_id = ? 498 558 ` 499 - row := e.QueryRow(query, repoAt, pullId) 559 + row := e.QueryRowContext(ctx, query, repoAt, pullId) 500 560 501 561 var pull Pull 502 562 var createdAt string ··· 515 575 &sourceRepoAt, 516 576 ) 517 577 if err != nil { 578 + span.RecordError(err) 518 579 return nil, err 519 580 } 520 581 521 582 createdTime, err := time.Parse(time.RFC3339, createdAt) 522 583 if err != nil { 584 + span.RecordError(err) 523 585 return nil, err 524 586 } 525 587 pull.Created = createdTime 526 588 527 - // populate source 528 589 if sourceBranch.Valid { 529 590 pull.PullSource = &PullSource{ 530 591 Branch: sourceBranch.String, ··· 532 593 if sourceRepoAt.Valid { 533 594 sourceRepoAtParsed, err := syntax.ParseATURI(sourceRepoAt.String) 534 595 if err != nil { 596 + span.RecordError(err) 535 597 return nil, err 536 598 } 537 599 pull.PullSource.RepoAt = &sourceRepoAtParsed 538 600 } 539 601 } 540 602 603 + span.AddEvent("query submissions") 541 604 submissionsQuery := ` 542 605 select 543 606 id, pull_id, repo_at, round_number, patch, created, source_rev ··· 546 609 where 547 610 repo_at = ? and pull_id = ? 548 611 ` 549 - submissionsRows, err := e.Query(submissionsQuery, repoAt, pullId) 612 + submissionsRows, err := e.QueryContext(ctx, submissionsQuery, repoAt, pullId) 550 613 if err != nil { 614 + span.RecordError(err) 551 615 return nil, err 552 616 } 553 617 defer submissionsRows.Close() ··· 568 632 &submissionSourceRev, 569 633 ) 570 634 if err != nil { 635 + span.RecordError(err) 571 636 return nil, err 572 637 } 573 638 574 639 submissionCreatedTime, err := time.Parse(time.RFC3339, submissionCreatedStr) 575 640 if err != nil { 641 + span.RecordError(err) 576 642 return nil, err 577 643 } 578 644 submission.Created = submissionCreatedTime ··· 584 650 submissionsMap[submission.ID] = &submission 585 651 } 586 652 if err = submissionsRows.Close(); err != nil { 653 + span.RecordError(err) 587 654 return nil, err 588 655 } 589 656 if len(submissionsMap) == 0 { ··· 595 662 args = append(args, k) 596 663 } 597 664 inClause := strings.TrimSuffix(strings.Repeat("?, ", len(submissionsMap)), ", ") 665 + 666 + span.AddEvent("query comments") 598 667 commentsQuery := fmt.Sprintf(` 599 668 select 600 669 id, ··· 612 681 order by 613 682 created asc 614 683 `, inClause) 615 - commentsRows, err := e.Query(commentsQuery, args...) 684 + commentsRows, err := e.QueryContext(ctx, commentsQuery, args...) 616 685 if err != nil { 686 + span.RecordError(err) 617 687 return nil, err 618 688 } 619 689 defer commentsRows.Close() ··· 632 702 &commentCreatedStr, 633 703 ) 634 704 if err != nil { 705 + span.RecordError(err) 635 706 return nil, err 636 707 } 637 708 638 709 commentCreatedTime, err := time.Parse(time.RFC3339, commentCreatedStr) 639 710 if err != nil { 711 + span.RecordError(err) 640 712 return nil, err 641 713 } 642 714 comment.Created = commentCreatedTime 643 715 644 - // Add the comment to its submission 645 716 if submission, ok := submissionsMap[comment.SubmissionId]; ok { 646 717 submission.Comments = append(submission.Comments, comment) 647 718 } 648 - 649 719 } 650 720 if err = commentsRows.Err(); err != nil { 721 + span.RecordError(err) 651 722 return nil, err 652 723 } 653 724 654 - var pullSourceRepo *Repo 655 - if pull.PullSource != nil { 656 - if pull.PullSource.RepoAt != nil { 657 - pullSourceRepo, err = GetRepoByAtUri(e, pull.PullSource.RepoAt.String()) 658 - if err != nil { 659 - log.Printf("failed to get repo by at uri: %v", err) 660 - } else { 661 - pull.PullSource.Repo = pullSourceRepo 662 - } 725 + if pull.PullSource != nil && pull.PullSource.RepoAt != nil { 726 + span.AddEvent("query pull source repo") 727 + pullSourceRepo, err := GetRepoByAtUri(ctx, e, pull.PullSource.RepoAt.String()) 728 + if err != nil { 729 + span.RecordError(err) 730 + log.Printf("failed to get repo by at uri: %v", err) 731 + } else { 732 + pull.PullSource.Repo = pullSourceRepo 663 733 } 664 734 } 665 735 ··· 747 817 return pulls, nil 748 818 } 749 819 750 - func NewPullComment(e Execer, comment *PullComment) (int64, error) { 820 + func NewPullComment(ctx context.Context, e Execer, comment *PullComment) (int64, error) { 821 + span := trace.SpanFromContext(ctx) 822 + defer span.End() 823 + 824 + span.SetAttributes( 825 + attribute.String("repo.at", comment.RepoAt), 826 + attribute.Int("pull.id", comment.PullId), 827 + attribute.Int("submission.id", comment.SubmissionId), 828 + attribute.String("owner.did", comment.OwnerDid), 829 + ) 830 + span.AddEvent("inserting new pull comment") 831 + 751 832 query := `insert into pull_comments (owner_did, repo_at, submission_id, comment_at, pull_id, body) values (?, ?, ?, ?, ?, ?)` 752 - res, err := e.Exec( 833 + res, err := e.ExecContext( 834 + ctx, 753 835 query, 754 836 comment.OwnerDid, 755 837 comment.RepoAt, ··· 759 841 comment.Body, 760 842 ) 761 843 if err != nil { 844 + span.RecordError(err) 762 845 return 0, err 763 846 } 764 847 765 848 i, err := res.LastInsertId() 766 849 if err != nil { 850 + span.RecordError(err) 767 851 return 0, err 768 852 } 769 853 854 + span.SetAttributes(attribute.Int64("comment.id", i)) 855 + span.AddEvent("pull comment created successfully") 770 856 return i, nil 771 857 } 772 858
+114 -12
appview/db/repos.go
··· 1 1 package db 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 5 6 "time" 6 7 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 + "go.opentelemetry.io/otel" 10 + "go.opentelemetry.io/otel/attribute" 8 11 ) 9 12 10 13 type Repo struct { ··· 23 26 Source string 24 27 } 25 28 26 - func GetAllRepos(e Execer, limit int) ([]Repo, error) { 29 + func GetAllRepos(ctx context.Context, e Execer, limit int) ([]Repo, error) { 30 + ctx, span := otel.Tracer("db").Start(ctx, "GetAllRepos") 31 + defer span.End() 32 + span.SetAttributes(attribute.Int("limit", limit)) 33 + 27 34 var repos []Repo 28 35 29 36 rows, err := e.Query( ··· 35 42 limit, 36 43 ) 37 44 if err != nil { 45 + span.RecordError(err) 38 46 return nil, err 39 47 } 40 48 defer rows.Close() ··· 45 53 rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created, &repo.Source, 46 54 ) 47 55 if err != nil { 56 + span.RecordError(err) 48 57 return nil, err 49 58 } 50 59 repos = append(repos, repo) 51 60 } 52 61 53 62 if err := rows.Err(); err != nil { 63 + span.RecordError(err) 54 64 return nil, err 55 65 } 56 66 67 + span.SetAttributes(attribute.Int("repos.count", len(repos))) 57 68 return repos, nil 58 69 } 59 70 60 - func GetAllReposByDid(e Execer, did string) ([]Repo, error) { 71 + func GetAllReposByDid(ctx context.Context, e Execer, did string) ([]Repo, error) { 72 + ctx, span := otel.Tracer("db").Start(ctx, "GetAllReposByDid") 73 + defer span.End() 74 + span.SetAttributes(attribute.String("did", did)) 75 + 61 76 var repos []Repo 62 77 63 78 rows, err := e.Query( ··· 81 96 order by r.created desc`, 82 97 did) 83 98 if err != nil { 99 + span.RecordError(err) 84 100 return nil, err 85 101 } 86 102 defer rows.Close() ··· 94 110 95 111 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount, &nullableSource) 96 112 if err != nil { 113 + span.RecordError(err) 97 114 return nil, err 98 115 } 99 116 ··· 118 135 } 119 136 120 137 if err := rows.Err(); err != nil { 138 + span.RecordError(err) 121 139 return nil, err 122 140 } 123 141 142 + span.SetAttributes(attribute.Int("repos.count", len(repos))) 124 143 return repos, nil 125 144 } 126 145 127 - func GetRepo(e Execer, did, name string) (*Repo, error) { 146 + func GetRepo(ctx context.Context, e Execer, did, name string) (*Repo, error) { 147 + ctx, span := otel.Tracer("db").Start(ctx, "GetRepo") 148 + defer span.End() 149 + span.SetAttributes( 150 + attribute.String("did", did), 151 + attribute.String("name", name), 152 + ) 153 + 128 154 var repo Repo 129 155 var nullableDescription sql.NullString 130 156 ··· 132 158 133 159 var createdAt string 134 160 if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil { 161 + span.RecordError(err) 135 162 return nil, err 136 163 } 137 164 createdAtTime, _ := time.Parse(time.RFC3339, createdAt) ··· 146 173 return &repo, nil 147 174 } 148 175 149 - func GetRepoByAtUri(e Execer, atUri string) (*Repo, error) { 176 + func GetRepoByAtUri(ctx context.Context, e Execer, atUri string) (*Repo, error) { 177 + ctx, span := otel.Tracer("db").Start(ctx, "GetRepoByAtUri") 178 + defer span.End() 179 + span.SetAttributes(attribute.String("atUri", atUri)) 180 + 150 181 var repo Repo 151 182 var nullableDescription sql.NullString 152 183 ··· 154 185 155 186 var createdAt string 156 187 if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &repo.AtUri, &nullableDescription); err != nil { 188 + span.RecordError(err) 157 189 return nil, err 158 190 } 159 191 createdAtTime, _ := time.Parse(time.RFC3339, createdAt) ··· 168 200 return &repo, nil 169 201 } 170 202 171 - func AddRepo(e Execer, repo *Repo) error { 203 + func AddRepo(ctx context.Context, e Execer, repo *Repo) error { 204 + ctx, span := otel.Tracer("db").Start(ctx, "AddRepo") 205 + defer span.End() 206 + span.SetAttributes( 207 + attribute.String("did", repo.Did), 208 + attribute.String("name", repo.Name), 209 + ) 210 + 172 211 _, err := e.Exec( 173 212 `insert into repos 174 213 (did, name, knot, rkey, at_uri, description, source) 175 214 values (?, ?, ?, ?, ?, ?, ?)`, 176 215 repo.Did, repo.Name, repo.Knot, repo.Rkey, repo.AtUri, repo.Description, repo.Source, 177 216 ) 217 + if err != nil { 218 + span.RecordError(err) 219 + } 178 220 return err 179 221 } 180 222 181 - func RemoveRepo(e Execer, did, name string) error { 223 + func RemoveRepo(ctx context.Context, e Execer, did, name string) error { 224 + ctx, span := otel.Tracer("db").Start(ctx, "RemoveRepo") 225 + defer span.End() 226 + span.SetAttributes( 227 + attribute.String("did", did), 228 + attribute.String("name", name), 229 + ) 230 + 182 231 _, err := e.Exec(`delete from repos where did = ? and name = ?`, did, name) 232 + if err != nil { 233 + span.RecordError(err) 234 + } 183 235 return err 184 236 } 185 237 186 - func GetRepoSource(e Execer, repoAt syntax.ATURI) (string, error) { 238 + func GetRepoSource(ctx context.Context, e Execer, repoAt syntax.ATURI) (string, error) { 239 + ctx, span := otel.Tracer("db").Start(ctx, "GetRepoSource") 240 + defer span.End() 241 + span.SetAttributes(attribute.String("repoAt", repoAt.String())) 242 + 187 243 var nullableSource sql.NullString 188 244 err := e.QueryRow(`select source from repos where at_uri = ?`, repoAt).Scan(&nullableSource) 189 245 if err != nil { 246 + span.RecordError(err) 190 247 return "", err 191 248 } 192 249 return nullableSource.String, nil 193 250 } 194 251 195 - func GetForksByDid(e Execer, did string) ([]Repo, error) { 252 + func GetForksByDid(ctx context.Context, e Execer, did string) ([]Repo, error) { 253 + ctx, span := otel.Tracer("db").Start(ctx, "GetForksByDid") 254 + defer span.End() 255 + span.SetAttributes(attribute.String("did", did)) 256 + 196 257 var repos []Repo 197 258 198 259 rows, err := e.Query( ··· 203 264 did, 204 265 ) 205 266 if err != nil { 267 + span.RecordError(err) 206 268 return nil, err 207 269 } 208 270 defer rows.Close() ··· 215 277 216 278 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource) 217 279 if err != nil { 280 + span.RecordError(err) 218 281 return nil, err 219 282 } 220 283 ··· 237 300 } 238 301 239 302 if err := rows.Err(); err != nil { 303 + span.RecordError(err) 240 304 return nil, err 241 305 } 242 306 307 + span.SetAttributes(attribute.Int("forks.count", len(repos))) 243 308 return repos, nil 244 309 } 245 310 246 - func GetForkByDid(e Execer, did string, name string) (*Repo, error) { 311 + func GetForkByDid(ctx context.Context, e Execer, did string, name string) (*Repo, error) { 312 + ctx, span := otel.Tracer("db").Start(ctx, "GetForkByDid") 313 + defer span.End() 314 + span.SetAttributes( 315 + attribute.String("did", did), 316 + attribute.String("name", name), 317 + ) 318 + 247 319 var repo Repo 248 320 var createdAt string 249 321 var nullableDescription sql.NullString ··· 258 330 259 331 err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repo.AtUri, &nullableSource) 260 332 if err != nil { 333 + span.RecordError(err) 261 334 return nil, err 262 335 } 263 336 ··· 279 352 return &repo, nil 280 353 } 281 354 282 - func AddCollaborator(e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error { 355 + func AddCollaborator(ctx context.Context, e Execer, collaborator, repoOwnerDid, repoName, repoKnot string) error { 356 + ctx, span := otel.Tracer("db").Start(ctx, "AddCollaborator") 357 + defer span.End() 358 + span.SetAttributes( 359 + attribute.String("collaborator", collaborator), 360 + attribute.String("repoOwnerDid", repoOwnerDid), 361 + attribute.String("repoName", repoName), 362 + ) 363 + 283 364 _, err := e.Exec( 284 365 `insert into collaborators (did, repo) 285 366 values (?, (select id from repos where did = ? and name = ? and knot = ?));`, 286 367 collaborator, repoOwnerDid, repoName, repoKnot) 368 + if err != nil { 369 + span.RecordError(err) 370 + } 287 371 return err 288 372 } 289 373 290 - func UpdateDescription(e Execer, repoAt, newDescription string) error { 374 + func UpdateDescription(ctx context.Context, e Execer, repoAt, newDescription string) error { 375 + ctx, span := otel.Tracer("db").Start(ctx, "UpdateDescription") 376 + defer span.End() 377 + span.SetAttributes( 378 + attribute.String("repoAt", repoAt), 379 + attribute.String("description", newDescription), 380 + ) 381 + 291 382 _, err := e.Exec( 292 383 `update repos set description = ? where at_uri = ?`, newDescription, repoAt) 384 + if err != nil { 385 + span.RecordError(err) 386 + } 293 387 return err 294 388 } 295 389 296 - func CollaboratingIn(e Execer, collaborator string) ([]Repo, error) { 390 + func CollaboratingIn(ctx context.Context, e Execer, collaborator string) ([]Repo, error) { 391 + ctx, span := otel.Tracer("db").Start(ctx, "CollaboratingIn") 392 + defer span.End() 393 + span.SetAttributes(attribute.String("collaborator", collaborator)) 394 + 297 395 var repos []Repo 298 396 299 397 rows, err := e.Query( ··· 310 408 group by 311 409 r.id;`, collaborator) 312 410 if err != nil { 411 + span.RecordError(err) 313 412 return nil, err 314 413 } 315 414 defer rows.Close() ··· 322 421 323 422 err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount) 324 423 if err != nil { 424 + span.RecordError(err) 325 425 return nil, err 326 426 } 327 427 ··· 344 444 } 345 445 346 446 if err := rows.Err(); err != nil { 447 + span.RecordError(err) 347 448 return nil, err 348 449 } 349 450 451 + span.SetAttributes(attribute.Int("repos.count", len(repos))) 350 452 return repos, nil 351 453 } 352 454
+5 -4
appview/db/star.go
··· 1 1 package db 2 2 3 3 import ( 4 + "context" 4 5 "log" 5 6 "time" 6 7 ··· 17 18 Repo *Repo 18 19 } 19 20 20 - func (star *Star) ResolveRepo(e Execer) error { 21 + func (star *Star) ResolveRepo(ctx context.Context, e Execer) error { 21 22 if star.Repo != nil { 22 23 return nil 23 24 } 24 25 25 - repo, err := GetRepoByAtUri(e, star.RepoAt.String()) 26 + repo, err := GetRepoByAtUri(ctx, e, star.RepoAt.String()) 26 27 if err != nil { 27 28 return err 28 29 } ··· 40 41 // Get a star record 41 42 func GetStar(e Execer, starredByDid string, repoAt syntax.ATURI) (*Star, error) { 42 43 query := ` 43 - select starred_by_did, repo_at, created, rkey 44 + select starred_by_did, repo_at, created, rkey 44 45 from stars 45 46 where starred_by_did = ? and repo_at = ?` 46 47 row := e.QueryRow(query, starredByDid, repoAt) ··· 97 98 var stars []Star 98 99 99 100 rows, err := e.Query(` 100 - select 101 + select 101 102 s.starred_by_did, 102 103 s.repo_at, 103 104 s.rkey,
+28 -3
appview/db/timeline.go
··· 1 1 package db 2 2 3 3 import ( 4 + "context" 4 5 "sort" 5 6 "time" 7 + 8 + "go.opentelemetry.io/otel/attribute" 9 + "go.opentelemetry.io/otel/trace" 6 10 ) 7 11 8 12 type TimelineEvent struct { ··· 18 22 19 23 // TODO: this gathers heterogenous events from different sources and aggregates 20 24 // them in code; if we did this entirely in sql, we could order and limit and paginate easily 21 - func MakeTimeline(e Execer) ([]TimelineEvent, error) { 25 + func MakeTimeline(ctx context.Context, e Execer) ([]TimelineEvent, error) { 26 + span := trace.SpanFromContext(ctx) 27 + defer span.End() 28 + 22 29 var events []TimelineEvent 23 30 limit := 50 24 31 25 - repos, err := GetAllRepos(e, limit) 32 + span.SetAttributes(attribute.Int("timeline.limit", limit)) 33 + 34 + repos, err := GetAllRepos(ctx, e, limit) 26 35 if err != nil { 36 + span.RecordError(err) 37 + span.SetAttributes(attribute.String("error.from", "GetAllRepos")) 27 38 return nil, err 28 39 } 40 + span.SetAttributes(attribute.Int("timeline.repos.count", len(repos))) 29 41 30 42 follows, err := GetAllFollows(e, limit) 31 43 if err != nil { 44 + span.RecordError(err) 45 + span.SetAttributes(attribute.String("error.from", "GetAllFollows")) 32 46 return nil, err 33 47 } 48 + span.SetAttributes(attribute.Int("timeline.follows.count", len(follows))) 34 49 35 50 stars, err := GetAllStars(e, limit) 36 51 if err != nil { 52 + span.RecordError(err) 53 + span.SetAttributes(attribute.String("error.from", "GetAllStars")) 37 54 return nil, err 38 55 } 56 + span.SetAttributes(attribute.Int("timeline.stars.count", len(stars))) 39 57 40 58 for _, repo := range repos { 41 59 var sourceRepo *Repo 42 60 if repo.Source != "" { 43 - sourceRepo, err = GetRepoByAtUri(e, repo.Source) 61 + sourceRepo, err = GetRepoByAtUri(ctx, e, repo.Source) 44 62 if err != nil { 63 + span.RecordError(err) 64 + span.SetAttributes( 65 + attribute.String("error.from", "GetRepoByAtUri"), 66 + attribute.String("repo.source", repo.Source), 67 + ) 45 68 return nil, err 46 69 } 47 70 } ··· 75 98 if len(events) > limit { 76 99 events = events[:limit] 77 100 } 101 + 102 + span.SetAttributes(attribute.Int("timeline.events.total", len(events))) 78 103 79 104 return events, nil 80 105 }
+1 -1
appview/state/artifact.go
··· 118 118 119 119 s.pages.RepoArtifactFragment(w, pages.RepoArtifactParams{ 120 120 LoggedInUser: user, 121 - RepoInfo: f.RepoInfo(s, user), 121 + RepoInfo: f.RepoInfo(r.Context(), s, user), 122 122 Artifact: artifact, 123 123 }) 124 124 }
+31 -13
appview/state/middleware.go
··· 12 12 13 13 "github.com/bluesky-social/indigo/atproto/identity" 14 14 "github.com/go-chi/chi/v5" 15 + "go.opentelemetry.io/otel/attribute" 15 16 "tangled.sh/tangled.sh/core/appview/db" 16 17 "tangled.sh/tangled.sh/core/appview/middleware" 17 18 ) ··· 19 20 func knotRoleMiddleware(s *State, group string) middleware.Middleware { 20 21 return func(next http.Handler) http.Handler { 21 22 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 23 + ctx, span := s.t.TraceStart(r.Context(), "knotRoleMiddleware") 24 + defer span.End() 25 + 22 26 // requires auth also 23 - actor := s.auth.GetUser(r) 27 + actor := s.auth.GetUser(r.WithContext(ctx)) 24 28 if actor == nil { 25 29 // we need a logged in user 26 30 log.Printf("not logged in, redirecting") ··· 41 45 return 42 46 } 43 47 44 - next.ServeHTTP(w, r) 48 + next.ServeHTTP(w, r.WithContext(ctx)) 45 49 }) 46 50 } 47 51 } ··· 53 57 func RepoPermissionMiddleware(s *State, requiredPerm string) middleware.Middleware { 54 58 return func(next http.Handler) http.Handler { 55 59 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 + ctx, span := s.t.TraceStart(r.Context(), "RepoPermissionMiddleware") 61 + defer span.End() 62 + 56 63 // requires auth also 57 - actor := s.auth.GetUser(r) 64 + actor := s.auth.GetUser(r.WithContext(ctx)) 58 65 if actor == nil { 59 66 // we need a logged in user 60 67 log.Printf("not logged in, redirecting") 61 68 http.Error(w, "Forbiden", http.StatusUnauthorized) 62 69 return 63 70 } 64 - f, err := s.fullyResolvedRepo(r) 71 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 65 72 if err != nil { 66 73 http.Error(w, "malformed url", http.StatusBadRequest) 67 74 return ··· 75 82 return 76 83 } 77 84 78 - next.ServeHTTP(w, r) 85 + next.ServeHTTP(w, r.WithContext(ctx)) 79 86 }) 80 87 } 81 88 } ··· 101 108 return 102 109 } 103 110 104 - id, err := s.resolver.ResolveIdent(req.Context(), didOrHandle) 111 + ctx, span := s.t.TraceStart(req.Context(), "ResolveIdent") 112 + defer span.End() 113 + 114 + id, err := s.resolver.ResolveIdent(ctx, didOrHandle) 105 115 if err != nil { 106 116 // invalid did or handle 107 117 log.Println("failed to resolve did/handle:", err) ··· 109 119 return 110 120 } 111 121 112 - ctx := context.WithValue(req.Context(), "resolvedId", *id) 122 + ctx = context.WithValue(ctx, "resolvedId", *id) 113 123 114 124 next.ServeHTTP(w, req.WithContext(ctx)) 115 125 }) ··· 119 129 func ResolveRepo(s *State) middleware.Middleware { 120 130 return func(next http.Handler) http.Handler { 121 131 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 132 + ctx, span := s.t.TraceStart(req.Context(), "ResolveRepo") 133 + defer span.End() 134 + 122 135 repoName := chi.URLParam(req, "repo") 123 - id, ok := req.Context().Value("resolvedId").(identity.Identity) 136 + id, ok := ctx.Value("resolvedId").(identity.Identity) 124 137 if !ok { 125 138 log.Println("malformed middleware") 126 139 w.WriteHeader(http.StatusInternalServerError) 127 140 return 128 141 } 129 142 130 - repo, err := db.GetRepo(s.db, id.DID.String(), repoName) 143 + repo, err := db.GetRepo(ctx, s.db, id.DID.String(), repoName) 131 144 if err != nil { 132 145 // invalid did or handle 133 146 log.Println("failed to resolve repo") ··· 135 148 return 136 149 } 137 150 138 - ctx := context.WithValue(req.Context(), "knot", repo.Knot) 151 + ctx = context.WithValue(ctx, "knot", repo.Knot) 139 152 ctx = context.WithValue(ctx, "repoAt", repo.AtUri) 140 153 ctx = context.WithValue(ctx, "repoDescription", repo.Description) 141 154 ctx = context.WithValue(ctx, "repoAddedAt", repo.Created.Format(time.RFC3339)) ··· 148 161 func ResolvePull(s *State) middleware.Middleware { 149 162 return func(next http.Handler) http.Handler { 150 163 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 151 - f, err := s.fullyResolvedRepo(r) 164 + ctx, span := s.t.TraceStart(r.Context(), "ResolvePull") 165 + defer span.End() 166 + 167 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 152 168 if err != nil { 153 169 log.Println("failed to fully resolve repo", err) 154 170 http.Error(w, "invalid repo url", http.StatusNotFound) ··· 163 179 return 164 180 } 165 181 166 - pr, err := db.GetPull(s.db, f.RepoAt, prIdInt) 182 + pr, err := db.GetPull(ctx, s.db, f.RepoAt, prIdInt) 167 183 if err != nil { 168 184 log.Println("failed to get pull and comments", err) 169 185 return 170 186 } 171 187 172 - ctx := context.WithValue(r.Context(), "pull", pr) 188 + span.SetAttributes(attribute.Int("pull.id", prIdInt)) 189 + 190 + ctx = context.WithValue(ctx, "pull", pr) 173 191 174 192 next.ServeHTTP(w, r.WithContext(ctx)) 175 193 })
+33 -5
appview/state/profile.go
··· 10 10 11 11 "github.com/bluesky-social/indigo/atproto/identity" 12 12 "github.com/go-chi/chi/v5" 13 + "go.opentelemetry.io/otel/attribute" 13 14 "tangled.sh/tangled.sh/core/appview/db" 14 15 "tangled.sh/tangled.sh/core/appview/pages" 15 16 ) 16 17 17 18 func (s *State) ProfilePage(w http.ResponseWriter, r *http.Request) { 19 + ctx, span := s.t.TraceStart(r.Context(), "ProfilePage") 20 + defer span.End() 21 + 18 22 didOrHandle := chi.URLParam(r, "user") 19 23 if didOrHandle == "" { 20 24 http.Error(w, "Bad request", http.StatusBadRequest) 21 25 return 22 26 } 23 27 24 - ident, ok := r.Context().Value("resolvedId").(identity.Identity) 28 + ident, ok := ctx.Value("resolvedId").(identity.Identity) 25 29 if !ok { 26 30 s.pages.Error404(w) 31 + span.RecordError(fmt.Errorf("failed to resolve identity")) 27 32 return 28 33 } 29 34 30 - repos, err := db.GetAllReposByDid(s.db, ident.DID.String()) 35 + span.SetAttributes( 36 + attribute.String("user.did", ident.DID.String()), 37 + attribute.String("user.handle", ident.Handle.String()), 38 + ) 39 + 40 + repos, err := db.GetAllReposByDid(ctx, s.db, ident.DID.String()) 31 41 if err != nil { 32 42 log.Printf("getting repos for %s: %s", ident.DID.String(), err) 43 + span.RecordError(err) 44 + span.SetAttributes(attribute.String("error.repos", err.Error())) 33 45 } 46 + span.SetAttributes(attribute.Int("repos.count", len(repos))) 34 47 35 - collaboratingRepos, err := db.CollaboratingIn(s.db, ident.DID.String()) 48 + collaboratingRepos, err := db.CollaboratingIn(ctx, s.db, ident.DID.String()) 36 49 if err != nil { 37 50 log.Printf("getting collaborating repos for %s: %s", ident.DID.String(), err) 51 + span.RecordError(err) 52 + span.SetAttributes(attribute.String("error.collaborating_repos", err.Error())) 38 53 } 54 + span.SetAttributes(attribute.Int("collaborating_repos.count", len(collaboratingRepos))) 39 55 40 - timeline, err := db.MakeProfileTimeline(s.db, ident.DID.String()) 56 + timeline, err := db.MakeProfileTimeline(ctx, s.db, ident.DID.String()) 41 57 if err != nil { 42 58 log.Printf("failed to create profile timeline for %s: %s", ident.DID.String(), err) 59 + span.RecordError(err) 60 + span.SetAttributes(attribute.String("error.timeline", err.Error())) 43 61 } 44 62 45 63 var didsToResolve []string ··· 60 78 } 61 79 } 62 80 } 81 + span.SetAttributes(attribute.Int("dids_to_resolve.count", len(didsToResolve))) 63 82 64 - resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve) 83 + resolvedIds := s.resolver.ResolveIdents(ctx, didsToResolve) 65 84 didHandleMap := make(map[string]string) 66 85 for _, identity := range resolvedIds { 67 86 if !identity.Handle.IsInvalidHandle() { ··· 70 89 didHandleMap[identity.DID.String()] = identity.DID.String() 71 90 } 72 91 } 92 + span.SetAttributes(attribute.Int("resolved_ids.count", len(resolvedIds))) 73 93 74 94 followers, following, err := db.GetFollowerFollowing(s.db, ident.DID.String()) 75 95 if err != nil { 76 96 log.Printf("getting follow stats repos for %s: %s", ident.DID.String(), err) 97 + span.RecordError(err) 98 + span.SetAttributes(attribute.String("error.follow_stats", err.Error())) 77 99 } 100 + span.SetAttributes( 101 + attribute.Int("followers.count", followers), 102 + attribute.Int("following.count", following), 103 + ) 78 104 79 105 loggedInUser := s.auth.GetUser(r) 80 106 followStatus := db.IsNotFollowing 81 107 if loggedInUser != nil { 82 108 followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String()) 109 + span.SetAttributes(attribute.String("logged_in_user.did", loggedInUser.Did)) 83 110 } 111 + span.SetAttributes(attribute.String("follow_status", string(db.FollowStatus(followStatus)))) 84 112 85 113 profileAvatarUri := s.GetAvatarUri(ident.Handle.String()) 86 114 s.pages.ProfilePage(w, pages.ProfilePageParams{
+633 -125
appview/state/pull.go
··· 1 1 package state 2 2 3 3 import ( 4 + "context" 4 5 "database/sql" 5 6 "encoding/json" 6 7 "errors" ··· 11 12 "strconv" 12 13 "time" 13 14 15 + "go.opentelemetry.io/otel/attribute" 14 16 "tangled.sh/tangled.sh/core/api/tangled" 15 17 "tangled.sh/tangled.sh/core/appview" 16 18 "tangled.sh/tangled.sh/core/appview/auth" 17 19 "tangled.sh/tangled.sh/core/appview/db" 18 20 "tangled.sh/tangled.sh/core/appview/pages" 19 21 "tangled.sh/tangled.sh/core/patchutil" 22 + "tangled.sh/tangled.sh/core/telemetry" 20 23 "tangled.sh/tangled.sh/core/types" 21 24 22 25 comatproto "github.com/bluesky-social/indigo/api/atproto" ··· 27 30 28 31 // htmx fragment 29 32 func (s *State) PullActions(w http.ResponseWriter, r *http.Request) { 33 + ctx, span := s.t.TraceStart(r.Context(), "PullActions") 34 + defer span.End() 35 + 30 36 switch r.Method { 31 37 case http.MethodGet: 32 38 user := s.auth.GetUser(r) 33 - f, err := s.fullyResolvedRepo(r) 39 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 34 40 if err != nil { 35 41 log.Println("failed to get repo and knot", err) 36 42 return 37 43 } 38 44 39 - pull, ok := r.Context().Value("pull").(*db.Pull) 45 + pull, ok := ctx.Value("pull").(*db.Pull) 40 46 if !ok { 41 47 log.Println("failed to get pull") 42 48 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") ··· 54 60 return 55 61 } 56 62 57 - mergeCheckResponse := s.mergeCheck(f, pull) 63 + _, mergeSpan := s.t.TraceStart(ctx, "mergeCheck") 64 + mergeCheckResponse := s.mergeCheck(ctx, f, pull) 65 + mergeSpan.End() 66 + 58 67 resubmitResult := pages.Unknown 59 68 if user.Did == pull.OwnerDid { 60 - resubmitResult = s.resubmitCheck(f, pull) 69 + _, resubmitSpan := s.t.TraceStart(ctx, "resubmitCheck") 70 + resubmitResult = s.resubmitCheck(ctx, f, pull) 71 + resubmitSpan.End() 61 72 } 62 73 74 + _, renderSpan := s.t.TraceStart(ctx, "renderPullActions") 63 75 s.pages.PullActionsFragment(w, pages.PullActionsParams{ 64 76 LoggedInUser: user, 65 - RepoInfo: f.RepoInfo(s, user), 77 + RepoInfo: f.RepoInfo(ctx, s, user), 66 78 Pull: pull, 67 79 RoundNumber: roundNumber, 68 80 MergeCheck: mergeCheckResponse, 69 81 ResubmitCheck: resubmitResult, 70 82 }) 83 + renderSpan.End() 71 84 return 72 85 } 73 86 } 74 87 75 88 func (s *State) RepoSinglePull(w http.ResponseWriter, r *http.Request) { 89 + ctx, span := s.t.TraceStart(r.Context(), "RepoSinglePull") 90 + defer span.End() 91 + 76 92 user := s.auth.GetUser(r) 77 - f, err := s.fullyResolvedRepo(r) 93 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 78 94 if err != nil { 79 95 log.Println("failed to get repo and knot", err) 96 + span.RecordError(err) 80 97 return 81 98 } 82 99 83 - pull, ok := r.Context().Value("pull").(*db.Pull) 100 + pull, ok := ctx.Value("pull").(*db.Pull) 84 101 if !ok { 85 - log.Println("failed to get pull") 102 + err := errors.New("failed to get pull from context") 103 + log.Println(err) 104 + span.RecordError(err) 86 105 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 87 106 return 88 107 } 89 108 109 + attrs := telemetry.MapAttrs[string](map[string]string{ 110 + "pull.id": fmt.Sprintf("%d", pull.PullId), 111 + "pull.owner": pull.OwnerDid, 112 + }) 113 + 114 + span.SetAttributes(attrs...) 115 + 90 116 totalIdents := 1 91 117 for _, submission := range pull.Submissions { 92 118 totalIdents += len(submission.Comments) ··· 104 130 } 105 131 } 106 132 107 - resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 133 + resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve) 108 134 didHandleMap := make(map[string]string) 109 135 for _, identity := range resolvedIds { 110 136 if !identity.Handle.IsInvalidHandle() { ··· 113 139 didHandleMap[identity.DID.String()] = identity.DID.String() 114 140 } 115 141 } 142 + span.SetAttributes(attribute.Int("identities.resolved", len(resolvedIds))) 116 143 117 - mergeCheckResponse := s.mergeCheck(f, pull) 144 + mergeCheckResponse := s.mergeCheck(ctx, f, pull) 145 + 118 146 resubmitResult := pages.Unknown 119 147 if user != nil && user.Did == pull.OwnerDid { 120 - resubmitResult = s.resubmitCheck(f, pull) 148 + resubmitResult = s.resubmitCheck(ctx, f, pull) 121 149 } 122 150 123 151 s.pages.RepoSinglePull(w, pages.RepoSinglePullParams{ 124 152 LoggedInUser: user, 125 - RepoInfo: f.RepoInfo(s, user), 153 + RepoInfo: f.RepoInfo(ctx, s, user), 126 154 DidHandleMap: didHandleMap, 127 155 Pull: pull, 128 156 MergeCheck: mergeCheckResponse, ··· 130 158 }) 131 159 } 132 160 133 - func (s *State) mergeCheck(f *FullyResolvedRepo, pull *db.Pull) types.MergeCheckResponse { 161 + func (s *State) mergeCheck(ctx context.Context, f *FullyResolvedRepo, pull *db.Pull) types.MergeCheckResponse { 134 162 if pull.State == db.PullMerged { 135 163 return types.MergeCheckResponse{} 136 164 } ··· 190 218 return mergeCheckResponse 191 219 } 192 220 193 - func (s *State) resubmitCheck(f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult { 221 + func (s *State) resubmitCheck(ctx context.Context, f *FullyResolvedRepo, pull *db.Pull) pages.ResubmitResult { 222 + ctx, span := s.t.TraceStart(ctx, "resubmitCheck") 223 + defer span.End() 224 + 225 + span.SetAttributes(attribute.Int("pull.id", pull.PullId)) 226 + 194 227 if pull.State == db.PullMerged || pull.PullSource == nil { 228 + span.SetAttributes(attribute.String("result", "Unknown")) 195 229 return pages.Unknown 196 230 } 197 231 ··· 199 233 200 234 if pull.PullSource.RepoAt != nil { 201 235 // fork-based pulls 202 - sourceRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 236 + span.SetAttributes(attribute.Bool("isForkBased", true)) 237 + sourceRepo, err := db.GetRepoByAtUri(ctx, s.db, pull.PullSource.RepoAt.String()) 203 238 if err != nil { 204 239 log.Println("failed to get source repo", err) 240 + span.RecordError(err) 241 + span.SetAttributes(attribute.String("error", "failed_to_get_source_repo")) 242 + span.SetAttributes(attribute.String("result", "Unknown")) 205 243 return pages.Unknown 206 244 } 207 245 ··· 210 248 repoName = sourceRepo.Name 211 249 } else { 212 250 // pulls within the same repo 251 + span.SetAttributes(attribute.Bool("isBranchBased", true)) 213 252 knot = f.Knot 214 253 ownerDid = f.OwnerDid() 215 254 repoName = f.RepoName 216 255 } 217 256 257 + span.SetAttributes( 258 + attribute.String("knot", knot), 259 + attribute.String("ownerDid", ownerDid), 260 + attribute.String("repoName", repoName), 261 + attribute.String("sourceBranch", pull.PullSource.Branch), 262 + ) 263 + 218 264 us, err := NewUnsignedClient(knot, s.config.Dev) 219 265 if err != nil { 220 266 log.Printf("failed to setup client for %s; ignoring: %v", knot, err) 267 + span.RecordError(err) 268 + span.SetAttributes(attribute.String("error", "failed_to_setup_client")) 269 + span.SetAttributes(attribute.String("result", "Unknown")) 221 270 return pages.Unknown 222 271 } 223 272 224 273 resp, err := us.Branch(ownerDid, repoName, pull.PullSource.Branch) 225 274 if err != nil { 226 275 log.Println("failed to reach knotserver", err) 276 + span.RecordError(err) 277 + span.SetAttributes(attribute.String("error", "failed_to_reach_knotserver")) 278 + span.SetAttributes(attribute.String("result", "Unknown")) 227 279 return pages.Unknown 228 280 } 229 281 230 282 body, err := io.ReadAll(resp.Body) 231 283 if err != nil { 232 284 log.Printf("error reading response body: %v", err) 285 + span.RecordError(err) 286 + span.SetAttributes(attribute.String("error", "failed_to_read_response")) 287 + span.SetAttributes(attribute.String("result", "Unknown")) 233 288 return pages.Unknown 234 289 } 235 290 defer resp.Body.Close() ··· 237 292 var result types.RepoBranchResponse 238 293 if err := json.Unmarshal(body, &result); err != nil { 239 294 log.Println("failed to parse response:", err) 295 + span.RecordError(err) 296 + span.SetAttributes(attribute.String("error", "failed_to_parse_response")) 297 + span.SetAttributes(attribute.String("result", "Unknown")) 240 298 return pages.Unknown 241 299 } 242 300 243 301 latestSubmission := pull.Submissions[pull.LastRoundNumber()] 302 + 303 + span.SetAttributes( 304 + attribute.String("latestSubmission.SourceRev", latestSubmission.SourceRev), 305 + attribute.String("branch.Hash", result.Branch.Hash), 306 + ) 307 + 244 308 if latestSubmission.SourceRev != result.Branch.Hash { 245 309 fmt.Println(latestSubmission.SourceRev, result.Branch.Hash) 310 + span.SetAttributes(attribute.String("result", "ShouldResubmit")) 246 311 return pages.ShouldResubmit 247 312 } 248 313 314 + span.SetAttributes(attribute.String("result", "ShouldNotResubmit")) 249 315 return pages.ShouldNotResubmit 250 316 } 251 317 252 318 func (s *State) RepoPullPatch(w http.ResponseWriter, r *http.Request) { 253 - user := s.auth.GetUser(r) 254 - f, err := s.fullyResolvedRepo(r) 319 + ctx, span := s.t.TraceStart(r.Context(), "RepoPullPatch") 320 + defer span.End() 321 + 322 + user := s.auth.GetUser(r.WithContext(ctx)) 323 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 255 324 if err != nil { 256 325 log.Println("failed to get repo and knot", err) 326 + span.RecordError(err) 257 327 return 258 328 } 259 329 260 - pull, ok := r.Context().Value("pull").(*db.Pull) 330 + pull, ok := ctx.Value("pull").(*db.Pull) 261 331 if !ok { 262 - log.Println("failed to get pull") 332 + err := errors.New("failed to get pull from context") 333 + log.Println(err) 334 + span.RecordError(err) 263 335 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 264 336 return 265 337 } ··· 269 341 if err != nil || roundIdInt >= len(pull.Submissions) { 270 342 http.Error(w, "bad round id", http.StatusBadRequest) 271 343 log.Println("failed to parse round id", err) 344 + span.RecordError(err) 345 + span.SetAttributes(attribute.String("error", "bad_round_id")) 272 346 return 273 347 } 274 348 349 + span.SetAttributes( 350 + attribute.Int("pull.id", pull.PullId), 351 + attribute.Int("round", roundIdInt), 352 + attribute.String("pull.owner", pull.OwnerDid), 353 + ) 354 + 275 355 identsToResolve := []string{pull.OwnerDid} 276 - resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 356 + resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve) 277 357 didHandleMap := make(map[string]string) 278 358 for _, identity := range resolvedIds { 279 359 if !identity.Handle.IsInvalidHandle() { ··· 282 362 didHandleMap[identity.DID.String()] = identity.DID.String() 283 363 } 284 364 } 365 + span.SetAttributes(attribute.Int("identities.resolved", len(resolvedIds))) 285 366 286 367 diff := pull.Submissions[roundIdInt].AsNiceDiff(pull.TargetBranch) 287 368 288 369 s.pages.RepoPullPatchPage(w, pages.RepoPullPatchParams{ 289 370 LoggedInUser: user, 290 371 DidHandleMap: didHandleMap, 291 - RepoInfo: f.RepoInfo(s, user), 372 + RepoInfo: f.RepoInfo(ctx, s, user), 292 373 Pull: pull, 293 374 Round: roundIdInt, 294 375 Submission: pull.Submissions[roundIdInt], 295 376 Diff: &diff, 296 377 }) 297 - 298 378 } 299 379 300 380 func (s *State) RepoPullInterdiff(w http.ResponseWriter, r *http.Request) { 381 + ctx, span := s.t.TraceStart(r.Context(), "RepoPullInterdiff") 382 + defer span.End() 383 + 301 384 user := s.auth.GetUser(r) 302 385 303 - f, err := s.fullyResolvedRepo(r) 386 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 304 387 if err != nil { 305 388 log.Println("failed to get repo and knot", err) 306 389 return 307 390 } 308 391 309 - pull, ok := r.Context().Value("pull").(*db.Pull) 392 + pull, ok := ctx.Value("pull").(*db.Pull) 310 393 if !ok { 311 394 log.Println("failed to get pull") 312 395 s.pages.Notice(w, "pull-error", "Failed to get pull.") 313 396 return 314 397 } 315 398 399 + _, roundSpan := s.t.TraceStart(ctx, "parseRound") 316 400 roundId := chi.URLParam(r, "round") 317 401 roundIdInt, err := strconv.Atoi(roundId) 318 402 if err != nil || roundIdInt >= len(pull.Submissions) { 319 403 http.Error(w, "bad round id", http.StatusBadRequest) 320 404 log.Println("failed to parse round id", err) 405 + roundSpan.End() 321 406 return 322 407 } 323 408 324 409 if roundIdInt == 0 { 325 410 http.Error(w, "bad round id", http.StatusBadRequest) 326 411 log.Println("cannot interdiff initial submission") 412 + roundSpan.End() 327 413 return 328 414 } 415 + roundSpan.End() 329 416 417 + _, identSpan := s.t.TraceStart(ctx, "resolveIdentities") 330 418 identsToResolve := []string{pull.OwnerDid} 331 - resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 419 + resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve) 332 420 didHandleMap := make(map[string]string) 333 421 for _, identity := range resolvedIds { 334 422 if !identity.Handle.IsInvalidHandle() { ··· 337 425 didHandleMap[identity.DID.String()] = identity.DID.String() 338 426 } 339 427 } 428 + identSpan.End() 340 429 430 + _, diffSpan := s.t.TraceStart(ctx, "calculateInterdiff") 341 431 currentPatch, err := pull.Submissions[roundIdInt].AsDiff(pull.TargetBranch) 342 432 if err != nil { 343 433 log.Println("failed to interdiff; current patch malformed") 344 434 s.pages.Notice(w, fmt.Sprintf("interdiff-error-%d", roundIdInt), "Failed to calculate interdiff; current patch is invalid.") 435 + diffSpan.End() 345 436 return 346 437 } 347 438 ··· 349 440 if err != nil { 350 441 log.Println("failed to interdiff; previous patch malformed") 351 442 s.pages.Notice(w, fmt.Sprintf("interdiff-error-%d", roundIdInt), "Failed to calculate interdiff; previous patch is invalid.") 443 + diffSpan.End() 352 444 return 353 445 } 354 446 355 447 interdiff := patchutil.Interdiff(previousPatch, currentPatch) 448 + diffSpan.End() 356 449 450 + _, renderSpan := s.t.TraceStart(ctx, "renderInterdiffPage") 357 451 s.pages.RepoPullInterdiffPage(w, pages.RepoPullInterdiffParams{ 358 - LoggedInUser: s.auth.GetUser(r), 359 - RepoInfo: f.RepoInfo(s, user), 452 + LoggedInUser: s.auth.GetUser(r.WithContext(ctx)), 453 + RepoInfo: f.RepoInfo(ctx, s, user), 360 454 Pull: pull, 361 455 Round: roundIdInt, 362 456 DidHandleMap: didHandleMap, 363 457 Interdiff: interdiff, 364 458 }) 459 + renderSpan.End() 365 460 return 366 461 } 367 462 368 463 func (s *State) RepoPullPatchRaw(w http.ResponseWriter, r *http.Request) { 369 - pull, ok := r.Context().Value("pull").(*db.Pull) 464 + ctx, span := s.t.TraceStart(r.Context(), "RepoPullPatchRaw") 465 + defer span.End() 466 + 467 + pull, ok := ctx.Value("pull").(*db.Pull) 370 468 if !ok { 371 469 log.Println("failed to get pull") 372 470 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 373 471 return 374 472 } 375 473 474 + _, roundSpan := s.t.TraceStart(ctx, "parseRound") 376 475 roundId := chi.URLParam(r, "round") 377 476 roundIdInt, err := strconv.Atoi(roundId) 378 477 if err != nil || roundIdInt >= len(pull.Submissions) { 379 478 http.Error(w, "bad round id", http.StatusBadRequest) 380 479 log.Println("failed to parse round id", err) 480 + roundSpan.End() 381 481 return 382 482 } 483 + roundSpan.End() 383 484 485 + _, identSpan := s.t.TraceStart(ctx, "resolveIdentities") 384 486 identsToResolve := []string{pull.OwnerDid} 385 - resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 487 + resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve) 386 488 didHandleMap := make(map[string]string) 387 489 for _, identity := range resolvedIds { 388 490 if !identity.Handle.IsInvalidHandle() { ··· 391 493 didHandleMap[identity.DID.String()] = identity.DID.String() 392 494 } 393 495 } 496 + identSpan.End() 394 497 498 + _, writeSpan := s.t.TraceStart(ctx, "writePatch") 395 499 w.Header().Set("Content-Type", "text/plain") 396 500 w.Write([]byte(pull.Submissions[roundIdInt].Patch)) 501 + writeSpan.End() 397 502 } 398 503 399 504 func (s *State) RepoPulls(w http.ResponseWriter, r *http.Request) { 505 + ctx, span := s.t.TraceStart(r.Context(), "RepoPulls") 506 + defer span.End() 507 + 400 508 user := s.auth.GetUser(r) 401 509 params := r.URL.Query() 402 510 511 + _, stateSpan := s.t.TraceStart(ctx, "determinePullState") 403 512 state := db.PullOpen 404 513 switch params.Get("state") { 405 514 case "closed": ··· 407 516 case "merged": 408 517 state = db.PullMerged 409 518 } 519 + stateSpan.End() 410 520 411 - f, err := s.fullyResolvedRepo(r) 521 + _, repoSpan := s.t.TraceStart(ctx, "resolveRepo") 522 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 412 523 if err != nil { 413 524 log.Println("failed to get repo and knot", err) 525 + repoSpan.End() 414 526 return 415 527 } 528 + repoSpan.End() 416 529 417 - pulls, err := db.GetPulls(s.db, f.RepoAt, state) 530 + _, pullsSpan := s.t.TraceStart(ctx, "getPulls") 531 + pulls, err := db.GetPulls(ctx, s.db, f.RepoAt, state) 418 532 if err != nil { 419 533 log.Println("failed to get pulls", err) 420 534 s.pages.Notice(w, "pulls", "Failed to load pulls. Try again later.") 535 + pullsSpan.End() 421 536 return 422 537 } 538 + pullsSpan.End() 423 539 540 + _, sourceRepoSpan := s.t.TraceStart(ctx, "resolvePullSources") 424 541 for _, p := range pulls { 425 542 var pullSourceRepo *db.Repo 426 543 if p.PullSource != nil { 427 544 if p.PullSource.RepoAt != nil { 428 - pullSourceRepo, err = db.GetRepoByAtUri(s.db, p.PullSource.RepoAt.String()) 545 + pullSourceRepo, err = db.GetRepoByAtUri(ctx, s.db, p.PullSource.RepoAt.String()) 429 546 if err != nil { 430 547 log.Printf("failed to get repo by at uri: %v", err) 431 548 continue ··· 435 552 } 436 553 } 437 554 } 555 + sourceRepoSpan.End() 438 556 557 + _, identSpan := s.t.TraceStart(ctx, "resolveIdentities") 439 558 identsToResolve := make([]string, len(pulls)) 440 559 for i, pull := range pulls { 441 560 identsToResolve[i] = pull.OwnerDid 442 561 } 443 - resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 562 + resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve) 444 563 didHandleMap := make(map[string]string) 445 564 for _, identity := range resolvedIds { 446 565 if !identity.Handle.IsInvalidHandle() { ··· 449 568 didHandleMap[identity.DID.String()] = identity.DID.String() 450 569 } 451 570 } 571 + identSpan.End() 452 572 573 + _, renderSpan := s.t.TraceStart(ctx, "renderPullsPage") 453 574 s.pages.RepoPulls(w, pages.RepoPullsParams{ 454 - LoggedInUser: s.auth.GetUser(r), 455 - RepoInfo: f.RepoInfo(s, user), 575 + LoggedInUser: s.auth.GetUser(r.WithContext(ctx)), 576 + RepoInfo: f.RepoInfo(ctx, s, user), 456 577 Pulls: pulls, 457 578 DidHandleMap: didHandleMap, 458 579 FilteringBy: state, 459 580 }) 581 + renderSpan.End() 460 582 return 461 583 } 462 584 463 585 func (s *State) PullComment(w http.ResponseWriter, r *http.Request) { 464 - user := s.auth.GetUser(r) 465 - f, err := s.fullyResolvedRepo(r) 586 + ctx, span := s.t.TraceStart(r.Context(), "PullComment") 587 + defer span.End() 588 + 589 + user := s.auth.GetUser(r.WithContext(ctx)) 590 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 466 591 if err != nil { 467 592 log.Println("failed to get repo and knot", err) 468 593 return 469 594 } 470 595 471 - pull, ok := r.Context().Value("pull").(*db.Pull) 596 + pull, ok := ctx.Value("pull").(*db.Pull) 472 597 if !ok { 473 598 log.Println("failed to get pull") 474 599 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 475 600 return 476 601 } 477 602 603 + _, roundSpan := s.t.TraceStart(ctx, "parseRoundNumber") 478 604 roundNumberStr := chi.URLParam(r, "round") 479 605 roundNumber, err := strconv.Atoi(roundNumberStr) 480 606 if err != nil || roundNumber >= len(pull.Submissions) { 481 607 http.Error(w, "bad round id", http.StatusBadRequest) 482 608 log.Println("failed to parse round id", err) 609 + roundSpan.End() 483 610 return 484 611 } 612 + roundSpan.End() 485 613 486 614 switch r.Method { 487 615 case http.MethodGet: 616 + _, renderSpan := s.t.TraceStart(ctx, "renderCommentFragment") 488 617 s.pages.PullNewCommentFragment(w, pages.PullNewCommentParams{ 489 618 LoggedInUser: user, 490 - RepoInfo: f.RepoInfo(s, user), 619 + RepoInfo: f.RepoInfo(ctx, s, user), 491 620 Pull: pull, 492 621 RoundNumber: roundNumber, 493 622 }) 623 + renderSpan.End() 494 624 return 495 625 case http.MethodPost: 626 + postCtx, postSpan := s.t.TraceStart(ctx, "CreateComment") 627 + defer postSpan.End() 628 + 629 + _, validateSpan := s.t.TraceStart(postCtx, "validateComment") 496 630 body := r.FormValue("body") 497 631 if body == "" { 498 632 s.pages.Notice(w, "pull", "Comment body is required") 633 + validateSpan.End() 499 634 return 500 635 } 636 + validateSpan.End() 501 637 502 638 // Start a transaction 503 - tx, err := s.db.BeginTx(r.Context(), nil) 639 + _, txSpan := s.t.TraceStart(postCtx, "startTransaction") 640 + tx, err := s.db.BeginTx(postCtx, nil) 504 641 if err != nil { 505 642 log.Println("failed to start transaction", err) 506 643 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 644 + txSpan.End() 507 645 return 508 646 } 509 647 defer tx.Rollback() 648 + txSpan.End() 510 649 511 650 createdAt := time.Now().Format(time.RFC3339) 512 651 ownerDid := user.Did 513 652 514 - pullAt, err := db.GetPullAt(s.db, f.RepoAt, pull.PullId) 653 + _, pullAtSpan := s.t.TraceStart(postCtx, "getPullAt") 654 + pullAt, err := db.GetPullAt(postCtx, s.db, f.RepoAt, pull.PullId) 515 655 if err != nil { 516 656 log.Println("failed to get pull at", err) 517 657 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 658 + pullAtSpan.End() 518 659 return 519 660 } 661 + pullAtSpan.End() 520 662 663 + _, atProtoSpan := s.t.TraceStart(postCtx, "createAtProtoRecord") 521 664 atUri := f.RepoAt.String() 522 - client, _ := s.auth.AuthorizedClient(r) 523 - atResp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 665 + client, _ := s.auth.AuthorizedClient(r.WithContext(postCtx)) 666 + atResp, err := comatproto.RepoPutRecord(postCtx, client, &comatproto.RepoPutRecord_Input{ 524 667 Collection: tangled.RepoPullCommentNSID, 525 668 Repo: user.Did, 526 669 Rkey: appview.TID(), ··· 537 680 if err != nil { 538 681 log.Println("failed to create pull comment", err) 539 682 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 683 + atProtoSpan.End() 540 684 return 541 685 } 686 + atProtoSpan.End() 542 687 543 688 // Create the pull comment in the database with the commentAt field 544 - commentId, err := db.NewPullComment(tx, &db.PullComment{ 689 + _, dbSpan := s.t.TraceStart(postCtx, "createDbComment") 690 + commentId, err := db.NewPullComment(postCtx, tx, &db.PullComment{ 545 691 OwnerDid: user.Did, 546 692 RepoAt: f.RepoAt.String(), 547 693 PullId: pull.PullId, ··· 552 698 if err != nil { 553 699 log.Println("failed to create pull comment", err) 554 700 s.pages.Notice(w, "pull-comment", "Failed to create comment.") 701 + dbSpan.End() 555 702 return 556 703 } 704 + dbSpan.End() 557 705 558 - // Commit the transaction 559 706 if err = tx.Commit(); err != nil { 560 707 log.Println("failed to commit transaction", err) 561 708 s.pages.Notice(w, "pull-comment", "Failed to create comment.") ··· 568 715 } 569 716 570 717 func (s *State) NewPull(w http.ResponseWriter, r *http.Request) { 571 - user := s.auth.GetUser(r) 572 - f, err := s.fullyResolvedRepo(r) 718 + ctx, span := s.t.TraceStart(r.Context(), "NewPull") 719 + defer span.End() 720 + 721 + user := s.auth.GetUser(r.WithContext(ctx)) 722 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 573 723 if err != nil { 574 724 log.Println("failed to get repo and knot", err) 725 + span.RecordError(err) 575 726 return 576 727 } 577 728 578 729 switch r.Method { 579 730 case http.MethodGet: 731 + span.SetAttributes(attribute.String("method", "GET")) 732 + 580 733 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 581 734 if err != nil { 582 735 log.Printf("failed to create unsigned client for %s", f.Knot) 736 + span.RecordError(err) 583 737 s.pages.Error503(w) 584 738 return 585 739 } ··· 587 741 resp, err := us.Branches(f.OwnerDid(), f.RepoName) 588 742 if err != nil { 589 743 log.Println("failed to reach knotserver", err) 744 + span.RecordError(err) 590 745 return 591 746 } 592 747 593 748 body, err := io.ReadAll(resp.Body) 594 749 if err != nil { 595 750 log.Printf("Error reading response body: %v", err) 751 + span.RecordError(err) 596 752 return 597 753 } 598 754 ··· 600 756 err = json.Unmarshal(body, &result) 601 757 if err != nil { 602 758 log.Println("failed to parse response:", err) 759 + span.RecordError(err) 603 760 return 604 761 } 605 762 606 763 s.pages.RepoNewPull(w, pages.RepoNewPullParams{ 607 764 LoggedInUser: user, 608 - RepoInfo: f.RepoInfo(s, user), 765 + RepoInfo: f.RepoInfo(ctx, s, user), 609 766 Branches: result.Branches, 610 767 }) 611 768 case http.MethodPost: 769 + span.SetAttributes(attribute.String("method", "POST")) 770 + 612 771 title := r.FormValue("title") 613 772 body := r.FormValue("body") 614 773 targetBranch := r.FormValue("targetBranch") 615 774 fromFork := r.FormValue("fork") 616 775 sourceBranch := r.FormValue("sourceBranch") 617 776 patch := r.FormValue("patch") 777 + 778 + span.SetAttributes( 779 + attribute.String("targetBranch", targetBranch), 780 + attribute.String("sourceBranch", sourceBranch), 781 + attribute.Bool("hasFork", fromFork != ""), 782 + attribute.Bool("hasPatch", patch != ""), 783 + ) 618 784 619 785 if targetBranch == "" { 620 786 s.pages.Notice(w, "pull", "Target branch is required.") 787 + span.SetAttributes(attribute.String("error", "missing_target_branch")) 621 788 return 622 789 } 623 790 624 791 // Determine PR type based on input parameters 625 - isPushAllowed := f.RepoInfo(s, user).Roles.IsPushAllowed() 792 + isPushAllowed := f.RepoInfo(ctx, s, user).Roles.IsPushAllowed() 626 793 isBranchBased := isPushAllowed && sourceBranch != "" && fromFork == "" 627 794 isForkBased := fromFork != "" && sourceBranch != "" 628 795 isPatchBased := patch != "" && !isBranchBased && !isForkBased 629 796 797 + span.SetAttributes( 798 + attribute.Bool("isPushAllowed", isPushAllowed), 799 + attribute.Bool("isBranchBased", isBranchBased), 800 + attribute.Bool("isForkBased", isForkBased), 801 + attribute.Bool("isPatchBased", isPatchBased), 802 + ) 803 + 630 804 if isPatchBased && !patchutil.IsFormatPatch(patch) { 631 805 if title == "" { 632 806 s.pages.Notice(w, "pull", "Title is required for git-diff patches.") 807 + span.SetAttributes(attribute.String("error", "missing_title_for_git_diff")) 633 808 return 634 809 } 635 810 } ··· 637 812 // Validate we have at least one valid PR creation method 638 813 if !isBranchBased && !isPatchBased && !isForkBased { 639 814 s.pages.Notice(w, "pull", "Neither source branch nor patch supplied.") 815 + span.SetAttributes(attribute.String("error", "no_valid_pr_method")) 640 816 return 641 817 } 642 818 643 819 // Can't mix branch-based and patch-based approaches 644 820 if isBranchBased && patch != "" { 645 821 s.pages.Notice(w, "pull", "Cannot select both patch and source branch.") 822 + span.SetAttributes(attribute.String("error", "mixed_pr_methods")) 646 823 return 647 824 } 648 825 649 826 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 650 827 if err != nil { 651 828 log.Printf("failed to create unsigned client to %s: %v", f.Knot, err) 829 + span.RecordError(err) 652 830 s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.") 653 831 return 654 832 } ··· 656 834 caps, err := us.Capabilities() 657 835 if err != nil { 658 836 log.Println("error fetching knot caps", f.Knot, err) 837 + span.RecordError(err) 659 838 s.pages.Notice(w, "pull", "Failed to create a pull request. Try again later.") 660 839 return 661 840 } 662 841 842 + span.SetAttributes( 843 + attribute.Bool("caps.pullRequests.formatPatch", caps.PullRequests.FormatPatch), 844 + attribute.Bool("caps.pullRequests.branchSubmissions", caps.PullRequests.BranchSubmissions), 845 + attribute.Bool("caps.pullRequests.forkSubmissions", caps.PullRequests.ForkSubmissions), 846 + attribute.Bool("caps.pullRequests.patchSubmissions", caps.PullRequests.PatchSubmissions), 847 + ) 848 + 663 849 if !caps.PullRequests.FormatPatch { 664 850 s.pages.Notice(w, "pull", "This knot doesn't support format-patch. Unfortunately, there is no fallback for now.") 851 + span.SetAttributes(attribute.String("error", "formatpatch_not_supported")) 665 852 return 666 853 } 667 854 ··· 669 856 if isBranchBased { 670 857 if !caps.PullRequests.BranchSubmissions { 671 858 s.pages.Notice(w, "pull", "This knot doesn't support branch-based pull requests. Try another way?") 859 + span.SetAttributes(attribute.String("error", "branch_submissions_not_supported")) 672 860 return 673 861 } 674 - s.handleBranchBasedPull(w, r, f, user, title, body, targetBranch, sourceBranch) 862 + s.handleBranchBasedPull(w, r.WithContext(ctx), f, user, title, body, targetBranch, sourceBranch) 675 863 } else if isForkBased { 676 864 if !caps.PullRequests.ForkSubmissions { 677 865 s.pages.Notice(w, "pull", "This knot doesn't support fork-based pull requests. Try another way?") 866 + span.SetAttributes(attribute.String("error", "fork_submissions_not_supported")) 678 867 return 679 868 } 680 - s.handleForkBasedPull(w, r, f, user, fromFork, title, body, targetBranch, sourceBranch) 869 + s.handleForkBasedPull(w, r.WithContext(ctx), f, user, fromFork, title, body, targetBranch, sourceBranch) 681 870 } else if isPatchBased { 682 871 if !caps.PullRequests.PatchSubmissions { 683 872 s.pages.Notice(w, "pull", "This knot doesn't support patch-based pull requests. Send your patch over email.") 873 + span.SetAttributes(attribute.String("error", "patch_submissions_not_supported")) 684 874 return 685 875 } 686 - s.handlePatchBasedPull(w, r, f, user, title, body, targetBranch, patch) 876 + s.handlePatchBasedPull(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch) 687 877 } 688 878 return 689 879 } 690 880 } 691 881 692 882 func (s *State) handleBranchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, sourceBranch string) { 883 + ctx, span := s.t.TraceStart(r.Context(), "handleBranchBasedPull") 884 + defer span.End() 885 + 886 + span.SetAttributes( 887 + attribute.String("targetBranch", targetBranch), 888 + attribute.String("sourceBranch", sourceBranch), 889 + ) 890 + 693 891 pullSource := &db.PullSource{ 694 892 Branch: sourceBranch, 695 893 } ··· 701 899 ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 702 900 if err != nil { 703 901 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 902 + span.RecordError(err) 903 + span.SetAttributes(attribute.String("error", "client_creation_failed")) 704 904 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 705 905 return 706 906 } ··· 708 908 comparison, err := ksClient.Compare(f.OwnerDid(), f.RepoName, targetBranch, sourceBranch) 709 909 if err != nil { 710 910 log.Println("failed to compare", err) 911 + span.RecordError(err) 912 + span.SetAttributes(attribute.String("error", "comparison_failed")) 711 913 s.pages.Notice(w, "pull", err.Error()) 712 914 return 713 915 } ··· 715 917 sourceRev := comparison.Rev2 716 918 patch := comparison.Patch 717 919 920 + span.SetAttributes(attribute.String("sourceRev", sourceRev)) 921 + 718 922 if !patchutil.IsPatchValid(patch) { 923 + span.SetAttributes(attribute.String("error", "invalid_patch_format")) 719 924 s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 720 925 return 721 926 } 722 927 723 - s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource) 928 + s.createPullRequest(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch, sourceRev, pullSource, recordPullSource) 724 929 } 725 930 726 931 func (s *State) handlePatchBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, title, body, targetBranch, patch string) { 932 + ctx, span := s.t.TraceStart(r.Context(), "handlePatchBasedPull") 933 + defer span.End() 934 + 935 + span.SetAttributes(attribute.String("targetBranch", targetBranch)) 936 + 727 937 if !patchutil.IsPatchValid(patch) { 938 + span.SetAttributes(attribute.String("error", "invalid_patch_format")) 728 939 s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 729 940 return 730 941 } 731 942 732 - s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, "", nil, nil) 943 + s.createPullRequest(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch, "", nil, nil) 733 944 } 734 945 735 946 func (s *State) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *FullyResolvedRepo, user *auth.User, forkRepo string, title, body, targetBranch, sourceBranch string) { 736 - fork, err := db.GetForkByDid(s.db, user.Did, forkRepo) 947 + ctx, span := s.t.TraceStart(r.Context(), "handleForkBasedPull") 948 + defer span.End() 949 + 950 + span.SetAttributes( 951 + attribute.String("forkRepo", forkRepo), 952 + attribute.String("targetBranch", targetBranch), 953 + attribute.String("sourceBranch", sourceBranch), 954 + ) 955 + 956 + fork, err := db.GetForkByDid(ctx, s.db, user.Did, forkRepo) 737 957 if errors.Is(err, sql.ErrNoRows) { 958 + span.SetAttributes(attribute.String("error", "fork_not_found")) 738 959 s.pages.Notice(w, "pull", "No such fork.") 739 960 return 740 961 } else if err != nil { 741 962 log.Println("failed to fetch fork:", err) 963 + span.RecordError(err) 964 + span.SetAttributes(attribute.String("error", "fork_fetch_failed")) 742 965 s.pages.Notice(w, "pull", "Failed to fetch fork.") 743 966 return 744 967 } ··· 746 969 secret, err := db.GetRegistrationKey(s.db, fork.Knot) 747 970 if err != nil { 748 971 log.Println("failed to fetch registration key:", err) 972 + span.RecordError(err) 973 + span.SetAttributes(attribute.String("error", "registration_key_fetch_failed")) 749 974 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 750 975 return 751 976 } ··· 753 978 sc, err := NewSignedClient(fork.Knot, secret, s.config.Dev) 754 979 if err != nil { 755 980 log.Println("failed to create signed client:", err) 981 + span.RecordError(err) 982 + span.SetAttributes(attribute.String("error", "signed_client_creation_failed")) 756 983 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 757 984 return 758 985 } ··· 760 987 us, err := NewUnsignedClient(fork.Knot, s.config.Dev) 761 988 if err != nil { 762 989 log.Println("failed to create unsigned client:", err) 990 + span.RecordError(err) 991 + span.SetAttributes(attribute.String("error", "unsigned_client_creation_failed")) 763 992 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 764 993 return 765 994 } ··· 767 996 resp, err := sc.NewHiddenRef(user.Did, fork.Name, sourceBranch, targetBranch) 768 997 if err != nil { 769 998 log.Println("failed to create hidden ref:", err, resp.StatusCode) 999 + span.RecordError(err) 1000 + span.SetAttributes(attribute.String("error", "hidden_ref_creation_failed")) 770 1001 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 771 1002 return 772 1003 } 773 1004 774 1005 switch resp.StatusCode { 775 1006 case 404: 1007 + span.SetAttributes(attribute.String("error", "not_found_status")) 776 1008 case 400: 1009 + span.SetAttributes(attribute.String("error", "bad_request_status")) 777 1010 s.pages.Notice(w, "pull", "Branch based pull requests are not supported on this knot.") 778 1011 return 779 1012 } 780 1013 781 1014 hiddenRef := fmt.Sprintf("hidden/%s/%s", sourceBranch, targetBranch) 1015 + span.SetAttributes(attribute.String("hiddenRef", hiddenRef)) 1016 + 782 1017 // We're now comparing the sourceBranch (on the fork) against the hiddenRef which is tracking 783 1018 // the targetBranch on the target repository. This code is a bit confusing, but here's an example: 784 1019 // hiddenRef: hidden/feature-1/main (on repo-fork) ··· 787 1022 comparison, err := us.Compare(user.Did, fork.Name, hiddenRef, sourceBranch) 788 1023 if err != nil { 789 1024 log.Println("failed to compare across branches", err) 1025 + span.RecordError(err) 1026 + span.SetAttributes(attribute.String("error", "branch_comparison_failed")) 790 1027 s.pages.Notice(w, "pull", err.Error()) 791 1028 return 792 1029 } 793 1030 794 1031 sourceRev := comparison.Rev2 795 1032 patch := comparison.Patch 1033 + span.SetAttributes(attribute.String("sourceRev", sourceRev)) 796 1034 797 1035 if !patchutil.IsPatchValid(patch) { 1036 + span.SetAttributes(attribute.String("error", "invalid_patch_format")) 798 1037 s.pages.Notice(w, "pull", "Invalid patch format. Please provide a valid diff.") 799 1038 return 800 1039 } ··· 802 1041 forkAtUri, err := syntax.ParseATURI(fork.AtUri) 803 1042 if err != nil { 804 1043 log.Println("failed to parse fork AT URI", err) 1044 + span.RecordError(err) 1045 + span.SetAttributes(attribute.String("error", "fork_aturi_parse_failed")) 805 1046 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 806 1047 return 807 1048 } 808 1049 809 - s.createPullRequest(w, r, f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{ 1050 + s.createPullRequest(w, r.WithContext(ctx), f, user, title, body, targetBranch, patch, sourceRev, &db.PullSource{ 810 1051 Branch: sourceBranch, 811 1052 RepoAt: &forkAtUri, 812 1053 }, &tangled.RepoPull_Source{Branch: sourceBranch, Repo: &fork.AtUri}) ··· 823 1064 pullSource *db.PullSource, 824 1065 recordPullSource *tangled.RepoPull_Source, 825 1066 ) { 826 - tx, err := s.db.BeginTx(r.Context(), nil) 1067 + ctx, span := s.t.TraceStart(r.Context(), "createPullRequest") 1068 + defer span.End() 1069 + 1070 + span.SetAttributes( 1071 + attribute.String("targetBranch", targetBranch), 1072 + attribute.String("sourceRev", sourceRev), 1073 + attribute.Bool("hasPullSource", pullSource != nil), 1074 + ) 1075 + 1076 + tx, err := s.db.BeginTx(ctx, nil) 827 1077 if err != nil { 828 1078 log.Println("failed to start tx") 1079 + span.RecordError(err) 1080 + span.SetAttributes(attribute.String("error", "transaction_start_failed")) 829 1081 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 830 1082 return 831 1083 } ··· 836 1088 if title == "" { 837 1089 formatPatches, err := patchutil.ExtractPatches(patch) 838 1090 if err != nil { 1091 + span.RecordError(err) 1092 + span.SetAttributes(attribute.String("error", "extract_patches_failed")) 839 1093 s.pages.Notice(w, "pull", fmt.Sprintf("Failed to extract patches: %v", err)) 840 1094 return 841 1095 } 842 1096 if len(formatPatches) == 0 { 1097 + span.SetAttributes(attribute.String("error", "no_patches_found")) 843 1098 s.pages.Notice(w, "pull", "No patches found in the supplied format-patch.") 844 1099 return 845 1100 } 846 1101 847 1102 title = formatPatches[0].Title 848 1103 body = formatPatches[0].Body 1104 + span.SetAttributes( 1105 + attribute.Bool("title_extracted", true), 1106 + attribute.Bool("body_extracted", formatPatches[0].Body != ""), 1107 + ) 849 1108 } 850 1109 851 1110 rkey := appview.TID() ··· 853 1112 Patch: patch, 854 1113 SourceRev: sourceRev, 855 1114 } 856 - err = db.NewPull(tx, &db.Pull{ 1115 + err = db.NewPull(ctx, tx, &db.Pull{ 857 1116 Title: title, 858 1117 Body: body, 859 1118 TargetBranch: targetBranch, ··· 867 1126 }) 868 1127 if err != nil { 869 1128 log.Println("failed to create pull request", err) 1129 + span.RecordError(err) 1130 + span.SetAttributes(attribute.String("error", "db_create_pull_failed")) 870 1131 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 871 1132 return 872 1133 } 873 - client, _ := s.auth.AuthorizedClient(r) 1134 + 1135 + client, _ := s.auth.AuthorizedClient(r.WithContext(ctx)) 874 1136 pullId, err := db.NextPullId(s.db, f.RepoAt) 875 1137 if err != nil { 876 1138 log.Println("failed to get pull id", err) 1139 + span.RecordError(err) 1140 + span.SetAttributes(attribute.String("error", "get_pull_id_failed")) 877 1141 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 878 1142 return 879 1143 } 1144 + span.SetAttributes(attribute.Int("pullId", pullId)) 880 1145 881 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1146 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 882 1147 Collection: tangled.RepoPullNSID, 883 1148 Repo: user.Did, 884 1149 Rkey: rkey, ··· 896 1161 897 1162 if err != nil { 898 1163 log.Println("failed to create pull request", err) 1164 + span.RecordError(err) 1165 + span.SetAttributes(attribute.String("error", "atproto_create_record_failed")) 1166 + s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 1167 + return 1168 + } 1169 + 1170 + if err = tx.Commit(); err != nil { 1171 + log.Println("failed to commit transaction", err) 1172 + span.RecordError(err) 1173 + span.SetAttributes(attribute.String("error", "transaction_commit_failed")) 899 1174 s.pages.Notice(w, "pull", "Failed to create pull request. Try again later.") 900 1175 return 901 1176 } ··· 904 1179 } 905 1180 906 1181 func (s *State) ValidatePatch(w http.ResponseWriter, r *http.Request) { 907 - _, err := s.fullyResolvedRepo(r) 1182 + ctx, span := s.t.TraceStart(r.Context(), "ValidatePatch") 1183 + defer span.End() 1184 + 1185 + _, err := s.fullyResolvedRepo(r.WithContext(ctx)) 908 1186 if err != nil { 909 1187 log.Println("failed to get repo and knot", err) 1188 + span.RecordError(err) 1189 + span.SetAttributes(attribute.String("error", "resolve_repo_failed")) 910 1190 return 911 1191 } 912 1192 913 1193 patch := r.FormValue("patch") 1194 + span.SetAttributes(attribute.Bool("hasPatch", patch != "")) 1195 + 914 1196 if patch == "" { 1197 + span.SetAttributes(attribute.String("error", "empty_patch")) 915 1198 s.pages.Notice(w, "patch-error", "Patch is required.") 916 1199 return 917 1200 } 918 1201 919 - if patch == "" || !patchutil.IsPatchValid(patch) { 1202 + if !patchutil.IsPatchValid(patch) { 1203 + span.SetAttributes(attribute.String("error", "invalid_patch_format")) 920 1204 s.pages.Notice(w, "patch-error", "Invalid patch format. Please provide a valid git diff or format-patch.") 921 1205 return 922 1206 } 923 1207 924 - if patchutil.IsFormatPatch(patch) { 1208 + isFormatPatch := patchutil.IsFormatPatch(patch) 1209 + span.SetAttributes(attribute.Bool("isFormatPatch", isFormatPatch)) 1210 + 1211 + if isFormatPatch { 925 1212 s.pages.Notice(w, "patch-preview", "git-format-patch detected. Title and description are optional; if left out, they will be extracted from the first commit.") 926 1213 } else { 927 1214 s.pages.Notice(w, "patch-preview", "Regular git-diff detected. Please provide a title and description.") ··· 929 1216 } 930 1217 931 1218 func (s *State) PatchUploadFragment(w http.ResponseWriter, r *http.Request) { 932 - user := s.auth.GetUser(r) 933 - f, err := s.fullyResolvedRepo(r) 1219 + ctx, span := s.t.TraceStart(r.Context(), "PatchUploadFragment") 1220 + defer span.End() 1221 + 1222 + user := s.auth.GetUser(r.WithContext(ctx)) 1223 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 934 1224 if err != nil { 935 1225 log.Println("failed to get repo and knot", err) 1226 + span.RecordError(err) 1227 + span.SetAttributes(attribute.String("error", "resolve_repo_failed")) 936 1228 return 937 1229 } 938 1230 939 1231 s.pages.PullPatchUploadFragment(w, pages.PullPatchUploadParams{ 940 - RepoInfo: f.RepoInfo(s, user), 1232 + RepoInfo: f.RepoInfo(ctx, s, user), 941 1233 }) 942 1234 } 943 1235 944 1236 func (s *State) CompareBranchesFragment(w http.ResponseWriter, r *http.Request) { 945 - user := s.auth.GetUser(r) 946 - f, err := s.fullyResolvedRepo(r) 1237 + ctx, span := s.t.TraceStart(r.Context(), "CompareBranchesFragment") 1238 + defer span.End() 1239 + 1240 + user := s.auth.GetUser(r.WithContext(ctx)) 1241 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 947 1242 if err != nil { 948 1243 log.Println("failed to get repo and knot", err) 1244 + span.RecordError(err) 1245 + span.SetAttributes(attribute.String("error", "resolve_repo_failed")) 949 1246 return 950 1247 } 951 1248 952 1249 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 953 1250 if err != nil { 954 1251 log.Printf("failed to create unsigned client for %s", f.Knot) 1252 + span.RecordError(err) 1253 + span.SetAttributes(attribute.String("error", "client_creation_failed")) 955 1254 s.pages.Error503(w) 956 1255 return 957 1256 } ··· 959 1258 resp, err := us.Branches(f.OwnerDid(), f.RepoName) 960 1259 if err != nil { 961 1260 log.Println("failed to reach knotserver", err) 1261 + span.RecordError(err) 1262 + span.SetAttributes(attribute.String("error", "knotserver_connection_failed")) 962 1263 return 963 1264 } 964 1265 965 1266 body, err := io.ReadAll(resp.Body) 966 1267 if err != nil { 967 1268 log.Printf("Error reading response body: %v", err) 1269 + span.RecordError(err) 1270 + span.SetAttributes(attribute.String("error", "response_read_failed")) 968 1271 return 969 1272 } 1273 + defer resp.Body.Close() 970 1274 971 1275 var result types.RepoBranchesResponse 972 1276 err = json.Unmarshal(body, &result) 973 1277 if err != nil { 974 1278 log.Println("failed to parse response:", err) 1279 + span.RecordError(err) 1280 + span.SetAttributes(attribute.String("error", "response_parse_failed")) 975 1281 return 976 1282 } 1283 + span.SetAttributes(attribute.Int("branches.count", len(result.Branches))) 977 1284 978 1285 s.pages.PullCompareBranchesFragment(w, pages.PullCompareBranchesParams{ 979 - RepoInfo: f.RepoInfo(s, user), 1286 + RepoInfo: f.RepoInfo(ctx, s, user), 980 1287 Branches: result.Branches, 981 1288 }) 982 1289 } 983 1290 984 1291 func (s *State) CompareForksFragment(w http.ResponseWriter, r *http.Request) { 985 - user := s.auth.GetUser(r) 986 - f, err := s.fullyResolvedRepo(r) 1292 + ctx, span := s.t.TraceStart(r.Context(), "CompareForksFragment") 1293 + defer span.End() 1294 + 1295 + user := s.auth.GetUser(r.WithContext(ctx)) 1296 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 987 1297 if err != nil { 988 1298 log.Println("failed to get repo and knot", err) 1299 + span.RecordError(err) 989 1300 return 990 1301 } 991 1302 992 - forks, err := db.GetForksByDid(s.db, user.Did) 1303 + forks, err := db.GetForksByDid(ctx, s.db, user.Did) 993 1304 if err != nil { 994 1305 log.Println("failed to get forks", err) 1306 + span.RecordError(err) 995 1307 return 996 1308 } 997 1309 998 1310 s.pages.PullCompareForkFragment(w, pages.PullCompareForkParams{ 999 - RepoInfo: f.RepoInfo(s, user), 1311 + RepoInfo: f.RepoInfo(ctx, s, user), 1000 1312 Forks: forks, 1001 1313 }) 1002 1314 } 1003 1315 1004 1316 func (s *State) CompareForksBranchesFragment(w http.ResponseWriter, r *http.Request) { 1005 - user := s.auth.GetUser(r) 1317 + ctx, span := s.t.TraceStart(r.Context(), "CompareForksBranchesFragment") 1318 + defer span.End() 1319 + 1320 + user := s.auth.GetUser(r.WithContext(ctx)) 1006 1321 1007 - f, err := s.fullyResolvedRepo(r) 1322 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1008 1323 if err != nil { 1009 1324 log.Println("failed to get repo and knot", err) 1325 + span.RecordError(err) 1010 1326 return 1011 1327 } 1012 1328 1013 1329 forkVal := r.URL.Query().Get("fork") 1330 + span.SetAttributes(attribute.String("fork", forkVal)) 1014 1331 1015 1332 // fork repo 1016 - repo, err := db.GetRepo(s.db, user.Did, forkVal) 1333 + repo, err := db.GetRepo(ctx, s.db, user.Did, forkVal) 1017 1334 if err != nil { 1018 1335 log.Println("failed to get repo", user.Did, forkVal) 1336 + span.RecordError(err) 1019 1337 return 1020 1338 } 1021 1339 1022 1340 sourceBranchesClient, err := NewUnsignedClient(repo.Knot, s.config.Dev) 1023 1341 if err != nil { 1024 1342 log.Printf("failed to create unsigned client for %s", repo.Knot) 1343 + span.RecordError(err) 1025 1344 s.pages.Error503(w) 1026 1345 return 1027 1346 } ··· 1029 1348 sourceResp, err := sourceBranchesClient.Branches(user.Did, repo.Name) 1030 1349 if err != nil { 1031 1350 log.Println("failed to reach knotserver for source branches", err) 1351 + span.RecordError(err) 1032 1352 return 1033 1353 } 1034 1354 1035 1355 sourceBody, err := io.ReadAll(sourceResp.Body) 1036 1356 if err != nil { 1037 1357 log.Println("failed to read source response body", err) 1358 + span.RecordError(err) 1038 1359 return 1039 1360 } 1040 1361 defer sourceResp.Body.Close() ··· 1043 1364 err = json.Unmarshal(sourceBody, &sourceResult) 1044 1365 if err != nil { 1045 1366 log.Println("failed to parse source branches response:", err) 1367 + span.RecordError(err) 1046 1368 return 1047 1369 } 1048 1370 1049 1371 targetBranchesClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 1050 1372 if err != nil { 1051 1373 log.Printf("failed to create unsigned client for target knot %s", f.Knot) 1374 + span.RecordError(err) 1052 1375 s.pages.Error503(w) 1053 1376 return 1054 1377 } ··· 1056 1379 targetResp, err := targetBranchesClient.Branches(f.OwnerDid(), f.RepoName) 1057 1380 if err != nil { 1058 1381 log.Println("failed to reach knotserver for target branches", err) 1382 + span.RecordError(err) 1059 1383 return 1060 1384 } 1061 1385 1062 1386 targetBody, err := io.ReadAll(targetResp.Body) 1063 1387 if err != nil { 1064 1388 log.Println("failed to read target response body", err) 1389 + span.RecordError(err) 1065 1390 return 1066 1391 } 1067 1392 defer targetResp.Body.Close() ··· 1070 1395 err = json.Unmarshal(targetBody, &targetResult) 1071 1396 if err != nil { 1072 1397 log.Println("failed to parse target branches response:", err) 1398 + span.RecordError(err) 1073 1399 return 1074 1400 } 1075 1401 1076 1402 s.pages.PullCompareForkBranchesFragment(w, pages.PullCompareForkBranchesParams{ 1077 - RepoInfo: f.RepoInfo(s, user), 1403 + RepoInfo: f.RepoInfo(ctx, s, user), 1078 1404 SourceBranches: sourceResult.Branches, 1079 1405 TargetBranches: targetResult.Branches, 1080 1406 }) 1081 1407 } 1082 1408 1083 1409 func (s *State) ResubmitPull(w http.ResponseWriter, r *http.Request) { 1084 - user := s.auth.GetUser(r) 1085 - f, err := s.fullyResolvedRepo(r) 1410 + ctx, span := s.t.TraceStart(r.Context(), "ResubmitPull") 1411 + defer span.End() 1412 + 1413 + user := s.auth.GetUser(r.WithContext(ctx)) 1414 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1086 1415 if err != nil { 1087 1416 log.Println("failed to get repo and knot", err) 1417 + span.RecordError(err) 1088 1418 return 1089 1419 } 1090 1420 1091 - pull, ok := r.Context().Value("pull").(*db.Pull) 1421 + pull, ok := ctx.Value("pull").(*db.Pull) 1092 1422 if !ok { 1093 1423 log.Println("failed to get pull") 1424 + span.RecordError(errors.New("failed to get pull from context")) 1094 1425 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1095 1426 return 1096 1427 } 1097 1428 1429 + span.SetAttributes( 1430 + attribute.Int("pull.id", pull.PullId), 1431 + attribute.String("pull.owner", pull.OwnerDid), 1432 + attribute.String("method", r.Method), 1433 + ) 1434 + 1098 1435 switch r.Method { 1099 1436 case http.MethodGet: 1100 1437 s.pages.PullResubmitFragment(w, pages.PullResubmitParams{ 1101 - RepoInfo: f.RepoInfo(s, user), 1438 + RepoInfo: f.RepoInfo(ctx, s, user), 1102 1439 Pull: pull, 1103 1440 }) 1104 1441 return 1105 1442 case http.MethodPost: 1106 1443 if pull.IsPatchBased() { 1107 - s.resubmitPatch(w, r) 1444 + span.SetAttributes(attribute.String("pull.type", "patch_based")) 1445 + s.resubmitPatch(w, r.WithContext(ctx)) 1108 1446 return 1109 1447 } else if pull.IsBranchBased() { 1110 - s.resubmitBranch(w, r) 1448 + span.SetAttributes(attribute.String("pull.type", "branch_based")) 1449 + s.resubmitBranch(w, r.WithContext(ctx)) 1111 1450 return 1112 1451 } else if pull.IsForkBased() { 1113 - s.resubmitFork(w, r) 1452 + span.SetAttributes(attribute.String("pull.type", "fork_based")) 1453 + s.resubmitFork(w, r.WithContext(ctx)) 1114 1454 return 1115 1455 } 1456 + span.SetAttributes(attribute.String("pull.type", "unknown")) 1116 1457 } 1117 1458 } 1118 1459 1119 1460 func (s *State) resubmitPatch(w http.ResponseWriter, r *http.Request) { 1120 - user := s.auth.GetUser(r) 1461 + ctx, span := s.t.TraceStart(r.Context(), "resubmitPatch") 1462 + defer span.End() 1121 1463 1122 - pull, ok := r.Context().Value("pull").(*db.Pull) 1464 + user := s.auth.GetUser(r.WithContext(ctx)) 1465 + 1466 + pull, ok := ctx.Value("pull").(*db.Pull) 1123 1467 if !ok { 1124 1468 log.Println("failed to get pull") 1469 + span.RecordError(errors.New("failed to get pull from context")) 1125 1470 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1126 1471 return 1127 1472 } 1128 1473 1129 - f, err := s.fullyResolvedRepo(r) 1474 + span.SetAttributes( 1475 + attribute.Int("pull.id", pull.PullId), 1476 + attribute.String("pull.owner", pull.OwnerDid), 1477 + ) 1478 + 1479 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1130 1480 if err != nil { 1131 1481 log.Println("failed to get repo and knot", err) 1482 + span.RecordError(err) 1132 1483 return 1133 1484 } 1134 1485 1135 1486 if user.Did != pull.OwnerDid { 1136 1487 log.Println("unauthorized user") 1488 + span.SetAttributes(attribute.String("error", "unauthorized_user")) 1137 1489 w.WriteHeader(http.StatusUnauthorized) 1138 1490 return 1139 1491 } 1140 1492 1141 1493 patch := r.FormValue("patch") 1494 + span.SetAttributes(attribute.Bool("has_patch", patch != "")) 1142 1495 1143 1496 if err = validateResubmittedPatch(pull, patch); err != nil { 1497 + span.SetAttributes(attribute.String("error", "invalid_patch")) 1144 1498 s.pages.Notice(w, "resubmit-error", err.Error()) 1145 1499 return 1146 1500 } 1147 1501 1148 - tx, err := s.db.BeginTx(r.Context(), nil) 1502 + tx, err := s.db.BeginTx(ctx, nil) 1149 1503 if err != nil { 1150 1504 log.Println("failed to start tx") 1505 + span.RecordError(err) 1151 1506 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1152 1507 return 1153 1508 } ··· 1156 1511 err = db.ResubmitPull(tx, pull, patch, "") 1157 1512 if err != nil { 1158 1513 log.Println("failed to resubmit pull request", err) 1514 + span.RecordError(err) 1159 1515 s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull request. Try again later.") 1160 1516 return 1161 1517 } 1162 - client, _ := s.auth.AuthorizedClient(r) 1518 + client, _ := s.auth.AuthorizedClient(r.WithContext(ctx)) 1163 1519 1164 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1520 + ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1165 1521 if err != nil { 1166 1522 // failed to get record 1523 + span.RecordError(err) 1524 + span.SetAttributes(attribute.String("error", "record_not_found")) 1167 1525 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1168 1526 return 1169 1527 } 1170 1528 1171 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1529 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 1172 1530 Collection: tangled.RepoPullNSID, 1173 1531 Repo: user.Did, 1174 1532 Rkey: pull.Rkey, ··· 1185 1543 }) 1186 1544 if err != nil { 1187 1545 log.Println("failed to update record", err) 1546 + span.RecordError(err) 1188 1547 s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1189 1548 return 1190 1549 } 1191 1550 1192 1551 if err = tx.Commit(); err != nil { 1193 1552 log.Println("failed to commit transaction", err) 1553 + span.RecordError(err) 1194 1554 s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 1195 1555 return 1196 1556 } ··· 1200 1560 } 1201 1561 1202 1562 func (s *State) resubmitBranch(w http.ResponseWriter, r *http.Request) { 1203 - user := s.auth.GetUser(r) 1563 + ctx, span := s.t.TraceStart(r.Context(), "resubmitBranch") 1564 + defer span.End() 1565 + 1566 + user := s.auth.GetUser(r.WithContext(ctx)) 1204 1567 1205 - pull, ok := r.Context().Value("pull").(*db.Pull) 1568 + pull, ok := ctx.Value("pull").(*db.Pull) 1206 1569 if !ok { 1207 1570 log.Println("failed to get pull") 1208 - s.pages.Notice(w, "resubmit-error", "Failed to edit patch. Try again later.") 1571 + span.RecordError(errors.New("failed to get pull from context")) 1572 + s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1209 1573 return 1210 1574 } 1211 1575 1212 - f, err := s.fullyResolvedRepo(r) 1576 + span.SetAttributes( 1577 + attribute.Int("pull.id", pull.PullId), 1578 + attribute.String("pull.owner", pull.OwnerDid), 1579 + attribute.String("pull.source_branch", pull.PullSource.Branch), 1580 + attribute.String("pull.target_branch", pull.TargetBranch), 1581 + ) 1582 + 1583 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1213 1584 if err != nil { 1214 1585 log.Println("failed to get repo and knot", err) 1586 + span.RecordError(err) 1215 1587 return 1216 1588 } 1217 1589 1218 1590 if user.Did != pull.OwnerDid { 1219 1591 log.Println("unauthorized user") 1592 + span.SetAttributes(attribute.String("error", "unauthorized_user")) 1220 1593 w.WriteHeader(http.StatusUnauthorized) 1221 1594 return 1222 1595 } 1223 1596 1224 - if !f.RepoInfo(s, user).Roles.IsPushAllowed() { 1597 + if !f.RepoInfo(ctx, s, user).Roles.IsPushAllowed() { 1225 1598 log.Println("unauthorized user") 1599 + span.SetAttributes(attribute.String("error", "push_not_allowed")) 1226 1600 w.WriteHeader(http.StatusUnauthorized) 1227 1601 return 1228 1602 } ··· 1230 1604 ksClient, err := NewUnsignedClient(f.Knot, s.config.Dev) 1231 1605 if err != nil { 1232 1606 log.Printf("failed to create client for %s: %s", f.Knot, err) 1607 + span.RecordError(err) 1608 + span.SetAttributes(attribute.String("error", "client_creation_failed")) 1233 1609 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1234 1610 return 1235 1611 } ··· 1237 1613 comparison, err := ksClient.Compare(f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.PullSource.Branch) 1238 1614 if err != nil { 1239 1615 log.Printf("compare request failed: %s", err) 1616 + span.RecordError(err) 1617 + span.SetAttributes(attribute.String("error", "compare_failed")) 1240 1618 s.pages.Notice(w, "resubmit-error", err.Error()) 1241 1619 return 1242 1620 } 1243 1621 1244 1622 sourceRev := comparison.Rev2 1245 1623 patch := comparison.Patch 1624 + span.SetAttributes(attribute.String("source_rev", sourceRev)) 1246 1625 1247 1626 if err = validateResubmittedPatch(pull, patch); err != nil { 1627 + span.SetAttributes(attribute.String("error", "invalid_patch")) 1248 1628 s.pages.Notice(w, "resubmit-error", err.Error()) 1249 1629 return 1250 1630 } 1251 1631 1252 1632 if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev { 1633 + span.SetAttributes(attribute.String("error", "no_changes")) 1253 1634 s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.") 1254 1635 return 1255 1636 } 1256 1637 1257 - tx, err := s.db.BeginTx(r.Context(), nil) 1638 + tx, err := s.db.BeginTx(ctx, nil) 1258 1639 if err != nil { 1259 1640 log.Println("failed to start tx") 1641 + span.RecordError(err) 1260 1642 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1261 1643 return 1262 1644 } ··· 1265 1647 err = db.ResubmitPull(tx, pull, patch, sourceRev) 1266 1648 if err != nil { 1267 1649 log.Println("failed to create pull request", err) 1650 + span.RecordError(err) 1268 1651 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1269 1652 return 1270 1653 } 1271 - client, _ := s.auth.AuthorizedClient(r) 1654 + client, _ := s.auth.AuthorizedClient(r.WithContext(ctx)) 1272 1655 1273 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1656 + ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1274 1657 if err != nil { 1275 1658 // failed to get record 1659 + span.RecordError(err) 1660 + span.SetAttributes(attribute.String("error", "record_not_found")) 1276 1661 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1277 1662 return 1278 1663 } ··· 1280 1665 recordPullSource := &tangled.RepoPull_Source{ 1281 1666 Branch: pull.PullSource.Branch, 1282 1667 } 1283 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1668 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 1284 1669 Collection: tangled.RepoPullNSID, 1285 1670 Repo: user.Did, 1286 1671 Rkey: pull.Rkey, ··· 1298 1683 }) 1299 1684 if err != nil { 1300 1685 log.Println("failed to update record", err) 1686 + span.RecordError(err) 1301 1687 s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1302 1688 return 1303 1689 } 1304 1690 1305 1691 if err = tx.Commit(); err != nil { 1306 1692 log.Println("failed to commit transaction", err) 1693 + span.RecordError(err) 1307 1694 s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 1308 1695 return 1309 1696 } ··· 1313 1700 } 1314 1701 1315 1702 func (s *State) resubmitFork(w http.ResponseWriter, r *http.Request) { 1316 - user := s.auth.GetUser(r) 1703 + ctx, span := s.t.TraceStart(r.Context(), "resubmitFork") 1704 + defer span.End() 1317 1705 1318 - pull, ok := r.Context().Value("pull").(*db.Pull) 1706 + user := s.auth.GetUser(r.WithContext(ctx)) 1707 + 1708 + pull, ok := ctx.Value("pull").(*db.Pull) 1319 1709 if !ok { 1320 1710 log.Println("failed to get pull") 1321 - s.pages.Notice(w, "resubmit-error", "Failed to edit patch. Try again later.") 1711 + span.RecordError(errors.New("failed to get pull from context")) 1712 + s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1322 1713 return 1323 1714 } 1324 1715 1325 - f, err := s.fullyResolvedRepo(r) 1716 + span.SetAttributes( 1717 + attribute.Int("pull.id", pull.PullId), 1718 + attribute.String("pull.owner", pull.OwnerDid), 1719 + attribute.String("pull.source_branch", pull.PullSource.Branch), 1720 + attribute.String("pull.target_branch", pull.TargetBranch), 1721 + ) 1722 + 1723 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1326 1724 if err != nil { 1327 1725 log.Println("failed to get repo and knot", err) 1726 + span.RecordError(err) 1328 1727 return 1329 1728 } 1330 1729 1331 1730 if user.Did != pull.OwnerDid { 1332 1731 log.Println("unauthorized user") 1732 + span.SetAttributes(attribute.String("error", "unauthorized_user")) 1333 1733 w.WriteHeader(http.StatusUnauthorized) 1334 1734 return 1335 1735 } 1336 1736 1337 - forkRepo, err := db.GetRepoByAtUri(s.db, pull.PullSource.RepoAt.String()) 1737 + forkRepo, err := db.GetRepoByAtUri(ctx, s.db, pull.PullSource.RepoAt.String()) 1338 1738 if err != nil { 1339 1739 log.Println("failed to get source repo", err) 1740 + span.RecordError(err) 1741 + span.SetAttributes(attribute.String("error", "source_repo_not_found")) 1340 1742 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1341 1743 return 1342 1744 } 1343 1745 1746 + span.SetAttributes( 1747 + attribute.String("fork.knot", forkRepo.Knot), 1748 + attribute.String("fork.did", forkRepo.Did), 1749 + attribute.String("fork.name", forkRepo.Name), 1750 + ) 1751 + 1344 1752 // extract patch by performing compare 1345 1753 ksClient, err := NewUnsignedClient(forkRepo.Knot, s.config.Dev) 1346 1754 if err != nil { 1347 1755 log.Printf("failed to create client for %s: %s", forkRepo.Knot, err) 1756 + span.RecordError(err) 1757 + span.SetAttributes(attribute.String("error", "client_creation_failed")) 1348 1758 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1349 1759 return 1350 1760 } ··· 1352 1762 secret, err := db.GetRegistrationKey(s.db, forkRepo.Knot) 1353 1763 if err != nil { 1354 1764 log.Printf("failed to get registration key for %s: %s", forkRepo.Knot, err) 1765 + span.RecordError(err) 1766 + span.SetAttributes(attribute.String("error", "reg_key_not_found")) 1355 1767 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1356 1768 return 1357 1769 } ··· 1360 1772 signedClient, err := NewSignedClient(forkRepo.Knot, secret, s.config.Dev) 1361 1773 if err != nil { 1362 1774 log.Printf("failed to create signed client for %s: %s", forkRepo.Knot, err) 1775 + span.RecordError(err) 1776 + span.SetAttributes(attribute.String("error", "signed_client_creation_failed")) 1363 1777 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1364 1778 return 1365 1779 } ··· 1367 1781 resp, err := signedClient.NewHiddenRef(forkRepo.Did, forkRepo.Name, pull.PullSource.Branch, pull.TargetBranch) 1368 1782 if err != nil || resp.StatusCode != http.StatusNoContent { 1369 1783 log.Printf("failed to update tracking branch: %s", err) 1784 + span.RecordError(err) 1785 + span.SetAttributes(attribute.String("error", "hidden_ref_update_failed")) 1370 1786 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1371 1787 return 1372 1788 } 1373 1789 1374 1790 hiddenRef := fmt.Sprintf("hidden/%s/%s", pull.PullSource.Branch, pull.TargetBranch) 1791 + span.SetAttributes(attribute.String("hidden_ref", hiddenRef)) 1792 + 1375 1793 comparison, err := ksClient.Compare(forkRepo.Did, forkRepo.Name, hiddenRef, pull.PullSource.Branch) 1376 1794 if err != nil { 1377 1795 log.Printf("failed to compare branches: %s", err) 1796 + span.RecordError(err) 1797 + span.SetAttributes(attribute.String("error", "compare_failed")) 1378 1798 s.pages.Notice(w, "resubmit-error", err.Error()) 1379 1799 return 1380 1800 } 1381 1801 1382 1802 sourceRev := comparison.Rev2 1383 1803 patch := comparison.Patch 1804 + span.SetAttributes(attribute.String("source_rev", sourceRev)) 1384 1805 1385 1806 if err = validateResubmittedPatch(pull, patch); err != nil { 1807 + span.SetAttributes(attribute.String("error", "invalid_patch")) 1386 1808 s.pages.Notice(w, "resubmit-error", err.Error()) 1387 1809 return 1388 1810 } 1389 1811 1390 1812 if sourceRev == pull.Submissions[pull.LastRoundNumber()].SourceRev { 1813 + span.SetAttributes(attribute.String("error", "no_changes")) 1391 1814 s.pages.Notice(w, "resubmit-error", "This branch has not changed since the last submission.") 1392 1815 return 1393 1816 } 1394 1817 1395 - tx, err := s.db.BeginTx(r.Context(), nil) 1818 + tx, err := s.db.BeginTx(ctx, nil) 1396 1819 if err != nil { 1397 1820 log.Println("failed to start tx") 1821 + span.RecordError(err) 1398 1822 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1399 1823 return 1400 1824 } ··· 1403 1827 err = db.ResubmitPull(tx, pull, patch, sourceRev) 1404 1828 if err != nil { 1405 1829 log.Println("failed to create pull request", err) 1830 + span.RecordError(err) 1406 1831 s.pages.Notice(w, "resubmit-error", "Failed to create pull request. Try again later.") 1407 1832 return 1408 1833 } 1409 - client, _ := s.auth.AuthorizedClient(r) 1834 + client, _ := s.auth.AuthorizedClient(r.WithContext(ctx)) 1410 1835 1411 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1836 + ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoPullNSID, user.Did, pull.Rkey) 1412 1837 if err != nil { 1413 1838 // failed to get record 1839 + span.RecordError(err) 1840 + span.SetAttributes(attribute.String("error", "record_not_found")) 1414 1841 s.pages.Notice(w, "resubmit-error", "Failed to update pull, no record found on PDS.") 1415 1842 return 1416 1843 } ··· 1420 1847 Branch: pull.PullSource.Branch, 1421 1848 Repo: &repoAt, 1422 1849 } 1423 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1850 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 1424 1851 Collection: tangled.RepoPullNSID, 1425 1852 Repo: user.Did, 1426 1853 Rkey: pull.Rkey, ··· 1438 1865 }) 1439 1866 if err != nil { 1440 1867 log.Println("failed to update record", err) 1868 + span.RecordError(err) 1441 1869 s.pages.Notice(w, "resubmit-error", "Failed to update pull request on the PDS. Try again later.") 1442 1870 return 1443 1871 } 1444 1872 1445 1873 if err = tx.Commit(); err != nil { 1446 1874 log.Println("failed to commit transaction", err) 1875 + span.RecordError(err) 1447 1876 s.pages.Notice(w, "resubmit-error", "Failed to resubmit pull.") 1448 1877 return 1449 1878 } ··· 1470 1899 } 1471 1900 1472 1901 func (s *State) MergePull(w http.ResponseWriter, r *http.Request) { 1473 - f, err := s.fullyResolvedRepo(r) 1902 + ctx, span := s.t.TraceStart(r.Context(), "MergePull") 1903 + defer span.End() 1904 + 1905 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1474 1906 if err != nil { 1475 1907 log.Println("failed to resolve repo:", err) 1908 + span.RecordError(err) 1909 + span.SetAttributes(attribute.String("error", "resolve_repo_failed")) 1476 1910 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") 1477 1911 return 1478 1912 } 1479 1913 1480 - pull, ok := r.Context().Value("pull").(*db.Pull) 1914 + pull, ok := ctx.Value("pull").(*db.Pull) 1481 1915 if !ok { 1482 1916 log.Println("failed to get pull") 1917 + span.SetAttributes(attribute.String("error", "pull_not_in_context")) 1483 1918 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1484 1919 return 1485 1920 } 1486 1921 1922 + span.SetAttributes( 1923 + attribute.Int("pull.id", pull.PullId), 1924 + attribute.String("pull.owner", pull.OwnerDid), 1925 + attribute.String("target_branch", pull.TargetBranch), 1926 + ) 1927 + 1487 1928 secret, err := db.GetRegistrationKey(s.db, f.Knot) 1488 1929 if err != nil { 1489 1930 log.Printf("no registration key found for domain %s: %s\n", f.Knot, err) 1931 + span.RecordError(err) 1932 + span.SetAttributes(attribute.String("error", "reg_key_not_found")) 1490 1933 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") 1491 1934 return 1492 1935 } 1493 1936 1494 - ident, err := s.resolver.ResolveIdent(r.Context(), pull.OwnerDid) 1937 + ident, err := s.resolver.ResolveIdent(ctx, pull.OwnerDid) 1495 1938 if err != nil { 1496 1939 log.Printf("resolving identity: %s", err) 1940 + span.RecordError(err) 1941 + span.SetAttributes(attribute.String("error", "resolve_identity_failed")) 1497 1942 w.WriteHeader(http.StatusNotFound) 1498 1943 return 1499 1944 } ··· 1501 1946 email, err := db.GetPrimaryEmail(s.db, pull.OwnerDid) 1502 1947 if err != nil { 1503 1948 log.Printf("failed to get primary email: %s", err) 1949 + span.RecordError(err) 1950 + span.SetAttributes(attribute.String("error", "get_email_failed")) 1504 1951 } 1505 1952 1506 1953 ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 1507 1954 if err != nil { 1508 1955 log.Printf("failed to create signed client for %s: %s", f.Knot, err) 1956 + span.RecordError(err) 1957 + span.SetAttributes(attribute.String("error", "client_creation_failed")) 1509 1958 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") 1510 1959 return 1511 1960 } ··· 1514 1963 resp, err := ksClient.Merge([]byte(pull.LatestPatch()), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address) 1515 1964 if err != nil { 1516 1965 log.Printf("failed to merge pull request: %s", err) 1966 + span.RecordError(err) 1967 + span.SetAttributes(attribute.String("error", "merge_failed")) 1517 1968 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") 1518 1969 return 1519 1970 } 1520 1971 1972 + span.SetAttributes(attribute.Int("response.status", resp.StatusCode)) 1973 + 1521 1974 if resp.StatusCode == http.StatusOK { 1522 1975 err := db.MergePull(s.db, f.RepoAt, pull.PullId) 1523 1976 if err != nil { 1524 1977 log.Printf("failed to update pull request status in database: %s", err) 1978 + span.RecordError(err) 1979 + span.SetAttributes(attribute.String("error", "db_update_failed")) 1525 1980 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") 1526 1981 return 1527 1982 } 1528 1983 s.pages.HxLocation(w, fmt.Sprintf("/@%s/%s/pulls/%d", f.OwnerHandle(), f.RepoName, pull.PullId)) 1529 1984 } else { 1530 1985 log.Printf("knotserver returned non-OK status code for merge: %d", resp.StatusCode) 1986 + span.SetAttributes(attribute.String("error", "non_ok_response")) 1531 1987 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.") 1532 1988 } 1533 1989 } 1534 1990 1535 1991 func (s *State) ClosePull(w http.ResponseWriter, r *http.Request) { 1536 - user := s.auth.GetUser(r) 1992 + ctx, span := s.t.TraceStart(r.Context(), "ClosePull") 1993 + defer span.End() 1537 1994 1538 - f, err := s.fullyResolvedRepo(r) 1995 + user := s.auth.GetUser(r.WithContext(ctx)) 1996 + 1997 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1539 1998 if err != nil { 1540 1999 log.Println("malformed middleware") 2000 + span.RecordError(err) 2001 + span.SetAttributes(attribute.String("error", "resolve_repo_failed")) 1541 2002 return 1542 2003 } 1543 2004 1544 - pull, ok := r.Context().Value("pull").(*db.Pull) 2005 + pull, ok := ctx.Value("pull").(*db.Pull) 1545 2006 if !ok { 1546 2007 log.Println("failed to get pull") 2008 + span.SetAttributes(attribute.String("error", "pull_not_in_context")) 1547 2009 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1548 2010 return 1549 2011 } 1550 2012 2013 + span.SetAttributes( 2014 + attribute.Int("pull.id", pull.PullId), 2015 + attribute.String("pull.owner", pull.OwnerDid), 2016 + attribute.String("user.did", user.Did), 2017 + ) 2018 + 1551 2019 // auth filter: only owner or collaborators can close 1552 2020 roles := RolesInRepo(s, user, f) 1553 2021 isCollaborator := roles.IsCollaborator() 1554 2022 isPullAuthor := user.Did == pull.OwnerDid 1555 2023 isCloseAllowed := isCollaborator || isPullAuthor 2024 + 2025 + span.SetAttributes( 2026 + attribute.Bool("is_collaborator", isCollaborator), 2027 + attribute.Bool("is_pull_author", isPullAuthor), 2028 + attribute.Bool("is_close_allowed", isCloseAllowed), 2029 + ) 2030 + 1556 2031 if !isCloseAllowed { 1557 2032 log.Println("failed to close pull") 2033 + span.SetAttributes(attribute.String("error", "unauthorized")) 1558 2034 s.pages.Notice(w, "pull-close", "You are unauthorized to close this pull.") 1559 2035 return 1560 2036 } 1561 2037 1562 2038 // Start a transaction 1563 - tx, err := s.db.BeginTx(r.Context(), nil) 2039 + tx, err := s.db.BeginTx(ctx, nil) 1564 2040 if err != nil { 1565 2041 log.Println("failed to start transaction", err) 2042 + span.RecordError(err) 2043 + span.SetAttributes(attribute.String("error", "transaction_start_failed")) 1566 2044 s.pages.Notice(w, "pull-close", "Failed to close pull.") 1567 2045 return 1568 2046 } ··· 1571 2049 err = db.ClosePull(tx, f.RepoAt, pull.PullId) 1572 2050 if err != nil { 1573 2051 log.Println("failed to close pull", err) 2052 + span.RecordError(err) 2053 + span.SetAttributes(attribute.String("error", "db_close_failed")) 1574 2054 s.pages.Notice(w, "pull-close", "Failed to close pull.") 1575 2055 return 1576 2056 } ··· 1578 2058 // Commit the transaction 1579 2059 if err = tx.Commit(); err != nil { 1580 2060 log.Println("failed to commit transaction", err) 2061 + span.RecordError(err) 2062 + span.SetAttributes(attribute.String("error", "transaction_commit_failed")) 1581 2063 s.pages.Notice(w, "pull-close", "Failed to close pull.") 1582 2064 return 1583 2065 } ··· 1587 2069 } 1588 2070 1589 2071 func (s *State) ReopenPull(w http.ResponseWriter, r *http.Request) { 1590 - user := s.auth.GetUser(r) 2072 + ctx, span := s.t.TraceStart(r.Context(), "ReopenPull") 2073 + defer span.End() 1591 2074 1592 - f, err := s.fullyResolvedRepo(r) 2075 + user := s.auth.GetUser(r.WithContext(ctx)) 2076 + 2077 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1593 2078 if err != nil { 1594 2079 log.Println("failed to resolve repo", err) 2080 + span.RecordError(err) 2081 + span.SetAttributes(attribute.String("error", "resolve_repo_failed")) 1595 2082 s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.") 1596 2083 return 1597 2084 } 1598 2085 1599 - pull, ok := r.Context().Value("pull").(*db.Pull) 2086 + pull, ok := ctx.Value("pull").(*db.Pull) 1600 2087 if !ok { 1601 2088 log.Println("failed to get pull") 2089 + span.SetAttributes(attribute.String("error", "pull_not_in_context")) 1602 2090 s.pages.Notice(w, "pull-error", "Failed to edit patch. Try again later.") 1603 2091 return 1604 2092 } 1605 2093 1606 - // auth filter: only owner or collaborators can close 2094 + span.SetAttributes( 2095 + attribute.Int("pull.id", pull.PullId), 2096 + attribute.String("pull.owner", pull.OwnerDid), 2097 + attribute.String("user.did", user.Did), 2098 + ) 2099 + 2100 + // auth filter: only owner or collaborators can reopen 1607 2101 roles := RolesInRepo(s, user, f) 1608 2102 isCollaborator := roles.IsCollaborator() 1609 2103 isPullAuthor := user.Did == pull.OwnerDid 1610 - isCloseAllowed := isCollaborator || isPullAuthor 1611 - if !isCloseAllowed { 1612 - log.Println("failed to close pull") 1613 - s.pages.Notice(w, "pull-close", "You are unauthorized to close this pull.") 2104 + isReopenAllowed := isCollaborator || isPullAuthor 2105 + 2106 + span.SetAttributes( 2107 + attribute.Bool("is_collaborator", isCollaborator), 2108 + attribute.Bool("is_pull_author", isPullAuthor), 2109 + attribute.Bool("is_reopen_allowed", isReopenAllowed), 2110 + ) 2111 + 2112 + if !isReopenAllowed { 2113 + log.Println("failed to reopen pull") 2114 + span.SetAttributes(attribute.String("error", "unauthorized")) 2115 + s.pages.Notice(w, "pull-close", "You are unauthorized to reopen this pull.") 1614 2116 return 1615 2117 } 1616 2118 1617 2119 // Start a transaction 1618 - tx, err := s.db.BeginTx(r.Context(), nil) 2120 + tx, err := s.db.BeginTx(ctx, nil) 1619 2121 if err != nil { 1620 2122 log.Println("failed to start transaction", err) 2123 + span.RecordError(err) 2124 + span.SetAttributes(attribute.String("error", "transaction_start_failed")) 1621 2125 s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.") 1622 2126 return 1623 2127 } ··· 1626 2130 err = db.ReopenPull(tx, f.RepoAt, pull.PullId) 1627 2131 if err != nil { 1628 2132 log.Println("failed to reopen pull", err) 2133 + span.RecordError(err) 2134 + span.SetAttributes(attribute.String("error", "db_reopen_failed")) 1629 2135 s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.") 1630 2136 return 1631 2137 } ··· 1633 2139 // Commit the transaction 1634 2140 if err = tx.Commit(); err != nil { 1635 2141 log.Println("failed to commit transaction", err) 2142 + span.RecordError(err) 2143 + span.SetAttributes(attribute.String("error", "transaction_commit_failed")) 1636 2144 s.pages.Notice(w, "pull-reopen", "Failed to reopen pull.") 1637 2145 return 1638 2146 }
+699 -95
appview/state/repo.go
··· 16 16 "strings" 17 17 "time" 18 18 19 + "go.opentelemetry.io/otel/attribute" 20 + "go.opentelemetry.io/otel/codes" 19 21 "tangled.sh/tangled.sh/core/api/tangled" 20 22 "tangled.sh/tangled.sh/core/appview" 21 23 "tangled.sh/tangled.sh/core/appview/auth" ··· 38 40 ) 39 41 40 42 func (s *State) RepoIndex(w http.ResponseWriter, r *http.Request) { 43 + ctx, span := s.t.TraceStart(r.Context(), "RepoIndex") 44 + defer span.End() 45 + 41 46 ref := chi.URLParam(r, "ref") 42 - f, err := s.fullyResolvedRepo(r) 47 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 43 48 if err != nil { 44 49 log.Println("failed to fully resolve repo", err) 50 + span.RecordError(err) 51 + span.SetStatus(codes.Error, "failed to fully resolve repo") 45 52 return 46 53 } 47 54 48 55 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 49 56 if err != nil { 50 57 log.Printf("failed to create unsigned client for %s", f.Knot) 58 + span.RecordError(err) 59 + span.SetStatus(codes.Error, "failed to create unsigned client") 51 60 s.pages.Error503(w) 52 61 return 53 62 } ··· 56 65 if err != nil { 57 66 s.pages.Error503(w) 58 67 log.Println("failed to reach knotserver", err) 68 + span.RecordError(err) 69 + span.SetStatus(codes.Error, "failed to reach knotserver") 59 70 return 60 71 } 61 72 defer resp.Body.Close() ··· 63 74 body, err := io.ReadAll(resp.Body) 64 75 if err != nil { 65 76 log.Printf("Error reading response body: %v", err) 77 + span.RecordError(err) 78 + span.SetStatus(codes.Error, "error reading response body") 66 79 return 67 80 } 68 81 ··· 70 83 err = json.Unmarshal(body, &result) 71 84 if err != nil { 72 85 log.Printf("Error unmarshalling response body: %v", err) 86 + span.RecordError(err) 87 + span.SetStatus(codes.Error, "error unmarshalling response body") 73 88 return 74 89 } 75 90 ··· 112 127 tagCount := len(result.Tags) 113 128 fileCount := len(result.Files) 114 129 130 + span.SetAttributes( 131 + attribute.Int("commits.count", commitCount), 132 + attribute.Int("branches.count", branchCount), 133 + attribute.Int("tags.count", tagCount), 134 + attribute.Int("files.count", fileCount), 135 + ) 136 + 115 137 commitCount, branchCount, tagCount = balanceIndexItems(commitCount, branchCount, tagCount, fileCount) 116 138 commitsTrunc := result.Commits[:min(commitCount, len(result.Commits))] 117 139 tagsTrunc := result.Tags[:min(tagCount, len(result.Tags))] ··· 122 144 user := s.auth.GetUser(r) 123 145 s.pages.RepoIndexPage(w, pages.RepoIndexParams{ 124 146 LoggedInUser: user, 125 - RepoInfo: f.RepoInfo(s, user), 147 + RepoInfo: f.RepoInfo(ctx, s, user), 126 148 TagMap: tagMap, 127 149 RepoIndexResponse: result, 128 150 CommitsTrunc: commitsTrunc, ··· 134 156 } 135 157 136 158 func (s *State) RepoLog(w http.ResponseWriter, r *http.Request) { 137 - f, err := s.fullyResolvedRepo(r) 159 + ctx, span := s.t.TraceStart(r.Context(), "RepoLog") 160 + defer span.End() 161 + 162 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 138 163 if err != nil { 139 164 log.Println("failed to fully resolve repo", err) 165 + span.RecordError(err) 166 + span.SetStatus(codes.Error, "failed to fully resolve repo") 140 167 return 141 168 } 142 169 ··· 149 176 } 150 177 151 178 ref := chi.URLParam(r, "ref") 179 + span.SetAttributes(attribute.Int("page", page), attribute.String("ref", ref)) 152 180 153 181 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 154 182 if err != nil { 155 183 log.Println("failed to create unsigned client", err) 184 + span.RecordError(err) 185 + span.SetStatus(codes.Error, "failed to create unsigned client") 156 186 return 157 187 } 158 188 159 189 resp, err := us.Log(f.OwnerDid(), f.RepoName, ref, page) 160 190 if err != nil { 161 191 log.Println("failed to reach knotserver", err) 192 + span.RecordError(err) 193 + span.SetStatus(codes.Error, "failed to reach knotserver") 162 194 return 163 195 } 164 196 165 197 body, err := io.ReadAll(resp.Body) 166 198 if err != nil { 167 199 log.Printf("error reading response body: %v", err) 200 + span.RecordError(err) 201 + span.SetStatus(codes.Error, "error reading response body") 168 202 return 169 203 } 170 204 ··· 172 206 err = json.Unmarshal(body, &repolog) 173 207 if err != nil { 174 208 log.Println("failed to parse json response", err) 209 + span.RecordError(err) 210 + span.SetStatus(codes.Error, "failed to parse json response") 175 211 return 176 212 } 213 + 214 + span.SetAttributes(attribute.Int("commits.count", len(repolog.Commits))) 177 215 178 216 result, err := us.Tags(f.OwnerDid(), f.RepoName) 179 217 if err != nil { 180 218 log.Println("failed to reach knotserver", err) 219 + span.RecordError(err) 220 + span.SetStatus(codes.Error, "failed to reach knotserver for tags") 181 221 return 182 222 } 183 223 ··· 190 230 tagMap[hash] = append(tagMap[hash], tag.Name) 191 231 } 192 232 233 + span.SetAttributes(attribute.Int("tags.count", len(result.Tags))) 234 + 193 235 user := s.auth.GetUser(r) 194 236 s.pages.RepoLog(w, pages.RepoLogParams{ 195 237 LoggedInUser: user, 196 238 TagMap: tagMap, 197 - RepoInfo: f.RepoInfo(s, user), 239 + RepoInfo: f.RepoInfo(ctx, s, user), 198 240 RepoLogResponse: repolog, 199 241 EmailToDidOrHandle: EmailToDidOrHandle(s, uniqueEmails(repolog.Commits)), 200 242 }) ··· 202 244 } 203 245 204 246 func (s *State) RepoDescriptionEdit(w http.ResponseWriter, r *http.Request) { 205 - f, err := s.fullyResolvedRepo(r) 247 + ctx, span := s.t.TraceStart(r.Context(), "RepoDescriptionEdit") 248 + defer span.End() 249 + 250 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 206 251 if err != nil { 207 252 log.Println("failed to get repo and knot", err) 208 253 w.WriteHeader(http.StatusBadRequest) ··· 211 256 212 257 user := s.auth.GetUser(r) 213 258 s.pages.EditRepoDescriptionFragment(w, pages.RepoDescriptionParams{ 214 - RepoInfo: f.RepoInfo(s, user), 259 + RepoInfo: f.RepoInfo(ctx, s, user), 215 260 }) 216 261 return 217 262 } 218 263 219 264 func (s *State) RepoDescription(w http.ResponseWriter, r *http.Request) { 220 - f, err := s.fullyResolvedRepo(r) 265 + ctx, span := s.t.TraceStart(r.Context(), "RepoDescription") 266 + defer span.End() 267 + 268 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 221 269 if err != nil { 222 270 log.Println("failed to get repo and knot", err) 271 + span.RecordError(err) 272 + span.SetStatus(codes.Error, "failed to resolve repo") 223 273 w.WriteHeader(http.StatusBadRequest) 224 274 return 225 275 } ··· 228 278 rkey := repoAt.RecordKey().String() 229 279 if rkey == "" { 230 280 log.Println("invalid aturi for repo", err) 281 + span.RecordError(err) 282 + span.SetStatus(codes.Error, "invalid aturi for repo") 231 283 w.WriteHeader(http.StatusInternalServerError) 232 284 return 233 285 } 234 286 235 287 user := s.auth.GetUser(r) 288 + span.SetAttributes(attribute.String("method", r.Method)) 236 289 237 290 switch r.Method { 238 291 case http.MethodGet: 239 292 s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{ 240 - RepoInfo: f.RepoInfo(s, user), 293 + RepoInfo: f.RepoInfo(ctx, s, user), 241 294 }) 242 295 return 243 296 case http.MethodPut: 244 297 user := s.auth.GetUser(r) 245 298 newDescription := r.FormValue("description") 299 + span.SetAttributes(attribute.String("description", newDescription)) 246 300 client, _ := s.auth.AuthorizedClient(r) 247 301 248 302 // optimistic update 249 - err = db.UpdateDescription(s.db, string(repoAt), newDescription) 303 + err = db.UpdateDescription(ctx, s.db, string(repoAt), newDescription) 250 304 if err != nil { 251 - log.Println("failed to perferom update-description query", err) 305 + log.Println("failed to perform update-description query", err) 306 + span.RecordError(err) 307 + span.SetStatus(codes.Error, "failed to update description in database") 252 308 s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.") 253 309 return 254 310 } ··· 256 312 // this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field 257 313 // 258 314 // SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests 259 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoNSID, user.Did, rkey) 315 + ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoNSID, user.Did, rkey) 260 316 if err != nil { 261 317 // failed to get record 318 + span.RecordError(err) 319 + span.SetStatus(codes.Error, "failed to get record from PDS") 262 320 s.pages.Notice(w, "repo-notice", "Failed to update description, no record found on PDS.") 263 321 return 264 322 } 265 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 323 + 324 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 266 325 Collection: tangled.RepoNSID, 267 326 Repo: user.Did, 268 327 Rkey: rkey, ··· 279 338 }) 280 339 281 340 if err != nil { 282 - log.Println("failed to perferom update-description query", err) 341 + log.Println("failed to perform update-description query", err) 342 + span.RecordError(err) 343 + span.SetStatus(codes.Error, "failed to put record to PDS") 283 344 // failed to get record 284 345 s.pages.Notice(w, "repo-notice", "Failed to update description, unable to save to PDS.") 285 346 return 286 347 } 287 348 288 - newRepoInfo := f.RepoInfo(s, user) 349 + newRepoInfo := f.RepoInfo(ctx, s, user) 289 350 newRepoInfo.Description = newDescription 290 351 291 352 s.pages.RepoDescriptionFragment(w, pages.RepoDescriptionParams{ ··· 296 357 } 297 358 298 359 func (s *State) RepoCommit(w http.ResponseWriter, r *http.Request) { 299 - f, err := s.fullyResolvedRepo(r) 360 + ctx, span := s.t.TraceStart(r.Context(), "RepoCommit") 361 + defer span.End() 362 + 363 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 300 364 if err != nil { 301 365 log.Println("failed to fully resolve repo", err) 366 + span.RecordError(err) 367 + span.SetStatus(codes.Error, "failed to fully resolve repo") 302 368 return 303 369 } 304 370 ref := chi.URLParam(r, "ref") ··· 307 373 protocol = "https" 308 374 } 309 375 376 + span.SetAttributes(attribute.String("ref", ref), attribute.String("protocol", protocol)) 377 + 310 378 if !plumbing.IsHash(ref) { 379 + span.SetAttributes(attribute.Bool("invalid_hash", true)) 311 380 s.pages.Error404(w) 312 381 return 313 382 } 314 383 315 - resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/commit/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref)) 384 + requestURL := fmt.Sprintf("%s://%s/%s/%s/commit/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref) 385 + span.SetAttributes(attribute.String("request_url", requestURL)) 386 + 387 + resp, err := http.Get(requestURL) 316 388 if err != nil { 317 389 log.Println("failed to reach knotserver", err) 390 + span.RecordError(err) 391 + span.SetStatus(codes.Error, "failed to reach knotserver") 318 392 return 319 393 } 320 394 321 395 body, err := io.ReadAll(resp.Body) 322 396 if err != nil { 323 397 log.Printf("Error reading response body: %v", err) 398 + span.RecordError(err) 399 + span.SetStatus(codes.Error, "error reading response body") 324 400 return 325 401 } 326 402 ··· 328 404 err = json.Unmarshal(body, &result) 329 405 if err != nil { 330 406 log.Println("failed to parse response:", err) 407 + span.RecordError(err) 408 + span.SetStatus(codes.Error, "failed to parse response") 331 409 return 332 410 } 333 411 334 412 user := s.auth.GetUser(r) 335 413 s.pages.RepoCommit(w, pages.RepoCommitParams{ 336 414 LoggedInUser: user, 337 - RepoInfo: f.RepoInfo(s, user), 415 + RepoInfo: f.RepoInfo(ctx, s, user), 338 416 RepoCommitResponse: result, 339 417 EmailToDidOrHandle: EmailToDidOrHandle(s, []string{result.Diff.Commit.Author.Email}), 340 418 }) ··· 342 420 } 343 421 344 422 func (s *State) RepoTree(w http.ResponseWriter, r *http.Request) { 345 - f, err := s.fullyResolvedRepo(r) 423 + ctx, span := s.t.TraceStart(r.Context(), "RepoTree") 424 + defer span.End() 425 + 426 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 346 427 if err != nil { 347 428 log.Println("failed to fully resolve repo", err) 429 + span.RecordError(err) 430 + span.SetStatus(codes.Error, "failed to fully resolve repo") 348 431 return 349 432 } 350 433 ··· 354 437 if !s.config.Dev { 355 438 protocol = "https" 356 439 } 357 - resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath)) 440 + 441 + span.SetAttributes( 442 + attribute.String("ref", ref), 443 + attribute.String("tree_path", treePath), 444 + attribute.String("protocol", protocol), 445 + ) 446 + 447 + requestURL := fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, treePath) 448 + span.SetAttributes(attribute.String("request_url", requestURL)) 449 + 450 + resp, err := http.Get(requestURL) 358 451 if err != nil { 359 452 log.Println("failed to reach knotserver", err) 453 + span.RecordError(err) 454 + span.SetStatus(codes.Error, "failed to reach knotserver") 360 455 return 361 456 } 362 457 363 458 body, err := io.ReadAll(resp.Body) 364 459 if err != nil { 365 460 log.Printf("Error reading response body: %v", err) 461 + span.RecordError(err) 462 + span.SetStatus(codes.Error, "error reading response body") 366 463 return 367 464 } 368 465 ··· 370 467 err = json.Unmarshal(body, &result) 371 468 if err != nil { 372 469 log.Println("failed to parse response:", err) 470 + span.RecordError(err) 471 + span.SetStatus(codes.Error, "failed to parse response") 373 472 return 374 473 } 375 474 376 475 // redirects tree paths trying to access a blob; in this case the result.Files is unpopulated, 377 476 // so we can safely redirect to the "parent" (which is the same file). 378 477 if len(result.Files) == 0 && result.Parent == treePath { 379 - http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound) 478 + redirectURL := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent) 479 + span.SetAttributes(attribute.String("redirect_url", redirectURL)) 480 + http.Redirect(w, r, redirectURL, http.StatusFound) 380 481 return 381 482 } 382 483 ··· 398 499 BreadCrumbs: breadcrumbs, 399 500 BaseTreeLink: baseTreeLink, 400 501 BaseBlobLink: baseBlobLink, 401 - RepoInfo: f.RepoInfo(s, user), 502 + RepoInfo: f.RepoInfo(ctx, s, user), 402 503 RepoTreeResponse: result, 403 504 }) 404 505 return 405 506 } 406 507 407 508 func (s *State) RepoTags(w http.ResponseWriter, r *http.Request) { 408 - f, err := s.fullyResolvedRepo(r) 509 + ctx, span := s.t.TraceStart(r.Context(), "RepoTags") 510 + defer span.End() 511 + 512 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 409 513 if err != nil { 410 514 log.Println("failed to get repo and knot", err) 515 + span.RecordError(err) 516 + span.SetStatus(codes.Error, "failed to get repo and knot") 411 517 return 412 518 } 413 519 414 520 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 415 521 if err != nil { 416 522 log.Println("failed to create unsigned client", err) 523 + span.RecordError(err) 524 + span.SetStatus(codes.Error, "failed to create unsigned client") 417 525 return 418 526 } 419 527 420 528 result, err := us.Tags(f.OwnerDid(), f.RepoName) 421 529 if err != nil { 422 530 log.Println("failed to reach knotserver", err) 531 + span.RecordError(err) 532 + span.SetStatus(codes.Error, "failed to reach knotserver") 423 533 return 424 534 } 425 535 536 + span.SetAttributes(attribute.Int("tags.count", len(result.Tags))) 537 + 426 538 artifacts, err := db.GetArtifact(s.db, db.Filter("repo_at", f.RepoAt)) 427 539 if err != nil { 428 540 log.Println("failed grab artifacts", err) 541 + span.RecordError(err) 542 + span.SetStatus(codes.Error, "failed to grab artifacts") 429 543 return 430 544 } 545 + 546 + span.SetAttributes(attribute.Int("artifacts.count", len(artifacts))) 431 547 432 548 // convert artifacts to map for easy UI building 433 549 artifactMap := make(map[plumbing.Hash][]db.Artifact) ··· 451 567 } 452 568 } 453 569 570 + span.SetAttributes(attribute.Int("dangling_artifacts.count", len(danglingArtifacts))) 571 + 454 572 user := s.auth.GetUser(r) 455 573 s.pages.RepoTags(w, pages.RepoTagsParams{ 456 574 LoggedInUser: user, 457 - RepoInfo: f.RepoInfo(s, user), 575 + RepoInfo: f.RepoInfo(ctx, s, user), 458 576 RepoTagsResponse: *result, 459 577 ArtifactMap: artifactMap, 460 578 DanglingArtifacts: danglingArtifacts, ··· 463 581 } 464 582 465 583 func (s *State) RepoBranches(w http.ResponseWriter, r *http.Request) { 466 - f, err := s.fullyResolvedRepo(r) 584 + ctx, span := s.t.TraceStart(r.Context(), "RepoBranches") 585 + defer span.End() 586 + 587 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 467 588 if err != nil { 468 589 log.Println("failed to get repo and knot", err) 590 + span.RecordError(err) 591 + span.SetStatus(codes.Error, "failed to get repo and knot") 469 592 return 470 593 } 471 594 472 595 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 473 596 if err != nil { 474 597 log.Println("failed to create unsigned client", err) 598 + span.RecordError(err) 599 + span.SetStatus(codes.Error, "failed to create unsigned client") 475 600 return 476 601 } 477 602 478 603 resp, err := us.Branches(f.OwnerDid(), f.RepoName) 479 604 if err != nil { 480 605 log.Println("failed to reach knotserver", err) 606 + span.RecordError(err) 607 + span.SetStatus(codes.Error, "failed to reach knotserver") 481 608 return 482 609 } 483 610 484 611 body, err := io.ReadAll(resp.Body) 485 612 if err != nil { 486 613 log.Printf("Error reading response body: %v", err) 614 + span.RecordError(err) 615 + span.SetStatus(codes.Error, "error reading response body") 487 616 return 488 617 } 489 618 ··· 491 620 err = json.Unmarshal(body, &result) 492 621 if err != nil { 493 622 log.Println("failed to parse response:", err) 623 + span.RecordError(err) 624 + span.SetStatus(codes.Error, "failed to parse response") 494 625 return 495 626 } 627 + 628 + span.SetAttributes(attribute.Int("branches.count", len(result.Branches))) 496 629 497 630 slices.SortFunc(result.Branches, func(a, b types.Branch) int { 498 631 if a.IsDefault { ··· 514 647 user := s.auth.GetUser(r) 515 648 s.pages.RepoBranches(w, pages.RepoBranchesParams{ 516 649 LoggedInUser: user, 517 - RepoInfo: f.RepoInfo(s, user), 650 + RepoInfo: f.RepoInfo(ctx, s, user), 518 651 RepoBranchesResponse: result, 519 652 }) 520 653 return 521 654 } 522 655 523 656 func (s *State) RepoBlob(w http.ResponseWriter, r *http.Request) { 524 - f, err := s.fullyResolvedRepo(r) 657 + ctx, span := s.t.TraceStart(r.Context(), "RepoBlob") 658 + defer span.End() 659 + 660 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 525 661 if err != nil { 526 662 log.Println("failed to get repo and knot", err) 663 + span.RecordError(err) 664 + span.SetStatus(codes.Error, "failed to get repo and knot") 527 665 return 528 666 } 529 667 ··· 533 671 if !s.config.Dev { 534 672 protocol = "https" 535 673 } 536 - resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) 674 + 675 + span.SetAttributes( 676 + attribute.String("ref", ref), 677 + attribute.String("file_path", filePath), 678 + attribute.String("protocol", protocol), 679 + ) 680 + 681 + requestURL := fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath) 682 + span.SetAttributes(attribute.String("request_url", requestURL)) 683 + 684 + resp, err := http.Get(requestURL) 537 685 if err != nil { 538 686 log.Println("failed to reach knotserver", err) 687 + span.RecordError(err) 688 + span.SetStatus(codes.Error, "failed to reach knotserver") 539 689 return 540 690 } 541 691 542 692 body, err := io.ReadAll(resp.Body) 543 693 if err != nil { 544 694 log.Printf("Error reading response body: %v", err) 695 + span.RecordError(err) 696 + span.SetStatus(codes.Error, "error reading response body") 545 697 return 546 698 } 547 699 ··· 549 701 err = json.Unmarshal(body, &result) 550 702 if err != nil { 551 703 log.Println("failed to parse response:", err) 704 + span.RecordError(err) 705 + span.SetStatus(codes.Error, "failed to parse response") 552 706 return 553 707 } 554 708 ··· 568 722 showRendered = r.URL.Query().Get("code") != "true" 569 723 } 570 724 725 + span.SetAttributes( 726 + attribute.Bool("is_binary", result.IsBinary), 727 + attribute.Bool("show_rendered", showRendered), 728 + attribute.Bool("render_toggle", renderToggle), 729 + ) 730 + 571 731 user := s.auth.GetUser(r) 572 732 s.pages.RepoBlob(w, pages.RepoBlobParams{ 573 733 LoggedInUser: user, 574 - RepoInfo: f.RepoInfo(s, user), 734 + RepoInfo: f.RepoInfo(ctx, s, user), 575 735 RepoBlobResponse: result, 576 736 BreadCrumbs: breadcrumbs, 577 737 ShowRendered: showRendered, ··· 581 741 } 582 742 583 743 func (s *State) RepoBlobRaw(w http.ResponseWriter, r *http.Request) { 584 - f, err := s.fullyResolvedRepo(r) 744 + ctx, span := s.t.TraceStart(r.Context(), "RepoBlobRaw") 745 + defer span.End() 746 + 747 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 585 748 if err != nil { 586 749 log.Println("failed to get repo and knot", err) 750 + span.RecordError(err) 751 + span.SetStatus(codes.Error, "failed to get repo and knot") 587 752 return 588 753 } 589 754 ··· 594 759 if !s.config.Dev { 595 760 protocol = "https" 596 761 } 597 - resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)) 762 + 763 + span.SetAttributes( 764 + attribute.String("ref", ref), 765 + attribute.String("file_path", filePath), 766 + attribute.String("protocol", protocol), 767 + ) 768 + 769 + requestURL := fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath) 770 + span.SetAttributes(attribute.String("request_url", requestURL)) 771 + 772 + resp, err := http.Get(requestURL) 598 773 if err != nil { 599 774 log.Println("failed to reach knotserver", err) 775 + span.RecordError(err) 776 + span.SetStatus(codes.Error, "failed to reach knotserver") 600 777 return 601 778 } 602 779 603 780 body, err := io.ReadAll(resp.Body) 604 781 if err != nil { 605 782 log.Printf("Error reading response body: %v", err) 783 + span.RecordError(err) 784 + span.SetStatus(codes.Error, "error reading response body") 606 785 return 607 786 } 608 787 ··· 610 789 err = json.Unmarshal(body, &result) 611 790 if err != nil { 612 791 log.Println("failed to parse response:", err) 792 + span.RecordError(err) 793 + span.SetStatus(codes.Error, "failed to parse response") 613 794 return 614 795 } 796 + 797 + span.SetAttributes(attribute.Bool("is_binary", result.IsBinary)) 615 798 616 799 if result.IsBinary { 617 800 w.Header().Set("Content-Type", "application/octet-stream") ··· 625 808 } 626 809 627 810 func (s *State) AddCollaborator(w http.ResponseWriter, r *http.Request) { 628 - f, err := s.fullyResolvedRepo(r) 811 + ctx, span := s.t.TraceStart(r.Context(), "AddCollaborator") 812 + defer span.End() 813 + 814 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 629 815 if err != nil { 630 816 log.Println("failed to get repo and knot", err) 817 + span.RecordError(err) 818 + span.SetStatus(codes.Error, "failed to get repo and knot") 631 819 return 632 820 } 633 821 634 822 collaborator := r.FormValue("collaborator") 635 823 if collaborator == "" { 824 + span.SetAttributes(attribute.String("error", "malformed_form")) 636 825 http.Error(w, "malformed form", http.StatusBadRequest) 637 826 return 638 827 } 639 828 640 - collaboratorIdent, err := s.resolver.ResolveIdent(r.Context(), collaborator) 829 + span.SetAttributes(attribute.String("collaborator", collaborator)) 830 + 831 + collaboratorIdent, err := s.resolver.ResolveIdent(ctx, collaborator) 641 832 if err != nil { 833 + span.RecordError(err) 834 + span.SetStatus(codes.Error, "failed to resolve collaborator") 642 835 w.Write([]byte("failed to resolve collaborator did to a handle")) 643 836 return 644 837 } 645 838 log.Printf("adding %s to %s\n", collaboratorIdent.Handle.String(), f.Knot) 839 + span.SetAttributes( 840 + attribute.String("collaborator_did", collaboratorIdent.DID.String()), 841 + attribute.String("collaborator_handle", collaboratorIdent.Handle.String()), 842 + ) 646 843 647 844 // TODO: create an atproto record for this 648 845 649 846 secret, err := db.GetRegistrationKey(s.db, f.Knot) 650 847 if err != nil { 651 848 log.Printf("no key found for domain %s: %s\n", f.Knot, err) 849 + span.RecordError(err) 850 + span.SetStatus(codes.Error, "no key found for domain") 652 851 return 653 852 } 654 853 655 854 ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 656 855 if err != nil { 657 856 log.Println("failed to create client to ", f.Knot) 857 + span.RecordError(err) 858 + span.SetStatus(codes.Error, "failed to create signed client") 658 859 return 659 860 } 660 861 661 862 ksResp, err := ksClient.AddCollaborator(f.OwnerDid(), f.RepoName, collaboratorIdent.DID.String()) 662 863 if err != nil { 663 864 log.Printf("failed to make request to %s: %s", f.Knot, err) 865 + span.RecordError(err) 866 + span.SetStatus(codes.Error, "failed to make request to knotserver") 664 867 return 665 868 } 666 869 667 870 if ksResp.StatusCode != http.StatusNoContent { 871 + span.SetAttributes(attribute.Int("status_code", ksResp.StatusCode)) 668 872 w.Write([]byte(fmt.Sprint("knotserver failed to add collaborator: ", err))) 669 873 return 670 874 } 671 875 672 - tx, err := s.db.BeginTx(r.Context(), nil) 876 + tx, err := s.db.BeginTx(ctx, nil) 673 877 if err != nil { 674 878 log.Println("failed to start tx") 879 + span.RecordError(err) 880 + span.SetStatus(codes.Error, "failed to start transaction") 675 881 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) 676 882 return 677 883 } ··· 685 891 686 892 err = s.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo()) 687 893 if err != nil { 894 + span.RecordError(err) 895 + span.SetStatus(codes.Error, "failed to add collaborator to enforcer") 688 896 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) 689 897 return 690 898 } 691 899 692 - err = db.AddCollaborator(s.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot) 900 + err = db.AddCollaborator(ctx, s.db, collaboratorIdent.DID.String(), f.OwnerDid(), f.RepoName, f.Knot) 693 901 if err != nil { 902 + span.RecordError(err) 903 + span.SetStatus(codes.Error, "failed to add collaborator to database") 694 904 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) 695 905 return 696 906 } ··· 698 908 err = tx.Commit() 699 909 if err != nil { 700 910 log.Println("failed to commit changes", err) 911 + span.RecordError(err) 912 + span.SetStatus(codes.Error, "failed to commit transaction") 701 913 http.Error(w, err.Error(), http.StatusInternalServerError) 702 914 return 703 915 } ··· 705 917 err = s.enforcer.E.SavePolicy() 706 918 if err != nil { 707 919 log.Println("failed to update ACLs", err) 920 + span.RecordError(err) 921 + span.SetStatus(codes.Error, "failed to save enforcer policy") 708 922 http.Error(w, err.Error(), http.StatusInternalServerError) 709 923 return 710 924 } 711 925 712 926 w.Write([]byte(fmt.Sprint("added collaborator: ", collaboratorIdent.Handle.String()))) 713 - 714 927 } 715 928 716 929 func (s *State) DeleteRepo(w http.ResponseWriter, r *http.Request) { 930 + ctx, span := s.t.TraceStart(r.Context(), "DeleteRepo") 931 + defer span.End() 932 + 717 933 user := s.auth.GetUser(r) 718 934 719 - f, err := s.fullyResolvedRepo(r) 935 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 720 936 if err != nil { 721 937 log.Println("failed to get repo and knot", err) 938 + span.RecordError(err) 939 + span.SetStatus(codes.Error, "failed to get repo and knot") 722 940 return 723 941 } 724 942 943 + span.SetAttributes( 944 + attribute.String("repo_name", f.RepoName), 945 + attribute.String("knot", f.Knot), 946 + attribute.String("owner_did", f.OwnerDid()), 947 + ) 948 + 725 949 // remove record from pds 726 950 xrpcClient, _ := s.auth.AuthorizedClient(r) 727 951 repoRkey := f.RepoAt.RecordKey().String() 728 - _, err = comatproto.RepoDeleteRecord(r.Context(), xrpcClient, &comatproto.RepoDeleteRecord_Input{ 952 + _, err = comatproto.RepoDeleteRecord(ctx, xrpcClient, &comatproto.RepoDeleteRecord_Input{ 729 953 Collection: tangled.RepoNSID, 730 954 Repo: user.Did, 731 955 Rkey: repoRkey, 732 956 }) 733 957 if err != nil { 734 958 log.Printf("failed to delete record: %s", err) 959 + span.RecordError(err) 960 + span.SetStatus(codes.Error, "failed to delete record from PDS") 735 961 s.pages.Notice(w, "settings-delete", "Failed to delete repository from PDS.") 736 962 return 737 963 } 738 964 log.Println("removed repo record ", f.RepoAt.String()) 965 + span.SetAttributes(attribute.String("repo_at", f.RepoAt.String())) 739 966 740 967 secret, err := db.GetRegistrationKey(s.db, f.Knot) 741 968 if err != nil { 742 969 log.Printf("no key found for domain %s: %s\n", f.Knot, err) 970 + span.RecordError(err) 971 + span.SetStatus(codes.Error, "no key found for domain") 743 972 return 744 973 } 745 974 746 975 ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 747 976 if err != nil { 748 977 log.Println("failed to create client to ", f.Knot) 978 + span.RecordError(err) 979 + span.SetStatus(codes.Error, "failed to create client") 749 980 return 750 981 } 751 982 752 983 ksResp, err := ksClient.RemoveRepo(f.OwnerDid(), f.RepoName) 753 984 if err != nil { 754 985 log.Printf("failed to make request to %s: %s", f.Knot, err) 986 + span.RecordError(err) 987 + span.SetStatus(codes.Error, "failed to make request to knotserver") 755 988 return 756 989 } 757 990 991 + span.SetAttributes(attribute.Int("ks_status_code", ksResp.StatusCode)) 758 992 if ksResp.StatusCode != http.StatusNoContent { 759 993 log.Println("failed to remove repo from knot, continuing anyway ", f.Knot) 994 + span.SetAttributes(attribute.Bool("knot_remove_failed", true)) 760 995 } else { 761 996 log.Println("removed repo from knot ", f.Knot) 997 + span.SetAttributes(attribute.Bool("knot_remove_success", true)) 762 998 } 763 999 764 - tx, err := s.db.BeginTx(r.Context(), nil) 1000 + tx, err := s.db.BeginTx(ctx, nil) 765 1001 if err != nil { 766 1002 log.Println("failed to start tx") 1003 + span.RecordError(err) 1004 + span.SetStatus(codes.Error, "failed to start transaction") 767 1005 w.Write([]byte(fmt.Sprint("failed to add collaborator: ", err))) 768 1006 return 769 1007 } ··· 772 1010 err = s.enforcer.E.LoadPolicy() 773 1011 if err != nil { 774 1012 log.Println("failed to rollback policies") 1013 + span.RecordError(err) 775 1014 } 776 1015 }() 777 1016 778 1017 // remove collaborator RBAC 779 1018 repoCollaborators, err := s.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot) 780 1019 if err != nil { 1020 + span.RecordError(err) 1021 + span.SetStatus(codes.Error, "failed to get collaborators") 781 1022 s.pages.Notice(w, "settings-delete", "Failed to remove collaborators") 782 1023 return 783 1024 } 1025 + span.SetAttributes(attribute.Int("collaborators.count", len(repoCollaborators))) 1026 + 784 1027 for _, c := range repoCollaborators { 785 1028 did := c[0] 786 1029 s.enforcer.RemoveCollaborator(did, f.Knot, f.DidSlashRepo()) ··· 790 1033 // remove repo RBAC 791 1034 err = s.enforcer.RemoveRepo(f.OwnerDid(), f.Knot, f.DidSlashRepo()) 792 1035 if err != nil { 1036 + span.RecordError(err) 1037 + span.SetStatus(codes.Error, "failed to remove repo RBAC") 793 1038 s.pages.Notice(w, "settings-delete", "Failed to update RBAC rules") 794 1039 return 795 1040 } 796 1041 797 1042 // remove repo from db 798 - err = db.RemoveRepo(tx, f.OwnerDid(), f.RepoName) 1043 + err = db.RemoveRepo(ctx, tx, f.OwnerDid(), f.RepoName) 799 1044 if err != nil { 1045 + span.RecordError(err) 1046 + span.SetStatus(codes.Error, "failed to remove repo from db") 800 1047 s.pages.Notice(w, "settings-delete", "Failed to update appview") 801 1048 return 802 1049 } ··· 805 1052 err = tx.Commit() 806 1053 if err != nil { 807 1054 log.Println("failed to commit changes", err) 1055 + span.RecordError(err) 1056 + span.SetStatus(codes.Error, "failed to commit transaction") 808 1057 http.Error(w, err.Error(), http.StatusInternalServerError) 809 1058 return 810 1059 } ··· 812 1061 err = s.enforcer.E.SavePolicy() 813 1062 if err != nil { 814 1063 log.Println("failed to update ACLs", err) 1064 + span.RecordError(err) 1065 + span.SetStatus(codes.Error, "failed to save policy") 815 1066 http.Error(w, err.Error(), http.StatusInternalServerError) 816 1067 return 817 1068 } ··· 820 1071 } 821 1072 822 1073 func (s *State) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 823 - f, err := s.fullyResolvedRepo(r) 1074 + ctx, span := s.t.TraceStart(r.Context(), "SetDefaultBranch") 1075 + defer span.End() 1076 + 1077 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 824 1078 if err != nil { 825 1079 log.Println("failed to get repo and knot", err) 1080 + span.RecordError(err) 1081 + span.SetStatus(codes.Error, "failed to get repo and knot") 826 1082 return 827 1083 } 828 1084 829 1085 branch := r.FormValue("branch") 830 1086 if branch == "" { 1087 + span.SetAttributes(attribute.Bool("malformed_form", true)) 1088 + span.SetStatus(codes.Error, "malformed form") 831 1089 http.Error(w, "malformed form", http.StatusBadRequest) 832 1090 return 833 1091 } 834 1092 1093 + span.SetAttributes( 1094 + attribute.String("branch", branch), 1095 + attribute.String("repo_name", f.RepoName), 1096 + attribute.String("knot", f.Knot), 1097 + attribute.String("owner_did", f.OwnerDid()), 1098 + ) 1099 + 835 1100 secret, err := db.GetRegistrationKey(s.db, f.Knot) 836 1101 if err != nil { 837 1102 log.Printf("no key found for domain %s: %s\n", f.Knot, err) 1103 + span.RecordError(err) 1104 + span.SetStatus(codes.Error, "no key found for domain") 838 1105 return 839 1106 } 840 1107 841 1108 ksClient, err := NewSignedClient(f.Knot, secret, s.config.Dev) 842 1109 if err != nil { 843 1110 log.Println("failed to create client to ", f.Knot) 1111 + span.RecordError(err) 1112 + span.SetStatus(codes.Error, "failed to create client") 844 1113 return 845 1114 } 846 1115 847 1116 ksResp, err := ksClient.SetDefaultBranch(f.OwnerDid(), f.RepoName, branch) 848 1117 if err != nil { 849 1118 log.Printf("failed to make request to %s: %s", f.Knot, err) 1119 + span.RecordError(err) 1120 + span.SetStatus(codes.Error, "failed to make request to knotserver") 850 1121 return 851 1122 } 852 1123 1124 + span.SetAttributes(attribute.Int("ks_status_code", ksResp.StatusCode)) 853 1125 if ksResp.StatusCode != http.StatusNoContent { 1126 + span.SetStatus(codes.Error, "failed to set default branch") 854 1127 s.pages.Notice(w, "repo-settings", "Failed to set default branch. Try again later.") 855 1128 return 856 1129 } ··· 859 1132 } 860 1133 861 1134 func (s *State) RepoSettings(w http.ResponseWriter, r *http.Request) { 862 - f, err := s.fullyResolvedRepo(r) 1135 + ctx, span := s.t.TraceStart(r.Context(), "RepoSettings") 1136 + defer span.End() 1137 + 1138 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 863 1139 if err != nil { 864 1140 log.Println("failed to get repo and knot", err) 1141 + span.RecordError(err) 1142 + span.SetStatus(codes.Error, "failed to get repo and knot") 865 1143 return 866 1144 } 867 1145 1146 + span.SetAttributes( 1147 + attribute.String("repo_name", f.RepoName), 1148 + attribute.String("knot", f.Knot), 1149 + attribute.String("owner_did", f.OwnerDid()), 1150 + attribute.String("method", r.Method), 1151 + ) 1152 + 868 1153 switch r.Method { 869 1154 case http.MethodGet: 870 1155 // for now, this is just pubkeys 871 1156 user := s.auth.GetUser(r) 872 - repoCollaborators, err := f.Collaborators(r.Context(), s) 1157 + repoCollaborators, err := f.Collaborators(ctx, s) 873 1158 if err != nil { 874 1159 log.Println("failed to get collaborators", err) 1160 + span.RecordError(err) 1161 + span.SetAttributes(attribute.String("error", "failed_to_get_collaborators")) 875 1162 } 1163 + span.SetAttributes(attribute.Int("collaborators.count", len(repoCollaborators))) 876 1164 877 1165 isCollaboratorInviteAllowed := false 878 1166 if user != nil { ··· 881 1169 isCollaboratorInviteAllowed = true 882 1170 } 883 1171 } 1172 + span.SetAttributes(attribute.Bool("invite_allowed", isCollaboratorInviteAllowed)) 884 1173 885 1174 var branchNames []string 886 1175 var defaultBranch string 887 1176 us, err := NewUnsignedClient(f.Knot, s.config.Dev) 888 1177 if err != nil { 889 1178 log.Println("failed to create unsigned client", err) 1179 + span.RecordError(err) 1180 + span.SetAttributes(attribute.String("error", "failed_to_create_unsigned_client")) 890 1181 } else { 891 1182 resp, err := us.Branches(f.OwnerDid(), f.RepoName) 892 1183 if err != nil { 893 1184 log.Println("failed to reach knotserver", err) 1185 + span.RecordError(err) 1186 + span.SetAttributes(attribute.String("error", "failed_to_reach_knotserver_for_branches")) 894 1187 } else { 895 1188 defer resp.Body.Close() 896 1189 897 1190 body, err := io.ReadAll(resp.Body) 898 1191 if err != nil { 899 1192 log.Printf("Error reading response body: %v", err) 1193 + span.RecordError(err) 1194 + span.SetAttributes(attribute.String("error", "failed_to_read_branches_response")) 900 1195 } else { 901 1196 var result types.RepoBranchesResponse 902 1197 err = json.Unmarshal(body, &result) 903 1198 if err != nil { 904 1199 log.Println("failed to parse response:", err) 1200 + span.RecordError(err) 1201 + span.SetAttributes(attribute.String("error", "failed_to_parse_branches_response")) 905 1202 } else { 906 1203 for _, branch := range result.Branches { 907 1204 branchNames = append(branchNames, branch.Name) 908 1205 } 1206 + span.SetAttributes(attribute.Int("branches.count", len(branchNames))) 909 1207 } 910 1208 } 911 1209 } ··· 913 1211 defaultBranchResp, err := us.DefaultBranch(f.OwnerDid(), f.RepoName) 914 1212 if err != nil { 915 1213 log.Println("failed to reach knotserver", err) 1214 + span.RecordError(err) 1215 + span.SetAttributes(attribute.String("error", "failed_to_reach_knotserver_for_default_branch")) 916 1216 } else { 917 1217 defaultBranch = defaultBranchResp.Branch 1218 + span.SetAttributes(attribute.String("default_branch", defaultBranch)) 918 1219 } 919 1220 } 920 1221 s.pages.RepoSettings(w, pages.RepoSettingsParams{ 921 1222 LoggedInUser: user, 922 - RepoInfo: f.RepoInfo(s, user), 1223 + RepoInfo: f.RepoInfo(ctx, s, user), 923 1224 Collaborators: repoCollaborators, 924 1225 IsCollaboratorInviteAllowed: isCollaboratorInviteAllowed, 925 1226 Branches: branchNames, ··· 1008 1309 return collaborators, nil 1009 1310 } 1010 1311 1011 - func (f *FullyResolvedRepo) RepoInfo(s *State, u *auth.User) repoinfo.RepoInfo { 1312 + func (f *FullyResolvedRepo) RepoInfo(ctx context.Context, s *State, u *auth.User) repoinfo.RepoInfo { 1313 + ctx, span := s.t.TraceStart(ctx, "RepoInfo") 1314 + defer span.End() 1315 + 1012 1316 isStarred := false 1013 1317 if u != nil { 1014 1318 isStarred = db.GetStarStatus(s.db, u.Did, syntax.ATURI(f.RepoAt)) 1319 + span.SetAttributes(attribute.Bool("is_starred", isStarred)) 1015 1320 } 1016 1321 1017 1322 starCount, err := db.GetStarCount(s.db, f.RepoAt) 1018 1323 if err != nil { 1019 1324 log.Println("failed to get star count for ", f.RepoAt) 1325 + span.RecordError(err) 1020 1326 } 1327 + 1021 1328 issueCount, err := db.GetIssueCount(s.db, f.RepoAt) 1022 1329 if err != nil { 1023 1330 log.Println("failed to get issue count for ", f.RepoAt) 1331 + span.RecordError(err) 1024 1332 } 1333 + 1025 1334 pullCount, err := db.GetPullCount(s.db, f.RepoAt) 1026 1335 if err != nil { 1027 1336 log.Println("failed to get issue count for ", f.RepoAt) 1337 + span.RecordError(err) 1028 1338 } 1029 - source, err := db.GetRepoSource(s.db, f.RepoAt) 1339 + 1340 + span.SetAttributes( 1341 + attribute.Int("stats.stars", starCount), 1342 + attribute.Int("stats.issues.open", issueCount.Open), 1343 + attribute.Int("stats.issues.closed", issueCount.Closed), 1344 + attribute.Int("stats.pulls.open", pullCount.Open), 1345 + attribute.Int("stats.pulls.closed", pullCount.Closed), 1346 + attribute.Int("stats.pulls.merged", pullCount.Merged), 1347 + ) 1348 + 1349 + source, err := db.GetRepoSource(ctx, s.db, f.RepoAt) 1030 1350 if errors.Is(err, sql.ErrNoRows) { 1031 1351 source = "" 1032 1352 } else if err != nil { 1033 1353 log.Println("failed to get repo source for ", f.RepoAt, err) 1354 + span.RecordError(err) 1034 1355 } 1035 1356 1036 1357 var sourceRepo *db.Repo 1037 1358 if source != "" { 1038 - sourceRepo, err = db.GetRepoByAtUri(s.db, source) 1359 + span.SetAttributes(attribute.String("source", source)) 1360 + sourceRepo, err = db.GetRepoByAtUri(ctx, s.db, source) 1039 1361 if err != nil { 1040 1362 log.Println("failed to get repo by at uri", err) 1363 + span.RecordError(err) 1041 1364 } 1042 1365 } 1043 1366 1044 1367 var sourceHandle *identity.Identity 1045 1368 if sourceRepo != nil { 1046 - sourceHandle, err = s.resolver.ResolveIdent(context.Background(), sourceRepo.Did) 1369 + sourceHandle, err = s.resolver.ResolveIdent(ctx, sourceRepo.Did) 1047 1370 if err != nil { 1048 1371 log.Println("failed to resolve source repo", err) 1372 + span.RecordError(err) 1373 + } else if sourceHandle != nil { 1374 + span.SetAttributes(attribute.String("source_handle", sourceHandle.Handle.String())) 1049 1375 } 1050 1376 } 1051 1377 1052 1378 knot := f.Knot 1379 + span.SetAttributes(attribute.String("knot", knot)) 1380 + 1053 1381 var disableFork bool 1054 1382 us, err := NewUnsignedClient(knot, s.config.Dev) 1055 1383 if err != nil { 1056 1384 log.Printf("failed to create unsigned client for %s: %v", knot, err) 1385 + span.RecordError(err) 1057 1386 } else { 1058 1387 resp, err := us.Branches(f.OwnerDid(), f.RepoName) 1059 1388 if err != nil { 1060 1389 log.Printf("failed to get branches for %s/%s: %v", f.OwnerDid(), f.RepoName, err) 1390 + span.RecordError(err) 1061 1391 } else { 1062 1392 defer resp.Body.Close() 1063 1393 body, err := io.ReadAll(resp.Body) 1064 1394 if err != nil { 1065 1395 log.Printf("error reading branch response body: %v", err) 1396 + span.RecordError(err) 1066 1397 } else { 1067 1398 var branchesResp types.RepoBranchesResponse 1068 1399 if err := json.Unmarshal(body, &branchesResp); err != nil { 1069 1400 log.Printf("error parsing branch response: %v", err) 1401 + span.RecordError(err) 1070 1402 } else { 1071 1403 disableFork = false 1072 1404 } ··· 1074 1406 if len(branchesResp.Branches) == 0 { 1075 1407 disableFork = true 1076 1408 } 1409 + span.SetAttributes( 1410 + attribute.Int("branches.count", len(branchesResp.Branches)), 1411 + attribute.Bool("disable_fork", disableFork), 1412 + ) 1077 1413 } 1078 1414 } 1079 1415 } ··· 1105 1441 } 1106 1442 1107 1443 func (s *State) RepoSingleIssue(w http.ResponseWriter, r *http.Request) { 1444 + ctx, span := s.t.TraceStart(r.Context(), "RepoSingleIssue") 1445 + defer span.End() 1446 + 1108 1447 user := s.auth.GetUser(r) 1109 - f, err := s.fullyResolvedRepo(r) 1448 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1110 1449 if err != nil { 1111 1450 log.Println("failed to get repo and knot", err) 1451 + span.RecordError(err) 1452 + span.SetStatus(codes.Error, "failed to resolve repo") 1112 1453 return 1113 1454 } 1114 1455 ··· 1117 1458 if err != nil { 1118 1459 http.Error(w, "bad issue id", http.StatusBadRequest) 1119 1460 log.Println("failed to parse issue id", err) 1461 + span.RecordError(err) 1462 + span.SetStatus(codes.Error, "failed to parse issue id") 1120 1463 return 1121 1464 } 1122 1465 1123 - issue, comments, err := db.GetIssueWithComments(s.db, f.RepoAt, issueIdInt) 1466 + span.SetAttributes(attribute.Int("issue_id", issueIdInt)) 1467 + 1468 + issue, comments, err := db.GetIssueWithComments(ctx, s.db, f.RepoAt, issueIdInt) 1124 1469 if err != nil { 1125 1470 log.Println("failed to get issue and comments", err) 1471 + span.RecordError(err) 1472 + span.SetStatus(codes.Error, "failed to get issue and comments") 1126 1473 s.pages.Notice(w, "issues", "Failed to load issue. Try again later.") 1127 1474 return 1128 1475 } 1129 1476 1130 - issueOwnerIdent, err := s.resolver.ResolveIdent(r.Context(), issue.OwnerDid) 1477 + span.SetAttributes( 1478 + attribute.Int("comments.count", len(comments)), 1479 + attribute.String("issue.title", issue.Title), 1480 + attribute.String("issue.owner_did", issue.OwnerDid), 1481 + ) 1482 + 1483 + issueOwnerIdent, err := s.resolver.ResolveIdent(ctx, issue.OwnerDid) 1131 1484 if err != nil { 1132 1485 log.Println("failed to resolve issue owner", err) 1486 + span.RecordError(err) 1487 + span.SetStatus(codes.Error, "failed to resolve issue owner") 1133 1488 } 1134 1489 1135 1490 identsToResolve := make([]string, len(comments)) 1136 1491 for i, comment := range comments { 1137 1492 identsToResolve[i] = comment.OwnerDid 1138 1493 } 1139 - resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 1494 + resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve) 1140 1495 didHandleMap := make(map[string]string) 1141 1496 for _, identity := range resolvedIds { 1142 1497 if !identity.Handle.IsInvalidHandle() { ··· 1148 1503 1149 1504 s.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 1150 1505 LoggedInUser: user, 1151 - RepoInfo: f.RepoInfo(s, user), 1506 + RepoInfo: f.RepoInfo(ctx, s, user), 1152 1507 Issue: *issue, 1153 1508 Comments: comments, 1154 1509 1155 1510 IssueOwnerHandle: issueOwnerIdent.Handle.String(), 1156 1511 DidHandleMap: didHandleMap, 1157 1512 }) 1158 - 1159 1513 } 1160 1514 1161 1515 func (s *State) CloseIssue(w http.ResponseWriter, r *http.Request) { 1516 + ctx, span := s.t.TraceStart(r.Context(), "CloseIssue") 1517 + defer span.End() 1518 + 1162 1519 user := s.auth.GetUser(r) 1163 - f, err := s.fullyResolvedRepo(r) 1520 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1164 1521 if err != nil { 1165 1522 log.Println("failed to get repo and knot", err) 1523 + span.RecordError(err) 1524 + span.SetStatus(codes.Error, "failed to resolve repo") 1166 1525 return 1167 1526 } 1168 1527 ··· 1171 1530 if err != nil { 1172 1531 http.Error(w, "bad issue id", http.StatusBadRequest) 1173 1532 log.Println("failed to parse issue id", err) 1533 + span.RecordError(err) 1534 + span.SetStatus(codes.Error, "failed to parse issue id") 1174 1535 return 1175 1536 } 1176 1537 1177 - issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt) 1538 + span.SetAttributes(attribute.Int("issue_id", issueIdInt)) 1539 + 1540 + issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt) 1178 1541 if err != nil { 1179 1542 log.Println("failed to get issue", err) 1543 + span.RecordError(err) 1544 + span.SetStatus(codes.Error, "failed to get issue") 1180 1545 s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 1181 1546 return 1182 1547 } 1183 1548 1184 - collaborators, err := f.Collaborators(r.Context(), s) 1549 + collaborators, err := f.Collaborators(ctx, s) 1185 1550 if err != nil { 1186 1551 log.Println("failed to fetch repo collaborators: %w", err) 1552 + span.RecordError(err) 1553 + span.SetStatus(codes.Error, "failed to fetch repo collaborators") 1187 1554 } 1188 1555 isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool { 1189 1556 return user.Did == collab.Did 1190 1557 }) 1191 1558 isIssueOwner := user.Did == issue.OwnerDid 1192 1559 1560 + span.SetAttributes( 1561 + attribute.Bool("is_collaborator", isCollaborator), 1562 + attribute.Bool("is_issue_owner", isIssueOwner), 1563 + ) 1564 + 1193 1565 // TODO: make this more granular 1194 1566 if isIssueOwner || isCollaborator { 1195 - 1196 1567 closed := tangled.RepoIssueStateClosed 1197 1568 1198 1569 client, _ := s.auth.AuthorizedClient(r) 1199 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1570 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 1200 1571 Collection: tangled.RepoIssueStateNSID, 1201 1572 Repo: user.Did, 1202 1573 Rkey: appview.TID(), ··· 1210 1581 1211 1582 if err != nil { 1212 1583 log.Println("failed to update issue state", err) 1584 + span.RecordError(err) 1585 + span.SetStatus(codes.Error, "failed to update issue state in PDS") 1213 1586 s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 1214 1587 return 1215 1588 } ··· 1217 1590 err := db.CloseIssue(s.db, f.RepoAt, issueIdInt) 1218 1591 if err != nil { 1219 1592 log.Println("failed to close issue", err) 1593 + span.RecordError(err) 1594 + span.SetStatus(codes.Error, "failed to close issue in database") 1220 1595 s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 1221 1596 return 1222 1597 } ··· 1225 1600 return 1226 1601 } else { 1227 1602 log.Println("user is not permitted to close issue") 1603 + span.SetAttributes(attribute.Bool("permission_denied", true)) 1228 1604 http.Error(w, "for biden", http.StatusUnauthorized) 1229 1605 return 1230 1606 } 1231 1607 } 1232 1608 1233 1609 func (s *State) ReopenIssue(w http.ResponseWriter, r *http.Request) { 1610 + ctx, span := s.t.TraceStart(r.Context(), "ReopenIssue") 1611 + defer span.End() 1612 + 1234 1613 user := s.auth.GetUser(r) 1235 - f, err := s.fullyResolvedRepo(r) 1614 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1236 1615 if err != nil { 1237 1616 log.Println("failed to get repo and knot", err) 1617 + span.RecordError(err) 1618 + span.SetStatus(codes.Error, "failed to resolve repo") 1238 1619 return 1239 1620 } 1240 1621 ··· 1243 1624 if err != nil { 1244 1625 http.Error(w, "bad issue id", http.StatusBadRequest) 1245 1626 log.Println("failed to parse issue id", err) 1627 + span.RecordError(err) 1628 + span.SetStatus(codes.Error, "failed to parse issue id") 1246 1629 return 1247 1630 } 1248 1631 1249 - issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt) 1632 + span.SetAttributes(attribute.Int("issue_id", issueIdInt)) 1633 + 1634 + issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt) 1250 1635 if err != nil { 1251 1636 log.Println("failed to get issue", err) 1637 + span.RecordError(err) 1638 + span.SetStatus(codes.Error, "failed to get issue") 1252 1639 s.pages.Notice(w, "issue-action", "Failed to close issue. Try again later.") 1253 1640 return 1254 1641 } 1255 1642 1256 - collaborators, err := f.Collaborators(r.Context(), s) 1643 + collaborators, err := f.Collaborators(ctx, s) 1257 1644 if err != nil { 1258 1645 log.Println("failed to fetch repo collaborators: %w", err) 1646 + span.RecordError(err) 1647 + span.SetStatus(codes.Error, "failed to fetch repo collaborators") 1259 1648 } 1260 1649 isCollaborator := slices.ContainsFunc(collaborators, func(collab pages.Collaborator) bool { 1261 1650 return user.Did == collab.Did 1262 1651 }) 1263 1652 isIssueOwner := user.Did == issue.OwnerDid 1653 + 1654 + span.SetAttributes( 1655 + attribute.Bool("is_collaborator", isCollaborator), 1656 + attribute.Bool("is_issue_owner", isIssueOwner), 1657 + ) 1264 1658 1265 1659 if isCollaborator || isIssueOwner { 1266 1660 err := db.ReopenIssue(s.db, f.RepoAt, issueIdInt) 1267 1661 if err != nil { 1268 1662 log.Println("failed to reopen issue", err) 1663 + span.RecordError(err) 1664 + span.SetStatus(codes.Error, "failed to reopen issue") 1269 1665 s.pages.Notice(w, "issue-action", "Failed to reopen issue. Try again later.") 1270 1666 return 1271 1667 } ··· 1273 1669 return 1274 1670 } else { 1275 1671 log.Println("user is not the owner of the repo") 1672 + span.SetAttributes(attribute.Bool("permission_denied", true)) 1276 1673 http.Error(w, "forbidden", http.StatusUnauthorized) 1277 1674 return 1278 1675 } 1279 1676 } 1280 1677 1281 1678 func (s *State) NewIssueComment(w http.ResponseWriter, r *http.Request) { 1679 + ctx, span := s.t.TraceStart(r.Context(), "NewIssueComment") 1680 + defer span.End() 1681 + 1282 1682 user := s.auth.GetUser(r) 1283 - f, err := s.fullyResolvedRepo(r) 1683 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1284 1684 if err != nil { 1285 1685 log.Println("failed to get repo and knot", err) 1686 + span.RecordError(err) 1687 + span.SetStatus(codes.Error, "failed to resolve repo") 1286 1688 return 1287 1689 } 1288 1690 ··· 1291 1693 if err != nil { 1292 1694 http.Error(w, "bad issue id", http.StatusBadRequest) 1293 1695 log.Println("failed to parse issue id", err) 1696 + span.RecordError(err) 1697 + span.SetStatus(codes.Error, "failed to parse issue id") 1294 1698 return 1295 1699 } 1296 1700 1701 + span.SetAttributes( 1702 + attribute.Int("issue_id", issueIdInt), 1703 + attribute.String("method", r.Method), 1704 + ) 1705 + 1297 1706 switch r.Method { 1298 1707 case http.MethodPost: 1299 1708 body := r.FormValue("body") 1300 1709 if body == "" { 1710 + span.SetAttributes(attribute.Bool("missing_body", true)) 1301 1711 s.pages.Notice(w, "issue", "Body is required") 1302 1712 return 1303 1713 } ··· 1305 1715 commentId := mathrand.IntN(1000000) 1306 1716 rkey := appview.TID() 1307 1717 1718 + span.SetAttributes( 1719 + attribute.Int("comment_id", commentId), 1720 + attribute.String("rkey", rkey), 1721 + ) 1722 + 1308 1723 err := db.NewIssueComment(s.db, &db.Comment{ 1309 1724 OwnerDid: user.Did, 1310 1725 RepoAt: f.RepoAt, ··· 1315 1730 }) 1316 1731 if err != nil { 1317 1732 log.Println("failed to create comment", err) 1733 + span.RecordError(err) 1734 + span.SetStatus(codes.Error, "failed to create comment in database") 1318 1735 s.pages.Notice(w, "issue-comment", "Failed to create comment.") 1319 1736 return 1320 1737 } ··· 1325 1742 issueAt, err := db.GetIssueAt(s.db, f.RepoAt, issueIdInt) 1326 1743 if err != nil { 1327 1744 log.Println("failed to get issue at", err) 1745 + span.RecordError(err) 1746 + span.SetStatus(codes.Error, "failed to get issue at") 1328 1747 s.pages.Notice(w, "issue-comment", "Failed to create comment.") 1329 1748 return 1330 1749 } 1331 1750 1751 + span.SetAttributes(attribute.String("issue_at", issueAt)) 1752 + 1332 1753 atUri := f.RepoAt.String() 1333 1754 client, _ := s.auth.AuthorizedClient(r) 1334 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1755 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 1335 1756 Collection: tangled.RepoIssueCommentNSID, 1336 1757 Repo: user.Did, 1337 1758 Rkey: rkey, ··· 1348 1769 }) 1349 1770 if err != nil { 1350 1771 log.Println("failed to create comment", err) 1772 + span.RecordError(err) 1773 + span.SetStatus(codes.Error, "failed to create comment in PDS") 1351 1774 s.pages.Notice(w, "issue-comment", "Failed to create comment.") 1352 1775 return 1353 1776 } ··· 1358 1781 } 1359 1782 1360 1783 func (s *State) IssueComment(w http.ResponseWriter, r *http.Request) { 1784 + ctx, span := s.t.TraceStart(r.Context(), "IssueComment") 1785 + defer span.End() 1786 + 1361 1787 user := s.auth.GetUser(r) 1362 - f, err := s.fullyResolvedRepo(r) 1788 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1363 1789 if err != nil { 1364 1790 log.Println("failed to get repo and knot", err) 1791 + span.RecordError(err) 1792 + span.SetStatus(codes.Error, "failed to resolve repo") 1365 1793 return 1366 1794 } 1367 1795 ··· 1370 1798 if err != nil { 1371 1799 http.Error(w, "bad issue id", http.StatusBadRequest) 1372 1800 log.Println("failed to parse issue id", err) 1801 + span.RecordError(err) 1802 + span.SetStatus(codes.Error, "failed to parse issue id") 1373 1803 return 1374 1804 } 1375 1805 ··· 1378 1808 if err != nil { 1379 1809 http.Error(w, "bad comment id", http.StatusBadRequest) 1380 1810 log.Println("failed to parse issue id", err) 1811 + span.RecordError(err) 1812 + span.SetStatus(codes.Error, "failed to parse comment id") 1381 1813 return 1382 1814 } 1383 1815 1384 - issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt) 1816 + span.SetAttributes( 1817 + attribute.Int("issue_id", issueIdInt), 1818 + attribute.Int("comment_id", commentIdInt), 1819 + ) 1820 + 1821 + issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt) 1385 1822 if err != nil { 1386 1823 log.Println("failed to get issue", err) 1824 + span.RecordError(err) 1825 + span.SetStatus(codes.Error, "failed to get issue") 1387 1826 s.pages.Notice(w, "issues", "Failed to load issue. Try again later.") 1388 1827 return 1389 1828 } ··· 1391 1830 comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt) 1392 1831 if err != nil { 1393 1832 http.Error(w, "bad comment id", http.StatusBadRequest) 1833 + span.RecordError(err) 1834 + span.SetStatus(codes.Error, "failed to get comment") 1394 1835 return 1395 1836 } 1396 1837 1397 - identity, err := s.resolver.ResolveIdent(r.Context(), comment.OwnerDid) 1838 + identity, err := s.resolver.ResolveIdent(ctx, comment.OwnerDid) 1398 1839 if err != nil { 1399 1840 log.Println("failed to resolve did") 1841 + span.RecordError(err) 1842 + span.SetStatus(codes.Error, "failed to resolve did") 1400 1843 return 1401 1844 } 1402 1845 ··· 1409 1852 1410 1853 s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{ 1411 1854 LoggedInUser: user, 1412 - RepoInfo: f.RepoInfo(s, user), 1855 + RepoInfo: f.RepoInfo(ctx, s, user), 1413 1856 DidHandleMap: didHandleMap, 1414 1857 Issue: issue, 1415 1858 Comment: comment, ··· 1417 1860 } 1418 1861 1419 1862 func (s *State) EditIssueComment(w http.ResponseWriter, r *http.Request) { 1863 + ctx, span := s.t.TraceStart(r.Context(), "EditIssueComment") 1864 + defer span.End() 1865 + 1420 1866 user := s.auth.GetUser(r) 1421 - f, err := s.fullyResolvedRepo(r) 1867 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1422 1868 if err != nil { 1423 1869 log.Println("failed to get repo and knot", err) 1870 + span.RecordError(err) 1871 + span.SetStatus(codes.Error, "failed to resolve repo") 1424 1872 return 1425 1873 } 1426 1874 ··· 1429 1877 if err != nil { 1430 1878 http.Error(w, "bad issue id", http.StatusBadRequest) 1431 1879 log.Println("failed to parse issue id", err) 1880 + span.RecordError(err) 1881 + span.SetStatus(codes.Error, "failed to parse issue id") 1432 1882 return 1433 1883 } 1434 1884 ··· 1437 1887 if err != nil { 1438 1888 http.Error(w, "bad comment id", http.StatusBadRequest) 1439 1889 log.Println("failed to parse issue id", err) 1890 + span.RecordError(err) 1891 + span.SetStatus(codes.Error, "failed to parse comment id") 1440 1892 return 1441 1893 } 1442 1894 1443 - issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt) 1895 + span.SetAttributes( 1896 + attribute.Int("issue_id", issueIdInt), 1897 + attribute.Int("comment_id", commentIdInt), 1898 + attribute.String("method", r.Method), 1899 + ) 1900 + 1901 + issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt) 1444 1902 if err != nil { 1445 1903 log.Println("failed to get issue", err) 1904 + span.RecordError(err) 1905 + span.SetStatus(codes.Error, "failed to get issue") 1446 1906 s.pages.Notice(w, "issues", "Failed to load issue. Try again later.") 1447 1907 return 1448 1908 } ··· 1450 1910 comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt) 1451 1911 if err != nil { 1452 1912 http.Error(w, "bad comment id", http.StatusBadRequest) 1913 + span.RecordError(err) 1914 + span.SetStatus(codes.Error, "failed to get comment") 1453 1915 return 1454 1916 } 1455 1917 1456 1918 if comment.OwnerDid != user.Did { 1457 1919 http.Error(w, "you are not the author of this comment", http.StatusUnauthorized) 1920 + span.SetAttributes(attribute.Bool("permission_denied", true)) 1458 1921 return 1459 1922 } 1460 1923 ··· 1462 1925 case http.MethodGet: 1463 1926 s.pages.EditIssueCommentFragment(w, pages.EditIssueCommentParams{ 1464 1927 LoggedInUser: user, 1465 - RepoInfo: f.RepoInfo(s, user), 1928 + RepoInfo: f.RepoInfo(ctx, s, user), 1466 1929 Issue: issue, 1467 1930 Comment: comment, 1468 1931 }) ··· 1472 1935 client, _ := s.auth.AuthorizedClient(r) 1473 1936 rkey := comment.Rkey 1474 1937 1938 + span.SetAttributes( 1939 + attribute.String("new_body", newBody), 1940 + attribute.String("rkey", rkey), 1941 + ) 1942 + 1475 1943 // optimistic update 1476 1944 edited := time.Now() 1477 1945 err = db.EditComment(s.db, comment.RepoAt, comment.Issue, comment.CommentId, newBody) 1478 1946 if err != nil { 1479 1947 log.Println("failed to perferom update-description query", err) 1948 + span.RecordError(err) 1949 + span.SetStatus(codes.Error, "failed to edit comment in database") 1480 1950 s.pages.Notice(w, "repo-notice", "Failed to update description, try again later.") 1481 1951 return 1482 1952 } ··· 1484 1954 // rkey is optional, it was introduced later 1485 1955 if comment.Rkey != "" { 1486 1956 // update the record on pds 1487 - ex, err := comatproto.RepoGetRecord(r.Context(), client, "", tangled.RepoIssueCommentNSID, user.Did, rkey) 1957 + ex, err := comatproto.RepoGetRecord(ctx, client, "", tangled.RepoIssueCommentNSID, user.Did, rkey) 1488 1958 if err != nil { 1489 1959 // failed to get record 1490 1960 log.Println(err, rkey) 1961 + span.RecordError(err) 1962 + span.SetStatus(codes.Error, "failed to get record from PDS") 1491 1963 s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.") 1492 1964 return 1493 1965 } ··· 1499 1971 createdAt := record["createdAt"].(string) 1500 1972 commentIdInt64 := int64(commentIdInt) 1501 1973 1502 - _, err = comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 1974 + _, err = comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 1503 1975 Collection: tangled.RepoIssueCommentNSID, 1504 1976 Repo: user.Did, 1505 1977 Rkey: rkey, ··· 1517 1989 }) 1518 1990 if err != nil { 1519 1991 log.Println(err) 1992 + span.RecordError(err) 1993 + span.SetStatus(codes.Error, "failed to put record to PDS") 1520 1994 } 1521 1995 } 1522 1996 ··· 1530 2004 // return new comment body with htmx 1531 2005 s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{ 1532 2006 LoggedInUser: user, 1533 - RepoInfo: f.RepoInfo(s, user), 2007 + RepoInfo: f.RepoInfo(ctx, s, user), 1534 2008 DidHandleMap: didHandleMap, 1535 2009 Issue: issue, 1536 2010 Comment: comment, 1537 2011 }) 1538 2012 return 1539 - 1540 2013 } 1541 - 1542 2014 } 1543 2015 1544 2016 func (s *State) DeleteIssueComment(w http.ResponseWriter, r *http.Request) { 2017 + ctx, span := s.t.TraceStart(r.Context(), "DeleteIssueComment") 2018 + defer span.End() 2019 + 1545 2020 user := s.auth.GetUser(r) 1546 - f, err := s.fullyResolvedRepo(r) 2021 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1547 2022 if err != nil { 1548 2023 log.Println("failed to get repo and knot", err) 2024 + span.RecordError(err) 2025 + span.SetStatus(codes.Error, "failed to resolve repo") 1549 2026 return 1550 2027 } 1551 2028 ··· 1554 2031 if err != nil { 1555 2032 http.Error(w, "bad issue id", http.StatusBadRequest) 1556 2033 log.Println("failed to parse issue id", err) 2034 + span.RecordError(err) 2035 + span.SetStatus(codes.Error, "failed to parse issue id") 1557 2036 return 1558 2037 } 1559 2038 1560 - issue, err := db.GetIssue(s.db, f.RepoAt, issueIdInt) 2039 + issue, err := db.GetIssue(ctx, s.db, f.RepoAt, issueIdInt) 1561 2040 if err != nil { 1562 2041 log.Println("failed to get issue", err) 2042 + span.RecordError(err) 2043 + span.SetStatus(codes.Error, "failed to get issue") 1563 2044 s.pages.Notice(w, "issues", "Failed to load issue. Try again later.") 1564 2045 return 1565 2046 } ··· 1569 2050 if err != nil { 1570 2051 http.Error(w, "bad comment id", http.StatusBadRequest) 1571 2052 log.Println("failed to parse issue id", err) 2053 + span.RecordError(err) 2054 + span.SetStatus(codes.Error, "failed to parse comment id") 1572 2055 return 1573 2056 } 1574 2057 2058 + span.SetAttributes( 2059 + attribute.Int("issue_id", issueIdInt), 2060 + attribute.Int("comment_id", commentIdInt), 2061 + ) 2062 + 1575 2063 comment, err := db.GetComment(s.db, f.RepoAt, issueIdInt, commentIdInt) 1576 2064 if err != nil { 1577 2065 http.Error(w, "bad comment id", http.StatusBadRequest) 2066 + span.RecordError(err) 2067 + span.SetStatus(codes.Error, "failed to get comment") 1578 2068 return 1579 2069 } 1580 2070 1581 2071 if comment.OwnerDid != user.Did { 1582 2072 http.Error(w, "you are not the author of this comment", http.StatusUnauthorized) 2073 + span.SetAttributes(attribute.Bool("permission_denied", true)) 1583 2074 return 1584 2075 } 1585 2076 1586 2077 if comment.Deleted != nil { 1587 2078 http.Error(w, "comment already deleted", http.StatusBadRequest) 2079 + span.SetAttributes(attribute.Bool("already_deleted", true)) 1588 2080 return 1589 2081 } 1590 2082 ··· 1593 2085 err = db.DeleteComment(s.db, f.RepoAt, issueIdInt, commentIdInt) 1594 2086 if err != nil { 1595 2087 log.Println("failed to delete comment") 2088 + span.RecordError(err) 2089 + span.SetStatus(codes.Error, "failed to delete comment in database") 1596 2090 s.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "failed to delete comment") 1597 2091 return 1598 2092 } ··· 1600 2094 // delete from pds 1601 2095 if comment.Rkey != "" { 1602 2096 client, _ := s.auth.AuthorizedClient(r) 1603 - _, err = comatproto.RepoDeleteRecord(r.Context(), client, &comatproto.RepoDeleteRecord_Input{ 2097 + _, err = comatproto.RepoDeleteRecord(ctx, client, &comatproto.RepoDeleteRecord_Input{ 1604 2098 Collection: tangled.GraphFollowNSID, 1605 2099 Repo: user.Did, 1606 2100 Rkey: comment.Rkey, 1607 2101 }) 1608 2102 if err != nil { 1609 2103 log.Println(err) 2104 + span.RecordError(err) 2105 + span.SetStatus(codes.Error, "failed to delete record from PDS") 1610 2106 } 1611 2107 } 1612 2108 ··· 1620 2116 // htmx fragment of comment after deletion 1621 2117 s.pages.SingleIssueCommentFragment(w, pages.SingleIssueCommentParams{ 1622 2118 LoggedInUser: user, 1623 - RepoInfo: f.RepoInfo(s, user), 2119 + RepoInfo: f.RepoInfo(ctx, s, user), 1624 2120 DidHandleMap: didHandleMap, 1625 2121 Issue: issue, 1626 2122 Comment: comment, ··· 1629 2125 } 1630 2126 1631 2127 func (s *State) RepoIssues(w http.ResponseWriter, r *http.Request) { 2128 + ctx, span := s.t.TraceStart(r.Context(), "RepoIssues") 2129 + defer span.End() 2130 + 1632 2131 params := r.URL.Query() 1633 2132 state := params.Get("state") 1634 2133 isOpen := true ··· 1641 2140 isOpen = true 1642 2141 } 1643 2142 2143 + span.SetAttributes( 2144 + attribute.Bool("is_open", isOpen), 2145 + attribute.String("state_param", state), 2146 + ) 2147 + 1644 2148 page, ok := r.Context().Value("page").(pagination.Page) 1645 2149 if !ok { 1646 2150 log.Println("failed to get page") 2151 + span.SetAttributes(attribute.Bool("page_not_found", true)) 1647 2152 page = pagination.FirstPage() 1648 2153 } 1649 2154 1650 2155 user := s.auth.GetUser(r) 1651 - f, err := s.fullyResolvedRepo(r) 2156 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1652 2157 if err != nil { 1653 2158 log.Println("failed to get repo and knot", err) 2159 + span.RecordError(err) 2160 + span.SetStatus(codes.Error, "failed to resolve repo") 1654 2161 return 1655 2162 } 1656 2163 1657 - issues, err := db.GetIssues(s.db, f.RepoAt, isOpen, page) 2164 + issues, err := db.GetIssues(ctx, s.db, f.RepoAt, isOpen, page) 1658 2165 if err != nil { 1659 2166 log.Println("failed to get issues", err) 2167 + span.RecordError(err) 2168 + span.SetStatus(codes.Error, "failed to get issues") 1660 2169 s.pages.Notice(w, "issues", "Failed to load issues. Try again later.") 1661 2170 return 1662 2171 } 1663 2172 2173 + span.SetAttributes(attribute.Int("issues.count", len(issues))) 2174 + 1664 2175 identsToResolve := make([]string, len(issues)) 1665 2176 for i, issue := range issues { 1666 2177 identsToResolve[i] = issue.OwnerDid 1667 2178 } 1668 - resolvedIds := s.resolver.ResolveIdents(r.Context(), identsToResolve) 2179 + resolvedIds := s.resolver.ResolveIdents(ctx, identsToResolve) 1669 2180 didHandleMap := make(map[string]string) 1670 2181 for _, identity := range resolvedIds { 1671 2182 if !identity.Handle.IsInvalidHandle() { ··· 1677 2188 1678 2189 s.pages.RepoIssues(w, pages.RepoIssuesParams{ 1679 2190 LoggedInUser: s.auth.GetUser(r), 1680 - RepoInfo: f.RepoInfo(s, user), 2191 + RepoInfo: f.RepoInfo(ctx, s, user), 1681 2192 Issues: issues, 1682 2193 DidHandleMap: didHandleMap, 1683 2194 FilteringByOpen: isOpen, ··· 1687 2198 } 1688 2199 1689 2200 func (s *State) NewIssue(w http.ResponseWriter, r *http.Request) { 2201 + ctx, span := s.t.TraceStart(r.Context(), "NewIssue") 2202 + defer span.End() 2203 + 1690 2204 user := s.auth.GetUser(r) 1691 2205 1692 - f, err := s.fullyResolvedRepo(r) 2206 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1693 2207 if err != nil { 1694 2208 log.Println("failed to get repo and knot", err) 2209 + span.RecordError(err) 2210 + span.SetStatus(codes.Error, "failed to resolve repo") 1695 2211 return 1696 2212 } 2213 + 2214 + span.SetAttributes(attribute.String("method", r.Method)) 1697 2215 1698 2216 switch r.Method { 1699 2217 case http.MethodGet: 1700 2218 s.pages.RepoNewIssue(w, pages.RepoNewIssueParams{ 1701 2219 LoggedInUser: user, 1702 - RepoInfo: f.RepoInfo(s, user), 2220 + RepoInfo: f.RepoInfo(ctx, s, user), 1703 2221 }) 1704 2222 case http.MethodPost: 1705 2223 title := r.FormValue("title") 1706 2224 body := r.FormValue("body") 1707 2225 2226 + span.SetAttributes( 2227 + attribute.String("title", title), 2228 + attribute.String("body_length", fmt.Sprintf("%d", len(body))), 2229 + ) 2230 + 1708 2231 if title == "" || body == "" { 2232 + span.SetAttributes(attribute.Bool("form_validation_failed", true)) 1709 2233 s.pages.Notice(w, "issues", "Title and body are required") 1710 2234 return 1711 2235 } 1712 2236 1713 - tx, err := s.db.BeginTx(r.Context(), nil) 2237 + tx, err := s.db.BeginTx(ctx, nil) 1714 2238 if err != nil { 2239 + span.RecordError(err) 2240 + span.SetStatus(codes.Error, "failed to begin transaction") 1715 2241 s.pages.Notice(w, "issues", "Failed to create issue, try again later") 1716 2242 return 1717 2243 } ··· 1724 2250 }) 1725 2251 if err != nil { 1726 2252 log.Println("failed to create issue", err) 2253 + span.RecordError(err) 2254 + span.SetStatus(codes.Error, "failed to create issue in database") 1727 2255 s.pages.Notice(w, "issues", "Failed to create issue.") 1728 2256 return 1729 2257 } ··· 1731 2259 issueId, err := db.GetIssueId(s.db, f.RepoAt) 1732 2260 if err != nil { 1733 2261 log.Println("failed to get issue id", err) 2262 + span.RecordError(err) 2263 + span.SetStatus(codes.Error, "failed to get issue id") 1734 2264 s.pages.Notice(w, "issues", "Failed to create issue.") 1735 2265 return 1736 2266 } 1737 2267 2268 + span.SetAttributes(attribute.Int("issue_id", issueId)) 2269 + 1738 2270 client, _ := s.auth.AuthorizedClient(r) 1739 2271 atUri := f.RepoAt.String() 1740 - resp, err := comatproto.RepoPutRecord(r.Context(), client, &comatproto.RepoPutRecord_Input{ 2272 + rkey := appview.TID() 2273 + span.SetAttributes(attribute.String("rkey", rkey)) 2274 + 2275 + resp, err := comatproto.RepoPutRecord(ctx, client, &comatproto.RepoPutRecord_Input{ 1741 2276 Collection: tangled.RepoIssueNSID, 1742 2277 Repo: user.Did, 1743 - Rkey: appview.TID(), 2278 + Rkey: rkey, 1744 2279 Record: &lexutil.LexiconTypeDecoder{ 1745 2280 Val: &tangled.RepoIssue{ 1746 2281 Repo: atUri, ··· 1753 2288 }) 1754 2289 if err != nil { 1755 2290 log.Println("failed to create issue", err) 2291 + span.RecordError(err) 2292 + span.SetStatus(codes.Error, "failed to create issue in PDS") 1756 2293 s.pages.Notice(w, "issues", "Failed to create issue.") 1757 2294 return 1758 2295 } 1759 2296 2297 + span.SetAttributes(attribute.String("issue_uri", resp.Uri)) 2298 + 1760 2299 err = db.SetIssueAt(s.db, f.RepoAt, issueId, resp.Uri) 1761 2300 if err != nil { 1762 2301 log.Println("failed to set issue at", err) 2302 + span.RecordError(err) 2303 + span.SetStatus(codes.Error, "failed to set issue URI in database") 1763 2304 s.pages.Notice(w, "issues", "Failed to create issue.") 1764 2305 return 1765 2306 } ··· 1770 2311 } 1771 2312 1772 2313 func (s *State) ForkRepo(w http.ResponseWriter, r *http.Request) { 2314 + ctx, span := s.t.TraceStart(r.Context(), "ForkRepo") 2315 + defer span.End() 2316 + 1773 2317 user := s.auth.GetUser(r) 1774 - f, err := s.fullyResolvedRepo(r) 2318 + f, err := s.fullyResolvedRepo(r.WithContext(ctx)) 1775 2319 if err != nil { 1776 2320 log.Printf("failed to resolve source repo: %v", err) 2321 + span.RecordError(err) 2322 + span.SetStatus(codes.Error, "failed to resolve source repo") 1777 2323 return 1778 2324 } 1779 2325 2326 + span.SetAttributes( 2327 + attribute.String("method", r.Method), 2328 + attribute.String("repo_name", f.RepoName), 2329 + attribute.String("owner_did", f.OwnerDid()), 2330 + attribute.String("knot", f.Knot), 2331 + ) 2332 + 1780 2333 switch r.Method { 1781 2334 case http.MethodGet: 1782 2335 user := s.auth.GetUser(r) 1783 2336 knots, err := s.enforcer.GetDomainsForUser(user.Did) 1784 2337 if err != nil { 2338 + span.RecordError(err) 2339 + span.SetStatus(codes.Error, "failed to get domains for user") 1785 2340 s.pages.Notice(w, "repo", "Invalid user account.") 1786 2341 return 1787 2342 } 1788 2343 2344 + span.SetAttributes(attribute.Int("knots.count", len(knots))) 2345 + 1789 2346 s.pages.ForkRepo(w, pages.ForkRepoParams{ 1790 2347 LoggedInUser: user, 1791 2348 Knots: knots, 1792 - RepoInfo: f.RepoInfo(s, user), 2349 + RepoInfo: f.RepoInfo(ctx, s, user), 1793 2350 }) 1794 2351 1795 2352 case http.MethodPost: 1796 - 1797 2353 knot := r.FormValue("knot") 1798 2354 if knot == "" { 2355 + span.SetAttributes(attribute.Bool("missing_knot", true)) 1799 2356 s.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.") 1800 2357 return 1801 2358 } 1802 2359 2360 + span.SetAttributes(attribute.String("target_knot", knot)) 2361 + 1803 2362 ok, err := s.enforcer.E.Enforce(user.Did, knot, knot, "repo:create") 1804 2363 if err != nil || !ok { 2364 + span.SetAttributes( 2365 + attribute.Bool("permission_denied", true), 2366 + attribute.Bool("enforce_error", err != nil), 2367 + ) 1805 2368 s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.") 1806 2369 return 1807 2370 } 1808 2371 1809 2372 forkName := fmt.Sprintf("%s", f.RepoName) 2373 + span.SetAttributes(attribute.String("fork_name", forkName)) 1810 2374 1811 2375 // this check is *only* to see if the forked repo name already exists 1812 2376 // in the user's account. 1813 - existingRepo, err := db.GetRepo(s.db, user.Did, f.RepoName) 2377 + existingRepo, err := db.GetRepo(ctx, s.db, user.Did, f.RepoName) 1814 2378 if err != nil { 1815 2379 if errors.Is(err, sql.ErrNoRows) { 1816 2380 // no existing repo with this name found, we can use the name as is 2381 + span.SetAttributes(attribute.Bool("repo_name_available", true)) 1817 2382 } else { 1818 2383 log.Println("error fetching existing repo from db", err) 2384 + span.RecordError(err) 2385 + span.SetStatus(codes.Error, "failed to check for existing repo") 1819 2386 s.pages.Notice(w, "repo", "Failed to fork this repository. Try again later.") 1820 2387 return 1821 2388 } 1822 2389 } else if existingRepo != nil { 1823 2390 // repo with this name already exists, append random string 1824 2391 forkName = fmt.Sprintf("%s-%s", forkName, randomString(3)) 2392 + span.SetAttributes( 2393 + attribute.Bool("repo_name_conflict", true), 2394 + attribute.String("adjusted_fork_name", forkName), 2395 + ) 1825 2396 } 2397 + 1826 2398 secret, err := db.GetRegistrationKey(s.db, knot) 1827 2399 if err != nil { 2400 + span.RecordError(err) 2401 + span.SetStatus(codes.Error, "failed to get registration key") 1828 2402 s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", knot)) 1829 2403 return 1830 2404 } 1831 2405 1832 2406 client, err := NewSignedClient(knot, secret, s.config.Dev) 1833 2407 if err != nil { 2408 + span.RecordError(err) 2409 + span.SetStatus(codes.Error, "failed to create signed client") 1834 2410 s.pages.Notice(w, "repo", "Failed to reach knot server.") 1835 2411 return 1836 2412 } ··· 1844 2420 forkSourceUrl := fmt.Sprintf("%s://%s/%s/%s", uri, f.Knot, f.OwnerDid(), f.RepoName) 1845 2421 sourceAt := f.RepoAt.String() 1846 2422 2423 + span.SetAttributes( 2424 + attribute.String("fork_source_url", forkSourceUrl), 2425 + attribute.String("source_at", sourceAt), 2426 + ) 2427 + 1847 2428 rkey := appview.TID() 1848 2429 repo := &db.Repo{ 1849 2430 Did: user.Did, ··· 1853 2434 Source: sourceAt, 1854 2435 } 1855 2436 1856 - tx, err := s.db.BeginTx(r.Context(), nil) 2437 + span.SetAttributes(attribute.String("rkey", rkey)) 2438 + 2439 + tx, err := s.db.BeginTx(ctx, nil) 1857 2440 if err != nil { 1858 2441 log.Println(err) 2442 + span.RecordError(err) 2443 + span.SetStatus(codes.Error, "failed to begin transaction") 1859 2444 s.pages.Notice(w, "repo", "Failed to save repository information.") 1860 2445 return 1861 2446 } ··· 1864 2449 err = s.enforcer.E.LoadPolicy() 1865 2450 if err != nil { 1866 2451 log.Println("failed to rollback policies") 2452 + span.RecordError(err) 1867 2453 } 1868 2454 }() 1869 2455 1870 2456 resp, err := client.ForkRepo(user.Did, forkSourceUrl, forkName) 1871 2457 if err != nil { 2458 + span.RecordError(err) 2459 + span.SetStatus(codes.Error, "failed to fork repo on knot server") 1872 2460 s.pages.Notice(w, "repo", "Failed to create repository on knot server.") 1873 2461 return 1874 2462 } 2463 + 2464 + span.SetAttributes(attribute.Int("fork_response_status", resp.StatusCode)) 1875 2465 1876 2466 switch resp.StatusCode { 1877 2467 case http.StatusConflict: 2468 + span.SetAttributes(attribute.Bool("name_conflict", true)) 1878 2469 s.pages.Notice(w, "repo", "A repository with that name already exists.") 1879 2470 return 1880 2471 case http.StatusInternalServerError: 2472 + span.SetAttributes(attribute.Bool("server_error", true)) 1881 2473 s.pages.Notice(w, "repo", "Failed to create repository on knot. Try again later.") 2474 + return 1882 2475 case http.StatusNoContent: 1883 2476 // continue 1884 2477 } ··· 1886 2479 xrpcClient, _ := s.auth.AuthorizedClient(r) 1887 2480 1888 2481 createdAt := time.Now().Format(time.RFC3339) 1889 - atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{ 2482 + atresp, err := comatproto.RepoPutRecord(ctx, xrpcClient, &comatproto.RepoPutRecord_Input{ 1890 2483 Collection: tangled.RepoNSID, 1891 2484 Repo: user.Did, 1892 2485 Rkey: rkey, ··· 1901 2494 }) 1902 2495 if err != nil { 1903 2496 log.Printf("failed to create record: %s", err) 2497 + span.RecordError(err) 2498 + span.SetStatus(codes.Error, "failed to create record in PDS") 1904 2499 s.pages.Notice(w, "repo", "Failed to announce repository creation.") 1905 2500 return 1906 2501 } 1907 2502 log.Println("created repo record: ", atresp.Uri) 2503 + span.SetAttributes(attribute.String("repo_uri", atresp.Uri)) 1908 2504 1909 2505 repo.AtUri = atresp.Uri 1910 - err = db.AddRepo(tx, repo) 2506 + err = db.AddRepo(ctx, tx, repo) 1911 2507 if err != nil { 1912 2508 log.Println(err) 2509 + span.RecordError(err) 2510 + span.SetStatus(codes.Error, "failed to add repo to database") 1913 2511 s.pages.Notice(w, "repo", "Failed to save repository information.") 1914 2512 return 1915 2513 } ··· 1919 2517 err = s.enforcer.AddRepo(user.Did, knot, p) 1920 2518 if err != nil { 1921 2519 log.Println(err) 2520 + span.RecordError(err) 2521 + span.SetStatus(codes.Error, "failed to set up repository permissions") 1922 2522 s.pages.Notice(w, "repo", "Failed to set up repository permissions.") 1923 2523 return 1924 2524 } ··· 1926 2526 err = tx.Commit() 1927 2527 if err != nil { 1928 2528 log.Println("failed to commit changes", err) 2529 + span.RecordError(err) 2530 + span.SetStatus(codes.Error, "failed to commit transaction") 1929 2531 http.Error(w, err.Error(), http.StatusInternalServerError) 1930 2532 return 1931 2533 } ··· 1933 2535 err = s.enforcer.E.SavePolicy() 1934 2536 if err != nil { 1935 2537 log.Println("failed to update ACLs", err) 2538 + span.RecordError(err) 2539 + span.SetStatus(codes.Error, "failed to save policy") 1936 2540 http.Error(w, err.Error(), http.StatusInternalServerError) 1937 2541 return 1938 2542 }
+24 -6
appview/state/repo_util.go
··· 12 12 "github.com/bluesky-social/indigo/atproto/syntax" 13 13 "github.com/go-chi/chi/v5" 14 14 "github.com/go-git/go-git/v5/plumbing/object" 15 + "go.opentelemetry.io/otel/attribute" 15 16 "tangled.sh/tangled.sh/core/appview/auth" 16 17 "tangled.sh/tangled.sh/core/appview/db" 17 18 "tangled.sh/tangled.sh/core/appview/pages/repoinfo" 19 + "tangled.sh/tangled.sh/core/telemetry" 18 20 ) 19 21 20 22 func (s *State) fullyResolvedRepo(r *http.Request) (*FullyResolvedRepo, error) { 23 + ctx := r.Context() 24 + 25 + attrs := telemetry.MapAttrs(map[string]string{ 26 + "repo": chi.URLParam(r, "repo"), 27 + "ref": chi.URLParam(r, "ref"), 28 + }) 29 + 30 + ctx, span := s.t.TraceStart(ctx, "fullyResolvedRepo", attrs...) 31 + defer span.End() 32 + 21 33 repoName := chi.URLParam(r, "repo") 22 - knot, ok := r.Context().Value("knot").(string) 34 + knot, ok := ctx.Value("knot").(string) 23 35 if !ok { 24 36 log.Println("malformed middleware") 25 37 return nil, fmt.Errorf("malformed middleware") 26 38 } 27 - id, ok := r.Context().Value("resolvedId").(identity.Identity) 39 + 40 + span.SetAttributes(attribute.String("knot", knot)) 41 + 42 + id, ok := ctx.Value("resolvedId").(identity.Identity) 28 43 if !ok { 29 44 log.Println("malformed middleware") 30 45 return nil, fmt.Errorf("malformed middleware") 31 46 } 32 47 33 - repoAt, ok := r.Context().Value("repoAt").(string) 48 + span.SetAttributes(attribute.String("did", id.DID.String())) 49 + 50 + repoAt, ok := ctx.Value("repoAt").(string) 34 51 if !ok { 35 52 log.Println("malformed middleware") 36 53 return nil, fmt.Errorf("malformed middleware") ··· 56 73 } 57 74 58 75 ref = defaultBranch.Branch 76 + 77 + span.SetAttributes(attribute.String("default_branch", ref)) 59 78 } 60 79 61 - // pass through values from the middleware 62 - description, ok := r.Context().Value("repoDescription").(string) 63 - addedAt, ok := r.Context().Value("repoAddedAt").(string) 80 + description, ok := ctx.Value("repoDescription").(string) 81 + addedAt, ok := ctx.Value("repoAddedAt").(string) 64 82 65 83 return &FullyResolvedRepo{ 66 84 Knot: knot,
+48 -8
appview/state/state.go
··· 18 18 lexutil "github.com/bluesky-social/indigo/lex/util" 19 19 securejoin "github.com/cyphar/filepath-securejoin" 20 20 "github.com/go-chi/chi/v5" 21 + "go.opentelemetry.io/otel/attribute" 21 22 "tangled.sh/tangled.sh/core/api/tangled" 22 23 "tangled.sh/tangled.sh/core/appview" 23 24 "tangled.sh/tangled.sh/core/appview/auth" ··· 83 84 if err != nil { 84 85 return nil, fmt.Errorf("failed to create jetstream client: %w", err) 85 86 } 86 - err = jc.StartJetstream(context.Background(), appview.Ingest(wrapper)) 87 + err = jc.StartJetstream(ctx, appview.Ingest(wrapper)) 87 88 if err != nil { 88 89 return nil, fmt.Errorf("failed to start jetstream watcher: %w", err) 89 90 } ··· 198 199 } 199 200 200 201 func (s *State) Timeline(w http.ResponseWriter, r *http.Request) { 202 + ctx, span := s.t.TraceStart(r.Context(), "Timeline") 203 + defer span.End() 204 + 201 205 user := s.auth.GetUser(r) 206 + span.SetAttributes(attribute.String("user.did", user.Did)) 202 207 203 - timeline, err := db.MakeTimeline(s.db) 208 + timeline, err := db.MakeTimeline(ctx, s.db) 204 209 if err != nil { 205 210 log.Println(err) 211 + span.RecordError(err) 206 212 s.pages.Notice(w, "timeline", "Uh oh! Failed to load timeline.") 207 213 } 208 214 ··· 221 227 didsToResolve = append(didsToResolve, ev.Star.StarredByDid, ev.Star.Repo.Did) 222 228 } 223 229 } 230 + span.SetAttributes(attribute.Int("dids.to_resolve.count", len(didsToResolve))) 224 231 225 - resolvedIds := s.resolver.ResolveIdents(r.Context(), didsToResolve) 232 + resolvedIds := s.resolver.ResolveIdents(ctx, didsToResolve) 226 233 didHandleMap := make(map[string]string) 227 234 for _, identity := range resolvedIds { 228 235 if !identity.Handle.IsInvalidHandle() { ··· 231 238 didHandleMap[identity.DID.String()] = identity.DID.String() 232 239 } 233 240 } 241 + span.SetAttributes(attribute.Int("dids.resolved.count", len(resolvedIds))) 234 242 235 243 s.pages.Timeline(w, pages.TimelineParams{ 236 244 LoggedInUser: user, ··· 594 602 } 595 603 596 604 func (s *State) NewRepo(w http.ResponseWriter, r *http.Request) { 605 + ctx, span := s.t.TraceStart(r.Context(), "NewRepo") 606 + defer span.End() 607 + 597 608 switch r.Method { 598 609 case http.MethodGet: 599 610 user := s.auth.GetUser(r) 611 + span.SetAttributes(attribute.String("user.did", user.Did)) 612 + span.SetAttributes(attribute.String("request.method", "GET")) 613 + 600 614 knots, err := s.enforcer.GetDomainsForUser(user.Did) 601 615 if err != nil { 616 + span.RecordError(err) 602 617 s.pages.Notice(w, "repo", "Invalid user account.") 603 618 return 604 619 } 620 + span.SetAttributes(attribute.Int("knots.count", len(knots))) 605 621 606 622 s.pages.NewRepo(w, pages.NewRepoParams{ 607 623 LoggedInUser: user, ··· 610 626 611 627 case http.MethodPost: 612 628 user := s.auth.GetUser(r) 629 + span.SetAttributes(attribute.String("user.did", user.Did)) 630 + span.SetAttributes(attribute.String("request.method", "POST")) 613 631 614 632 domain := r.FormValue("domain") 615 633 if domain == "" { 616 634 s.pages.Notice(w, "repo", "Invalid form submission—missing knot domain.") 617 635 return 618 636 } 637 + span.SetAttributes(attribute.String("domain", domain)) 619 638 620 639 repoName := r.FormValue("name") 621 640 if repoName == "" { 622 641 s.pages.Notice(w, "repo", "Repository name cannot be empty.") 623 642 return 624 643 } 644 + span.SetAttributes(attribute.String("repo.name", repoName)) 625 645 626 646 // Check for valid repository name (GitHub-like rules) 627 647 // No spaces, only alphanumeric characters, dashes, and underscores ··· 639 659 if defaultBranch == "" { 640 660 defaultBranch = "main" 641 661 } 662 + span.SetAttributes(attribute.String("repo.default_branch", defaultBranch)) 642 663 643 664 description := r.FormValue("description") 644 665 645 666 ok, err := s.enforcer.E.Enforce(user.Did, domain, domain, "repo:create") 646 667 if err != nil || !ok { 668 + if err != nil { 669 + span.RecordError(err) 670 + } 671 + span.SetAttributes(attribute.Bool("permission.granted", false)) 647 672 s.pages.Notice(w, "repo", "You do not have permission to create a repo in this knot.") 648 673 return 649 674 } 675 + span.SetAttributes(attribute.Bool("permission.granted", true)) 650 676 651 - existingRepo, err := db.GetRepo(s.db, user.Did, repoName) 677 + existingRepo, err := db.GetRepo(ctx, s.db, user.Did, repoName) 652 678 if err == nil && existingRepo != nil { 679 + span.SetAttributes(attribute.Bool("repo.exists", true)) 653 680 s.pages.Notice(w, "repo", fmt.Sprintf("A repo by this name already exists on %s", existingRepo.Knot)) 654 681 return 655 682 } 683 + span.SetAttributes(attribute.Bool("repo.exists", false)) 656 684 657 685 secret, err := db.GetRegistrationKey(s.db, domain) 658 686 if err != nil { 687 + span.RecordError(err) 659 688 s.pages.Notice(w, "repo", fmt.Sprintf("No registration key found for knot %s.", domain)) 660 689 return 661 690 } 662 691 663 692 client, err := NewSignedClient(domain, secret, s.config.Dev) 664 693 if err != nil { 694 + span.RecordError(err) 665 695 s.pages.Notice(w, "repo", "Failed to connect to knot server.") 666 696 return 667 697 } ··· 675 705 Description: description, 676 706 } 677 707 678 - xrpcClient, _ := s.auth.AuthorizedClient(r) 708 + rWithCtx := r.WithContext(ctx) 709 + xrpcClient, _ := s.auth.AuthorizedClient(rWithCtx) 679 710 680 711 createdAt := time.Now().Format(time.RFC3339) 681 - atresp, err := comatproto.RepoPutRecord(r.Context(), xrpcClient, &comatproto.RepoPutRecord_Input{ 712 + atresp, err := comatproto.RepoPutRecord(ctx, xrpcClient, &comatproto.RepoPutRecord_Input{ 682 713 Collection: tangled.RepoNSID, 683 714 Repo: user.Did, 684 715 Rkey: rkey, ··· 691 722 }}, 692 723 }) 693 724 if err != nil { 725 + span.RecordError(err) 694 726 log.Printf("failed to create record: %s", err) 695 727 s.pages.Notice(w, "repo", "Failed to announce repository creation.") 696 728 return 697 729 } 698 730 log.Println("created repo record: ", atresp.Uri) 731 + span.SetAttributes(attribute.String("repo.uri", atresp.Uri)) 699 732 700 - tx, err := s.db.BeginTx(r.Context(), nil) 733 + tx, err := s.db.BeginTx(ctx, nil) 701 734 if err != nil { 735 + span.RecordError(err) 702 736 log.Println(err) 703 737 s.pages.Notice(w, "repo", "Failed to save repository information.") 704 738 return ··· 713 747 714 748 resp, err := client.NewRepo(user.Did, repoName, defaultBranch) 715 749 if err != nil { 750 + span.RecordError(err) 716 751 s.pages.Notice(w, "repo", "Failed to create repository on knot server.") 717 752 return 718 753 } 754 + span.SetAttributes(attribute.Int("knot_response.status", resp.StatusCode)) 719 755 720 756 switch resp.StatusCode { 721 757 case http.StatusConflict: ··· 728 764 } 729 765 730 766 repo.AtUri = atresp.Uri 731 - err = db.AddRepo(tx, repo) 767 + err = db.AddRepo(ctx, tx, repo) 732 768 if err != nil { 769 + span.RecordError(err) 733 770 log.Println(err) 734 771 s.pages.Notice(w, "repo", "Failed to save repository information.") 735 772 return ··· 739 776 p, _ := securejoin.SecureJoin(user.Did, repoName) 740 777 err = s.enforcer.AddRepo(user.Did, domain, p) 741 778 if err != nil { 779 + span.RecordError(err) 742 780 log.Println(err) 743 781 s.pages.Notice(w, "repo", "Failed to set up repository permissions.") 744 782 return ··· 746 784 747 785 err = tx.Commit() 748 786 if err != nil { 787 + span.RecordError(err) 749 788 log.Println("failed to commit changes", err) 750 789 http.Error(w, err.Error(), http.StatusInternalServerError) 751 790 return ··· 753 792 754 793 err = s.enforcer.E.SavePolicy() 755 794 if err != nil { 795 + span.RecordError(err) 756 796 log.Println("failed to update ACLs", err) 757 797 http.Error(w, err.Error(), http.StatusInternalServerError) 758 798 return
+14 -4
telemetry/telemetry.go
··· 2 2 3 3 import ( 4 4 "context" 5 + "fmt" 5 6 6 - "go.opentelemetry.io/otel" 7 + "go.opentelemetry.io/otel/attribute" 7 8 otelmetric "go.opentelemetry.io/otel/metric" 8 9 "go.opentelemetry.io/otel/sdk/metric" 9 10 "go.opentelemetry.io/otel/sdk/resource" ··· 60 61 return t.tracer 61 62 } 62 63 63 - func (t *Telemetry) TraceStart(ctx context.Context, name string) (context.Context, oteltrace.Span) { 64 - tracer := otel.Tracer(t.serviceName) 65 - return tracer.Start(ctx, name) 64 + func (t *Telemetry) TraceStart(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, oteltrace.Span) { 65 + ctx, span := t.tracer.Start(ctx, name) 66 + span.SetAttributes(attrs...) 67 + return ctx, span 68 + } 69 + 70 + func MapAttrs[T any](attrs map[string]T) []attribute.KeyValue { 71 + var result []attribute.KeyValue 72 + for k, v := range attrs { 73 + result = append(result, attribute.Key(k).String(fmt.Sprintf("%v", v))) 74 + } 75 + return result 66 76 }