pico
created pr with
35.1
added 35.2
1: 77aaa29 ! 1: 8d56535 reactor(metric-drain): use caddy json format
-: ------- > 2: a336041 wip
-: ------- > 3: 7ae45b3 chore: wrap
-: ------- > 4: bfa5c4f done
added 35.3
1: 8d56535 < -: ------- reactor(metric-drain): use caddy json format
-: ------- > 1: c7eeb12 reactor(metric-drain): use caddy access logs
2: a336041 < -: ------- wip
3: 7ae45b3 < -: ------- chore: wrap
4: bfa5c4f < -: ------- done
added 35.4
1: c7eeb12 ! 1: 4e0839a reactor(metric-drain): use caddy access logs
changed status to
accepted
cmds
checkout latest patchset:
ssh pr.pico.sh print 35 | git am -3checkout any patchset in a patch request:
ssh pr.pico.sh print 35.[rev] | git am -3add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 35set PR to open (enables RSS notifications):
ssh pr.pico.sh pr open 35set PR to draft (stops RSS notifications):
ssh pr.pico.sh pr draft 35
Patchset
35.2
reactor(metric-drain): use caddy json format
Eric Bower
2024-11-15T15:02:24ZSemantic diff summary
5 added,
5 modified,
0 signature changed,
2 removed
across 4 analyzed files
(1 file skipped: unsupported file type)
+106
-26
auth/api.go
#
| ... | ... | @@ -578,6 +579,89 @@ func checkoutHandler() http.HandlerFunc { | |
| 578 | 579 | } | |
| 579 | 580 | } | |
| 580 | 581 | ||
| 582 | + | type AccessLogReq struct { | |
| 583 | + | RemoteIP string `json:"remote_ip"` | |
| 584 | + | RemotePort string `json:"remote_port"` | |
| 585 | + | ClientIP string `json:"client_ip"` | |
| 586 | + | Method string `json:"method"` | |
| 587 | + | Host string `json:"host"` | |
| 588 | + | Uri string `json:"uri"` | |
| 589 | + | Headers struct { | |
| 590 | + | UserAgent string `json:"User-Agent"` | |
| 591 | + | Referer string `json:"Referer"` | |
| 592 | + | } `json:"headers"` | |
| 593 | + | Tls struct { | |
| 594 | + | ServerName string `json:"server_name"` | |
| 595 | + | } `json:"tls"` | |
| 596 | + | } | |
| 597 | + | ||
| 598 | + | type CaddyAccessLog struct { | |
| 599 | + | Request AccessLogReq `json:"request"` | |
| 600 | + | Status int `json:"status"` | |
| 601 | + | } | |
| 602 | + | ||
| 603 | + | func deserializeCaddyAccessLog(dbpool db.DB, access *CaddyAccessLog) (*db.AnalyticsVisits, error) { | |
| 604 | + | spaceRaw := strings.SplitN(access.Request.Tls.ServerName, ".", 2) | |
| 605 | + | space := spaceRaw[0] | |
| 606 | + | host := access.Request.Host | |
| 607 | + | path := access.Request.Uri | |
| 608 | + | subdomain := "" | |
| 609 | + | ||
| 610 | + | // grab subdomain based on host | |
| 611 | + | if strings.HasSuffix(host, "tuns.sh") { | |
| 612 | + | subdomain = strings.TrimSuffix(host, ".tuns.sh") | |
| 613 | + | } else if strings.HasSuffix(host, "pgs.sh") { | |
| 614 | + | subdomain = strings.TrimSuffix(host, ".pgs.sh") | |
| 615 | + | } else if strings.HasSuffix(host, "prose.sh") { | |
| 616 | + | subdomain = strings.TrimSuffix(host, ".prose.sh") | |
| 617 | + | } else { | |
| 618 | + | subdomain = shared.GetCustomDomain(host, space) | |
| 619 | + | } | |
| 620 | + | ||
| 621 | + | // get user and namespace details from subdomain | |
| 622 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 623 | + | if err != nil { | |
| 624 | + | return nil, err | |
| 625 | + | } | |
| 626 | + | // get user ID | |
| 627 | + | user, err := dbpool.FindUserForName(props.Username) | |
| 628 | + | if err != nil { | |
| 629 | + | return nil, err | |
| 630 | + | } | |
| 631 | + | ||
| 632 | + | projectID := "" | |
| 633 | + | postID := "" | |
| 634 | + | if space == "pgs" { // figure out project ID | |
| 635 | + | project, err := dbpool.FindProjectByName(user.ID, props.ProjectName) | |
| 636 | + | if err != nil { | |
| 637 | + | return nil, err | |
| 638 | + | } | |
| 639 | + | projectID = project.ID | |
| 640 | + | } else if space == "prose" { // figure out post ID | |
| 641 | + | if path == "" || path == "/" { | |
| 642 | + | } else { | |
| 643 | + | post, err := dbpool.FindPostWithSlug(path, user.ID, space) | |
| 644 | + | if err != nil { | |
| 645 | + | return nil, err | |
| 646 | + | } | |
| 647 | + | postID = post.ID | |
| 648 | + | } | |
| 649 | + | } | |
| 650 | + | ||
| 651 | + | return &db.AnalyticsVisits{ | |
| 652 | + | UserID: user.ID, | |
| 653 | + | ProjectID: projectID, | |
| 654 | + | PostID: postID, | |
| 655 | + | Namespace: space, | |
| 656 | + | Host: host, | |
| 657 | + | Path: path, | |
| 658 | + | IpAddress: access.Request.ClientIP, | |
| 659 | + | UserAgent: access.Request.Headers.UserAgent, | |
| 660 | + | Referer: access.Request.Headers.Referer, // TODO: I don't see referer in the access log | |
| 661 | + | Status: access.Status, | |
| 662 | + | }, nil | |
| 663 | + | } | |
| 664 | + | ||
| 581 | 665 | func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secret string) { | |
| 582 | 666 | drain := metrics.ReconnectReadMetrics( | |
| 583 | 667 | ctx, |
| ... | ... | @@ -587,36 +671,32 @@ func metricDrainSub(ctx context.Context, dbpool db.DB, logger *slog.Logger, secr | |
| 587 | 671 | -1, | |
| 588 | 672 | ) | |
| 589 | 673 | ||
| 590 | - | for { | |
| 591 | - | scanner := bufio.NewScanner(drain) | |
| 592 | - | for scanner.Scan() { | |
| 593 | - | line := scanner.Text() | |
| 594 | - | visit := db.AnalyticsVisits{} | |
| 595 | - | err := json.Unmarshal([]byte(line), &visit) | |
| 596 | - | if err != nil { | |
| 597 | - | logger.Error("json unmarshal", "err", err) | |
| 598 | - | continue | |
| 599 | - | } | |
| 600 | - | ||
| 601 | - | user := slog.Any("userId", visit.UserID) | |
| 602 | - | ||
| 603 | - | err = shared.AnalyticsVisitFromVisit(&visit, dbpool, secret) | |
| 604 | - | if err != nil { | |
| 605 | - | if !errors.Is(err, shared.ErrAnalyticsDisabled) { | |
| 606 | - | logger.Info("could not record analytics visit", "reason", err, "visit", visit, user) | |
| 607 | - | continue | |
| 608 | - | } | |
| 609 | - | } | |
| 674 | + | scanner := bufio.NewScanner(drain) | |
| 675 | + | for scanner.Scan() { | |
| 676 | + | line := scanner.Text() | |
| 677 | + | accessLog := CaddyAccessLog{} | |
| 678 | + | err := json.Unmarshal([]byte(line), &accessLog) | |
| 679 | + | if err != nil { | |
| 680 | + | logger.Error("json unmarshal", "err", err) | |
| 681 | + | continue | |
| 682 | + | } | |
| 610 | 683 | ||
| 611 | - | logger.Info("inserting visit", "visit", visit, user) | |
| 612 | - | err = dbpool.InsertVisit(&visit) | |
| 613 | - | if err != nil { | |
| 614 | - | logger.Error("could not insert visit record", "err", err, "visit", visit, user) | |
| 684 | + | visit, err := deserializeCaddyAccessLog(dbpool, &accessLog) | |
| 685 | + | if err != nil { | |
| 686 | + | logger.Error("cannot deserialize access log", "err", err) | |
| 687 | + | continue | |
| 688 | + | } | |
| 689 | + | err = shared.AnalyticsVisitFromVisit(visit, dbpool, secret) | |
| 690 | + | if err != nil { | |
| 691 | + | if !errors.Is(err, shared.ErrAnalyticsDisabled) { | |
| 692 | + | logger.Info("could not record analytics visit", "reason", err) | |
| 615 | 693 | } | |
| 616 | 694 | } | |
| 617 | 695 | ||
| 618 | - | if scanner.Err() != nil { | |
| 619 | - | logger.Error("scanner error", "err", scanner.Err()) | |
| 696 | + | logger.Info("inserting visit", "visit", visit) | |
| 697 | + | err = dbpool.InsertVisit(visit) | |
| 698 | + | if err != nil { | |
| 699 | + | logger.Error("could not insert visit record", "err", err) | |
| 620 | 700 | } | |
| 621 | 701 | } | |
| 622 | 702 | } |
+40
-0
caddy.json
#
| ... | ... | @@ -0,0 +1,40 @@ | |
| 1 | + | { | |
| 2 | + | "level": "info", | |
| 3 | + | "ts": 1731644477.313701, | |
| 4 | + | "logger": "http.log.access", | |
| 5 | + | "msg": "handled request", | |
| 6 | + | "request": { | |
| 7 | + | "remote_ip": "127.0.0.1", | |
| 8 | + | "remote_port": "40400", | |
| 9 | + | "client_ip": "127.0.0.1", | |
| 10 | + | "proto": "HTTP/2.0", | |
| 11 | + | "method": "GET", | |
| 12 | + | "host": "pgs.sh", | |
| 13 | + | "uri": "/", | |
| 14 | + | "headers": { "User-Agent": ["Blackbox Exporter/0.24.0"] }, | |
| 15 | + | "tls": { | |
| 16 | + | "resumed": false, | |
| 17 | + | "version": 772, | |
| 18 | + | "cipher_suite": 4865, | |
| 19 | + | "proto": "h2", | |
| 20 | + | "server_name": "pgs.sh" | |
| 21 | + | } | |
| 22 | + | }, | |
| 23 | + | "bytes_read": 0, | |
| 24 | + | "user_id": "", | |
| 25 | + | "duration": 0.001207084, | |
| 26 | + | "size": 3718, | |
| 27 | + | "status": 200, | |
| 28 | + | "resp_headers": { | |
| 29 | + | "Referrer-Policy": ["no-referrer-when-downgrade"], | |
| 30 | + | "Strict-Transport-Security": ["max-age=31536000;"], | |
| 31 | + | "X-Content-Type-Options": ["nosniff"], | |
| 32 | + | "X-Frame-Options": ["DENY"], | |
| 33 | + | "Server": ["Caddy"], | |
| 34 | + | "Alt-Svc": ["h3=\":443\"; ma=2592000"], | |
| 35 | + | "Date": ["Fri, 15 Nov 2024 04:21:17 GMT"], | |
| 36 | + | "Content-Type": ["text/html; charset=utf-8"], | |
| 37 | + | "X-Xss-Protection": ["1; mode=block"], | |
| 38 | + | "Permissions-Policy": ["interest-cohort=()"] | |
| 39 | + | } | |
| 40 | + | } |
+1
-1
pgs/tunnel.go
#
| ... | ... | @@ -51,7 +51,7 @@ func createHttpHandler(apiConfig *shared.ApiConfig) CtxHttpBridge { | |
| 51 | 51 | "pubkey", pubkeyStr, | |
| 52 | 52 | ) | |
| 53 | 53 | ||
| 54 | - | props, err := getProjectFromSubdomain(subdomain) | |
| 54 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 55 | 55 | if err != nil { | |
| 56 | 56 | log.Error(err.Error()) | |
| 57 | 57 | return http.HandlerFunc(shared.UnauthorizedHandler) |
+2
-19
pgs/web.go
#
| ... | ... | @@ -177,7 +177,7 @@ func (web *WebRouter) checkHandler(w http.ResponseWriter, r *http.Request) { | |
| 177 | 177 | ||
| 178 | 178 | if !strings.Contains(hostDomain, appDomain) { | |
| 179 | 179 | subdomain := shared.GetCustomDomain(hostDomain, cfg.Space) | |
| 180 | - | props, err := getProjectFromSubdomain(subdomain) | |
| 180 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 181 | 181 | if err != nil { | |
| 182 | 182 | logger.Error( | |
| 183 | 183 | "could not get project from subdomain", |
| ... | ... | @@ -333,7 +333,7 @@ func (web *WebRouter) ServeAsset(fname string, opts *storage.ImgProcessOpts, fro | |
| 333 | 333 | "host", r.Host, | |
| 334 | 334 | ) | |
| 335 | 335 | ||
| 336 | - | props, err := getProjectFromSubdomain(subdomain) | |
| 336 | + | props, err := shared.GetProjectFromSubdomain(subdomain) | |
| 337 | 337 | if err != nil { | |
| 338 | 338 | logger.Info( | |
| 339 | 339 | "could not determine project from subdomain", |
| ... | ... | @@ -450,20 +450,3 @@ func (web *WebRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |
| 450 | 450 | ctx = context.WithValue(ctx, shared.CtxSubdomainKey{}, subdomain) | |
| 451 | 451 | router.ServeHTTP(w, r.WithContext(ctx)) | |
| 452 | 452 | } | |
| 453 | - | ||
| 454 | - | type SubdomainProps struct { | |
| 455 | - | ProjectName string | |
| 456 | - | Username string | |
| 457 | - | } | |
| 458 | - | ||
| 459 | - | func getProjectFromSubdomain(subdomain string) (*SubdomainProps, error) { | |
| 460 | - | props := &SubdomainProps{} | |
| 461 | - | strs := strings.SplitN(subdomain, "-", 2) | |
| 462 | - | props.Username = strs[0] | |
| 463 | - | if len(strs) == 2 { | |
| 464 | - | props.ProjectName = strs[1] | |
| 465 | - | } else { | |
| 466 | - | props.ProjectName = props.Username | |
| 467 | - | } | |
| 468 | - | return props, nil | |
| 469 | - | } |