Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).

appview: instrument all the things

anirudh.fi df653304 5c8b5c1f

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