├── .gitignore
├── CHANGELOG.md
├── .snapshots
├── TestApplication_Red
├── TestApplication_Blue
├── TestApplication_Color
├── TestApplication_Gray
├── TestApplication_Green
├── TestApplication_Orange
├── TestApplication_Purple
├── Test_CanSendAnArrayToRay
├── TestApplication_Ban
├── Test_CanSendAStringToRay
├── TestApplication_Bool
└── TestApplication_Charles
├── SECURITY.md
├── playground
└── playground.go
├── payloads
├── NullPayload.go
├── HidePayload.go
├── BoolPayload.go
├── HtmlPayload.go
├── RemovePayload.go
├── HideAppPayload.go
├── ShowAppPayload.go
├── StringPayload.go
├── ClearAllPayload.go
├── Payload.go
├── ClearScreenPayload.go
├── SizePayload.go
├── ColorPayload.go
├── LogPayload.go
├── NotifyPayload.go
├── NewScreenPayload.go
├── CreateLockPayload.go
├── CustomPayload.go
├── ImagePayload.go
├── TimePayload.go
├── JsonStringPayload.go
└── DumpPayload.go
├── Makefile
├── sizes.go
├── go.mod
├── .editorconfig
├── .github
├── workflows
│ ├── linter.yml
│ └── tests.yml
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── ray_test.go
├── colors.go
├── LICENSE.md
├── payloads_test.go
├── color_test.go
├── utils
├── client.go
└── stacktrace.go
├── go.sum
├── README.md
└── ray.go
/.gitignore:
--------------------------------------------------------------------------------
1 | coverage.out
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to Ray Go module will be documented in this file.
4 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Red:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"red"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Blue:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"blue"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Color:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"green"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Gray:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"gray"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Green:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"green"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Orange:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"orange"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Purple:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"purple"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/Test_CanSendAnArrayToRay:
--------------------------------------------------------------------------------
1 | [{"type":"color","content":{"color":"green"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Ban:
--------------------------------------------------------------------------------
1 | [{"type":"custom","content":{"content":"🕶","label":""},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/Test_CanSendAStringToRay:
--------------------------------------------------------------------------------
1 | [{"type":"custom","content":{"content":"hey","label":""},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Bool:
--------------------------------------------------------------------------------
1 | [{"type":"custom","content":{"content":false,"label":"Boolean"},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/.snapshots/TestApplication_Charles:
--------------------------------------------------------------------------------
1 | [{"type":"custom","content":{"content":"🎶 🎹 🎷 🕺","label":""},"origin":{"file":"","line_number":""}}]
2 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | If you discover any security related issues, please email me@octoper.me instead of using the issue tracker.
4 |
--------------------------------------------------------------------------------
/playground/playground.go:
--------------------------------------------------------------------------------
1 | //nolint
2 | package main
3 |
4 | import (
5 | "github.com/octoper/go-ray"
6 | )
7 |
8 | func main() {
9 | ray.Ray().Ban()
10 | }
11 |
--------------------------------------------------------------------------------
/payloads/NullPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewNullPayload creates a new Null Payload
4 | func NewNullPayload() Payload {
5 | return NewCustomPayload(nil, "Null")
6 | }
7 |
--------------------------------------------------------------------------------
/payloads/HidePayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewHidePayload creates a new Hide Payload
4 | func NewHidePayload() Payload {
5 | return Payload {
6 | Type: "hide",
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/payloads/BoolPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewBoolPayload creates a new Bool Payload
4 | func NewBoolPayload(bool bool) Payload {
5 | return NewCustomPayload(bool, "Boolean")
6 | }
7 |
--------------------------------------------------------------------------------
/payloads/HtmlPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewHtmlPayload creates a new HTML Payload
4 | func NewHtmlPayload(html string) Payload {
5 | return NewCustomPayload(html, "HTML")
6 | }
7 |
--------------------------------------------------------------------------------
/payloads/RemovePayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewRemovePayload creates a new Remove Payload
4 | func NewRemovePayload() Payload {
5 | return Payload {
6 | Type: "remove",
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/payloads/HideAppPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewHideAppPayload creates a new Hide App Payload
4 | func NewHideAppPayload() Payload {
5 | return Payload {
6 | Type: "hide_app",
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/payloads/ShowAppPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewShowAppPayload creates a new Show App Payload
4 | func NewShowAppPayload() Payload {
5 | return Payload {
6 | Type: "show_app",
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/payloads/StringPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewStringPayload creates a new String Payload
4 | func NewStringPayload(value string) Payload {
5 | return NewCustomPayload(value, "String")
6 | }
7 |
--------------------------------------------------------------------------------
/payloads/ClearAllPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewClearAllPayload creates a new Clear All Payload
4 | func NewClearAllPayload() Payload{
5 | return Payload {
6 | Type: "clear_all",
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/payloads/Payload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // Payload struct
4 | type Payload struct {
5 | Type string `json:"type"`
6 | Content interface{} `json:"content"`
7 | Origin interface{} `json:"origin"`
8 | }
9 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | build_playground: clean_playground
2 | go build -o playground/bin/main playground/playground.go
3 |
4 | playground:
5 | go run playground/playground.go
6 |
7 | clean_playground:
8 | rm -R playground/bin/
9 |
--------------------------------------------------------------------------------
/payloads/ClearScreenPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewClearScreenPayload creates a new Clear Screen Payload
4 | func NewClearScreenPayload() Payload {
5 | return Payload {
6 | Type: "new_screen",
7 | Content: "",
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/payloads/SizePayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewSizePayload creates a new Size Payload
4 | func NewSizePayload(color string) Payload{
5 | return Payload{
6 | Type: "size",
7 | Content: map[string]string {
8 | "size": color,
9 | },
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/payloads/ColorPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewColorPayload creates a new Color Payload
4 | func NewColorPayload(color string) Payload {
5 | return Payload {
6 | Type: "color",
7 | Content: map[string]string {
8 | "color": color,
9 | },
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/payloads/LogPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewLogPayload creates a new Log Payload
4 | func NewLogPayload(values ...interface{}) Payload {
5 | return Payload {
6 | Type: "log",
7 | Content: map[string]interface{} {
8 | "values": values,
9 | },
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/payloads/NotifyPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewNotifyPayload creates a new Notify Payload
4 | func NewNotifyPayload(value string) Payload {
5 | return Payload {
6 | Type: "notify",
7 | Content: map[string]interface{} {
8 | "value": value,
9 | },
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/payloads/NewScreenPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewNewScreenPayload creates a new New Screen Payload
4 | func NewNewScreenPayload(name string) Payload {
5 | return Payload {
6 | Type: "new_screen",
7 | Content: map[string]string {
8 | "name": name,
9 | },
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/payloads/CreateLockPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewCreateLockPayload creates a new Create Lock Payload
4 | func NewCreateLockPayload(name string) Payload {
5 | return Payload {
6 | Type: "create_lock",
7 | Content: map[string]string {
8 | "name": name,
9 | },
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/sizes.go:
--------------------------------------------------------------------------------
1 | package ray
2 |
3 | import "github.com/octoper/go-ray/payloads"
4 |
5 | func (r *application) Small() *application {
6 | return r.SendRequest(payloads.NewSizePayload("sm"))
7 | }
8 |
9 | func (r *application) Large() *application {
10 | return r.SendRequest(payloads.NewSizePayload("lg"))
11 | }
12 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/octoper/go-ray
2 |
3 | go 1.15
4 |
5 | require (
6 | github.com/bradleyjkemp/cupaloy/v2 v2.6.0
7 | github.com/davecgh/go-spew v1.1.1
8 | github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8
9 | github.com/google/uuid v1.2.0
10 | github.com/stretchr/testify v1.7.0
11 | )
12 |
--------------------------------------------------------------------------------
/payloads/CustomPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | // NewCustomPayload creates a new Custom Payload
4 | func NewCustomPayload(content interface{}, label string) Payload {
5 | return Payload {
6 | Type: "custom",
7 | Content: map[string]interface{} {
8 | "content": content,
9 | "label": label,
10 | },
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/payloads/ImagePayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | import (
4 | "strings"
5 | )
6 |
7 | // NewImagePayload creates a new Image Payload
8 | func NewImagePayload(location string) Payload {
9 | location = strings.ReplaceAll(location, "\"", "")
10 |
11 | return NewCustomPayload("", "Image")
12 | }
13 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | insert_final_newline = true
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | indent_style = tab
8 | indent_size = 4
9 |
10 | [{Makefile,go.mod,go.sum,*.go,.gitmodules}]
11 | indent_style = tab
12 | indent_size = 4
13 |
14 | [*.md]
15 | indent_size = 4
16 | trim_trailing_whitespace = false
17 |
18 | eclint_indent_style = unset
19 |
20 |
--------------------------------------------------------------------------------
/payloads/TimePayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | import "time"
4 |
5 | // NewTimePayload creates a new Time Payload
6 | func NewTimePayload(time time.Time, format string) Payload {
7 | timezoneName, _ := time.Zone()
8 |
9 | return Payload {
10 | Type: "carbon",
11 | Content: map[string]interface{} {
12 | "formatted": time.Format(format),
13 | "timestamp": time.Unix(),
14 | "timezone": timezoneName,
15 | },
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/payloads/JsonStringPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | import "encoding/json"
4 |
5 | // NewJsonStringPayload creates a new Json String Payload
6 | func NewJsonStringPayload(value interface{}) Payload {
7 | jsonValue, err := json.Marshal(value)
8 |
9 | if err != nil {
10 | panic(err)
11 | }
12 |
13 | return Payload {
14 | Type: "json_string",
15 | Content: map[string]interface{} {
16 | "value": string(jsonValue),
17 | },
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/linter.yml:
--------------------------------------------------------------------------------
1 | name: "Golang CI Lint"
2 | on:
3 | push:
4 | tags:
5 | - v*
6 | branches:
7 | - main
8 | pull_request:
9 | jobs:
10 | golangci:
11 | name: lint
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: golangci-lint
16 | uses: golangci/golangci-lint-action@v2
17 | with:
18 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
19 | version: v1.37.1
--------------------------------------------------------------------------------
/ray_test.go:
--------------------------------------------------------------------------------
1 | package ray
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "os"
6 | "testing"
7 | )
8 |
9 | func TestMain(m *testing.M) {
10 | Ray().Disable()
11 |
12 | os.Exit(m.Run())
13 | }
14 |
15 | func TestApplication_SetHost(t *testing.T) {
16 | t.Parallel()
17 |
18 | Ray().SetHost("192.168.1.255")
19 |
20 | assert.Equal(t, "192.168.1.255", Ray().Host())
21 | }
22 |
23 |
24 | func TestApplication_SetPort(t *testing.T) {
25 | t.Parallel()
26 |
27 | Ray().SetPort(27154)
28 |
29 | assert.Equal(t, 27154, Ray().Port())
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/payloads/DumpPayload.go:
--------------------------------------------------------------------------------
1 | package payloads
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/davecgh/go-spew/spew"
7 | "github.com/gomarkdown/markdown"
8 | )
9 |
10 | // NewDumpPayload creates a new Dump Payload
11 | func NewDumpPayload(value interface{}) Payload {
12 | md := []byte("``` \n" + spew.Sdump(value) + "\n```")
13 | output := markdown.ToHTML(md, nil, nil)
14 | styledOutput := strings.Replace(string(output), "
", strings.Join([]string{
15 | ``,
16 | ``,
17 | }, ""), 1)
18 | return NewCustomPayload(styledOutput, "")
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | on: [push, pull_request]
2 | name: "Tests"
3 | env:
4 | GO111MODULE: on
5 |
6 | jobs:
7 | test:
8 | strategy:
9 | matrix:
10 | go-version: [1.x, 1.16.x]
11 | platform: [ubuntu-latest, macos-latest]
12 | runs-on: ${{ matrix.platform }}
13 |
14 | steps:
15 | - uses: actions/setup-go@v1
16 | with:
17 | go-version: ${{ matrix.go-version }}
18 | - uses: actions/checkout@v2
19 |
20 | - name: Cache go modules
21 | uses: actions/cache@v1
22 | with:
23 | path: ~/go/pkg/mod
24 | key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
25 | restore-keys: ${{ runner.os }}-go-
26 |
27 | - name: Run go test
28 | run: go test -v ./...
29 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: octoper
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
19 | **Screenshots**
20 | If applicable, add screenshots to help explain your problem.
21 |
22 | **Platform (please complete the following information):**
23 | - OS: [e.g. iOS]
24 | - Go version [e.g.1.5, 1.16]
25 | - Ray module version [e.g. 0.0.3]
26 | - Ray App Version [e.g. 1.13]
27 |
28 | **Additional context**
29 | Add any other context about the problem here.
30 |
--------------------------------------------------------------------------------
/colors.go:
--------------------------------------------------------------------------------
1 | package ray
2 |
3 | import "github.com/octoper/go-ray/payloads"
4 |
5 | // Green Color
6 | func (r *application) Green() *application {
7 | return r.SendRequest(payloads.NewColorPayload("green"))
8 | }
9 |
10 | // Orange Color
11 | func (r *application) Orange() *application {
12 | return r.SendRequest(payloads.NewColorPayload("orange"))
13 | }
14 |
15 | // Red Color
16 | func (r *application) Red() *application {
17 | return r.SendRequest(payloads.NewColorPayload("red"))
18 | }
19 |
20 | // Blue Color
21 | func (r *application) Blue() *application {
22 | return r.SendRequest(payloads.NewColorPayload("blue"))
23 | }
24 |
25 | // Purple Color
26 | func (r *application) Purple() *application {
27 | return r.SendRequest(payloads.NewColorPayload("purple"))
28 | }
29 |
30 | // Gray Color
31 | func (r *application) Gray() *application {
32 | return r.SendRequest(payloads.NewColorPayload("gray"))
33 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Vaggelis Yfantis
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/payloads_test.go:
--------------------------------------------------------------------------------
1 | package ray
2 |
3 | import (
4 | "github.com/bradleyjkemp/cupaloy/v2"
5 | "testing"
6 | )
7 |
8 | func Test_CanSendAStringToRay(t *testing.T) {
9 | ray := Ray("hey")
10 |
11 | result, _ := ray.SentJsonPayloads()
12 |
13 | cupaloy.SnapshotT(t, result)
14 | }
15 |
16 | func Test_CanSendAnArrayToRay(t *testing.T) {
17 | ray := Ray([]int{2,4,6}).Color("green")
18 |
19 | result, _ := ray.SentJsonPayloads()
20 |
21 | cupaloy.SnapshotT(t, result)
22 | }
23 |
24 | func TestApplication_Bool(t *testing.T) {
25 |
26 | ray := Ray().Bool(false)
27 |
28 | result, _ := ray.SentJsonPayloads()
29 |
30 | cupaloy.SnapshotT(t, result)
31 | }
32 |
33 | func TestApplication_Ban(t *testing.T) {
34 |
35 | ray := Ray().Ban()
36 |
37 | result, _ := ray.SentJsonPayloads()
38 |
39 | cupaloy.SnapshotT(t, result)
40 | }
41 |
42 | func TestApplication_Color(t *testing.T) {
43 | t.Parallel()
44 |
45 | ray := Ray().Color("green")
46 |
47 | result, _ := ray.SentJsonPayloads()
48 |
49 | cupaloy.SnapshotT(t, result)
50 | }
51 |
52 | func TestApplication_Charles(t *testing.T) {
53 | t.Parallel()
54 |
55 | ray := Ray().Charles()
56 |
57 | result, _ := ray.SentJsonPayloads()
58 |
59 | cupaloy.SnapshotT(t, result)
60 | }
--------------------------------------------------------------------------------
/color_test.go:
--------------------------------------------------------------------------------
1 | package ray
2 |
3 | import (
4 | "github.com/bradleyjkemp/cupaloy/v2"
5 | "testing"
6 | )
7 |
8 | func TestApplication_Red(t *testing.T) {
9 | // t.Parallel()
10 |
11 | ray := Ray().Red()
12 |
13 | result, _ := ray.SentJsonPayloads()
14 |
15 | cupaloy.SnapshotT(t, result)
16 | }
17 |
18 |
19 | func TestApplication_Green(t *testing.T) {
20 | // t.Parallel()
21 |
22 | ray := Ray().Green()
23 |
24 | result, _ := ray.SentJsonPayloads()
25 |
26 | cupaloy.SnapshotT(t, result)
27 | }
28 |
29 | func TestApplication_Blue(t *testing.T) {
30 | // t.Parallel()
31 |
32 | ray := Ray().Blue()
33 |
34 | result, _ := ray.SentJsonPayloads()
35 |
36 | cupaloy.SnapshotT(t, result)
37 | }
38 |
39 | func TestApplication_Purple(t *testing.T) {
40 | // t.Parallel()
41 |
42 | ray := Ray().Purple()
43 |
44 | result, _ := ray.SentJsonPayloads()
45 |
46 | cupaloy.SnapshotT(t, result)
47 | }
48 |
49 | func TestApplication_Orange(t *testing.T) {
50 | // t.Parallel()
51 |
52 | ray := Ray().Orange()
53 |
54 | result, _ := ray.SentJsonPayloads()
55 |
56 | cupaloy.SnapshotT(t, result)
57 | }
58 |
59 | func TestApplication_Gray(t *testing.T) {
60 | // t.Parallel()
61 |
62 | ray := Ray().Gray()
63 |
64 | result, _ := ray.SentJsonPayloads()
65 |
66 | cupaloy.SnapshotT(t, result)
67 | }
68 |
69 |
--------------------------------------------------------------------------------
/utils/client.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "io"
7 | "net/http"
8 | "os"
9 | "strconv"
10 | )
11 |
12 | // Client struct
13 | type Client struct {
14 | host string
15 | port int
16 | }
17 |
18 | // LockRespnse struct
19 | type LockRespnse struct {
20 | Name string `json:"name"`
21 | Active bool `json:"active"`
22 | StopExecution bool `json:"stop_execution"`
23 | GroupUuid string `json:"displayed_on_group_uuid"`
24 | }
25 |
26 |
27 | // NewClient creates a Client instance
28 | func NewClient() *Client {
29 | return &Client{}
30 | }
31 |
32 | // SetPort Sets the port
33 | func (c *Client) SetPort(port int) int {
34 | c.port = port
35 | return c.port
36 | }
37 |
38 | // SetHost Sets the host
39 | func (c *Client) SetHost(host string) string {
40 | c.host = host
41 | return c.host
42 | }
43 |
44 |
45 | // Sent sents the give payload to Ray
46 | func (c *Client) Sent(requestPayload interface{}) (*http.Response, error) {
47 | requestJson, _ := json.Marshal(requestPayload)
48 |
49 | responseBody := bytes.NewBuffer(requestJson)
50 |
51 | //Make a request to Ray
52 | resp, err := http.Post("http://" + c.host + ":" + strconv.Itoa(c.port), "application/json", responseBody)
53 |
54 | if err != nil {
55 | panic(err)
56 | }
57 |
58 | defer resp.Body.Close()
59 |
60 | return resp, err
61 | }
62 |
63 | // LockExists checks if lock exists
64 | func (c *Client) LockExists(lockName string) LockRespnse {
65 | //Make a request to Ray
66 | resp, err := http.Get("http://" + c.host + ":" + strconv.Itoa(c.port) + "/locks/"+ lockName)
67 |
68 | if err != nil {
69 | panic(err)
70 | }
71 |
72 | body, bodyErr := io.ReadAll(resp.Body)
73 |
74 | if bodyErr != nil {
75 | panic(bodyErr)
76 | }
77 |
78 | res := LockRespnse{}
79 | if err := json.Unmarshal([]byte(body), &res); err != nil {
80 | panic(err)
81 | }
82 |
83 | if res.StopExecution {
84 | os.Exit(1)
85 | }
86 |
87 | return res
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/bradleyjkemp/cupaloy/v2 v2.6.0 h1:knToPYa2xtfg42U3I6punFEjaGFKWQRXJwj0JTv4mTs=
2 | github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6 | github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8 h1:nWU6p08f1VgIalT6iZyqXi4o5cZsz4X6qa87nusfcsc=
7 | github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
8 | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
9 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
14 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
16 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
17 | golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
20 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Debug your Go with Ray to fix problems faster
2 |
3 | 
4 | 
5 | 
6 | [](https://github.com/octoper/go-ray/actions/workflows/linter.yml)
7 | 
8 | [](https://pkg.go.dev/github.com/octoper/go-ray)
9 | [](https://plant.treeware.earth/octoper/go-ray)
10 |
11 | This module can be installed in any Go application to send messages to the Ray app.
12 |
13 | ## Install
14 |
15 | When using Go Modules, you do not need to install anything to start using Ray with your Go program. Import the module
16 | and the go will automatically download the latest version of the module when you next build your program.
17 |
18 | ```go
19 | import (
20 | "github.com/octoper/go-ray"
21 | )
22 | ```
23 |
24 | With or without Go Modules, to use the latest version of the SDK, run:
25 |
26 | `go get github.com/octoper/go-ray`
27 |
28 | Consult the [Go documentation on Modules](https://github.com/golang/go/wiki/Modules#how-to-upgrade-and-downgrade-dependencies) for more information on how to manage dependencies.
29 |
30 | ## Documentation
31 |
32 | You can find the full documentation on [our documentation site](https://spatie.be/docs/ray).
33 |
34 | ## Testing
35 |
36 | ```bash
37 | go test -v
38 | ```
39 |
40 | ## Changelog
41 |
42 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
43 |
44 | ## Contributing
45 |
46 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.
47 |
48 | ## Contributing
49 |
50 | Bug reports and pull requests are welcome on GitHub at https://github.com/octoper/go-ray. This project is intended to be
51 | a safe, welcoming space for collaboration, and contributors are expected to adhere to
52 | the [code of conduct](https://github.com/spatie/ray/blob/master/CODE_OF_CONDUCT.md).
53 |
54 | ## Contributing
55 |
56 | Please see [CODE_OF_CONDUCT](.github/CODE_OF_CONDUCT.md) for details.
57 |
58 | ## Security Vulnerabilities
59 |
60 | Please review [our security policy](SECURITY.md) on how to report security vulnerabilities.
61 |
62 | ## Credits
63 |
64 | - [Vaggelis Yfantis](https://github.com/octoper)
65 | - [All Contributors](../../contributors)
66 |
67 | ## License
68 |
69 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information.
70 |
71 | ## Treeware
72 |
73 | You're free to use this package, but if it makes it to your production environment you are required to buy the world a tree.
74 |
75 | It’s now common knowledge that one of the best tools to tackle the climate crisis and keep our temperatures from rising above 1.5C is to plant trees. If you support this package and contribute to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.
76 |
77 | You can buy trees here [offset.earth/treeware](https://plant.treeware.earth/octoper/go-ray)
78 |
79 | Read more about Treeware at [treeware.earth](http://treeware.earth)
80 |
--------------------------------------------------------------------------------
/ray.go:
--------------------------------------------------------------------------------
1 | package ray
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "encoding/json"
7 | "github.com/google/uuid"
8 | payloads "github.com/octoper/go-ray/payloads"
9 | "github.com/octoper/go-ray/utils"
10 | "os"
11 | "strconv"
12 | "time"
13 | )
14 |
15 | // Callable mocks a function that returns a boolean
16 | type Callable = func() bool
17 |
18 | type application struct {
19 | uuid string
20 | host string
21 | port int
22 | enabled bool
23 | sentPayloads []payloads.Payload
24 | client utils.Client
25 | }
26 |
27 | type request struct {
28 | Uuid string `json:"uuid"`
29 | Payloads interface{} `json:"payloads"`
30 | Meta map[string]string `json:"meta"`
31 | }
32 |
33 | var applicationConfig = application{
34 | host: "127.0.0.1",
35 | port: 23517,
36 | enabled: true,
37 | client: *utils.NewClient(),
38 | }
39 |
40 | // NewRay creates a New Ray instance
41 | func NewRay() *application {
42 | app := applicationConfig
43 | return &app
44 | }
45 |
46 | // Ray Creates a new insatnce of the application also can receive values to send to Ray
47 | func Ray(values ...interface{}) *application {
48 | r := NewRay()
49 |
50 | r.uuid = uuid.New().String()
51 |
52 | if values != nil {
53 | r.Send(values...)
54 | }
55 |
56 | return r
57 | }
58 |
59 | // Send Values
60 | func (r *application) Send(values ...interface{}) *application {
61 | var payloadsMap []payloads.Payload
62 |
63 | for _, payload := range values {
64 | switch payload.(type) { // nolint:gosimple
65 | case bool:
66 | payloadsMap = append(payloadsMap, payloads.NewBoolPayload(payload.(bool)))
67 | case nil:
68 | payloadsMap = append(payloadsMap, payloads.NewNullPayload())
69 | case string, int, int32, int64, float32, float64, complex64, complex128, uint, uint8, uint16, uint32, uint64:
70 | payloadsMap = append(payloadsMap, payloads.NewCustomPayload(payload, ""))
71 | default:
72 | payloadsMap = append(payloadsMap, payloads.NewDumpPayload(payload))
73 | }
74 | }
75 |
76 | return r.SendRequest(payloadsMap...)
77 | }
78 |
79 | // Get the UUID
80 | func (r *application) Uuid() string {
81 | return r.uuid
82 | }
83 |
84 | // Get the UUID
85 | func (r *application) Client() *utils.Client {
86 | r.client.SetHost(r.Host())
87 | r.client.SetPort(r.Port())
88 | return &r.client
89 | }
90 |
91 | // Get the port application is running
92 | func (r *application) Port() int {
93 | return r.port
94 | }
95 |
96 | // Set the port application is running
97 | func (r *application) SetPort(port int) {
98 | applicationConfig.port = port
99 | }
100 |
101 | // Get the host application is running
102 | func (r *application) Host() string {
103 | return r.host
104 | }
105 |
106 | // Set the host application is running
107 | func (r *application) SetHost(host string) {
108 | applicationConfig.host = host
109 | }
110 |
111 | // Get Sent Payloads as Json
112 | func (r *application) SentJsonPayloads() ([]byte, error) {
113 | return json.Marshal(applicationConfig.sentPayloads)
114 | }
115 |
116 | // Enable sending payloads to Ray
117 | func (r *application) Enable() {
118 | applicationConfig.enabled = true
119 | }
120 |
121 | // Prevents sending payloads to Ray
122 | func (r *application) Disable() {
123 | applicationConfig.enabled = false
124 | }
125 |
126 | // Check if Ray is enabled
127 | func (r *application) Enabled() bool {
128 | return applicationConfig.enabled
129 | }
130 |
131 | // Check if Ray is disabled
132 | func (r *application) Disabled() bool {
133 | return !applicationConfig.enabled
134 | }
135 |
136 | // Create New Screen
137 | func (r *application) NewScreen(name string) *application {
138 | return r.SendRequest(payloads.NewNewScreenPayload(name))
139 | }
140 |
141 | // Color
142 | func (r *application) Color(color string) *application {
143 | return r.SendRequest(payloads.NewColorPayload(color))
144 | }
145 |
146 | // Send custom payload
147 | func (r *application) SendCustom(content interface{}, label string) *application {
148 | return r.SendRequest(payloads.NewCustomPayload(content, label))
149 | }
150 |
151 | // Size
152 | func (r *application) Size(size string) *application {
153 | return r.SendRequest(payloads.NewSizePayload(size))
154 | }
155 |
156 | // Hide
157 | func (r *application) Hide() *application {
158 | return r.SendRequest(payloads.NewHidePayload())
159 | }
160 |
161 | // Hide App
162 | func (r *application) HideApp() *application {
163 | return r.SendRequest(payloads.NewHideAppPayload())
164 | }
165 |
166 | // Show App
167 | func (r *application) ShowApp() *application {
168 | return r.SendRequest(payloads.NewShowAppPayload())
169 | }
170 |
171 | // Clear Screen
172 | func (r *application) ClearScreen() *application {
173 | return r.SendRequest(payloads.NewClearScreenPayload())
174 | }
175 |
176 | // Clear All
177 | func (r *application) ClearAll() *application {
178 | return r.SendRequest(payloads.NewClearAllPayload())
179 | }
180 |
181 | // HTML
182 | func (r *application) Html(html string) *application {
183 | return r.SendRequest(payloads.NewHtmlPayload(html))
184 | }
185 |
186 | // Notify
187 | func (r *application) Notify(text string) *application {
188 | return r.SendRequest(payloads.NewNotifyPayload(text))
189 | }
190 |
191 | // Boolean
192 | func (r *application) Bool(bool bool) *application {
193 | return r.SendRequest(payloads.NewBoolPayload(bool))
194 | }
195 |
196 | // Null
197 | func (r *application) Null() *application {
198 | return r.SendRequest(payloads.NewNullPayload())
199 | }
200 |
201 | // Charles
202 | func (r *application) Charles() *application {
203 | return r.Send("🎶 🎹 🎷 🕺")
204 | }
205 |
206 | // String
207 | func (r *application) String(str string) *application {
208 | return r.SendRequest(payloads.NewStringPayload(str))
209 | }
210 |
211 | //Time
212 | func (r *application) Time(time time.Time) *application {
213 | return r.SendRequest(payloads.NewTimePayload(time, "2006-01-02 15:04:05"))
214 | }
215 |
216 | // Time with Format
217 | func (r *application) TimeWithFormat(time time.Time, format string) *application {
218 | return r.SendRequest(payloads.NewTimePayload(time, format))
219 | }
220 |
221 | // Pause code execution
222 | func (r *application) Pause() *application {
223 | hash := md5.New()
224 | _, err := hash.Write([]byte(time.Now().String()))
225 |
226 | if err != nil {
227 | panic(err)
228 | }
229 |
230 | lockName := hash.Sum(nil)
231 |
232 | r.SendRequest(payloads.NewCreateLockPayload(hex.EncodeToString(lockName)))
233 |
234 | for {
235 | time.Sleep(time.Second);
236 |
237 | lockExistsClient := r.Client().LockExists(hex.EncodeToString(lockName))
238 | if !lockExistsClient.Active {
239 | break
240 | }
241 | }
242 |
243 | return r
244 | }
245 |
246 | // Sends the provided value(s) encoded as a JSON string using json.Marshal.
247 | func (r *application) ToJson(jsons ...interface{}) *application {
248 | for _, jsonValue := range jsons {
249 | r.SendRequest(payloads.NewJsonStringPayload(jsonValue))
250 | }
251 | return r
252 | }
253 |
254 | // Image
255 | func (r *application) Image(value string) *application {
256 | return r.SendRequest(payloads.NewImagePayload(value))
257 | }
258 |
259 | // Ban
260 | func (r *application) Ban() *application {
261 | return r.Send("🕶")
262 | }
263 |
264 | // Die
265 | func (r *application) Die() {
266 | r.DieWithStatusCode(1)
267 | }
268 |
269 | // Die with Status Code
270 | func (r *application) DieWithStatusCode(status int) {
271 | os.Exit(status)
272 | }
273 |
274 | // Remove
275 | func (r *application) Remove() *application {
276 | return r.SendRequest(payloads.NewRemovePayload())
277 | }
278 |
279 | // Show When
280 | func (r *application) ShowWhen(show interface{}) *application {
281 | switch show.(type) { // nolint:gosimple
282 | case Callable:
283 | show = show.(Callable)()
284 | }
285 |
286 | if !show.(bool) {
287 | return r.Remove()
288 | }
289 |
290 | return r
291 | }
292 |
293 | // Show If
294 | func (r *application) ShowIf(show interface{}) *application {
295 | return r.ShowWhen(show)
296 | }
297 |
298 | // Remove When
299 | func (r *application) RemoveWhen(show interface{}) *application {
300 | switch show.(type) { // nolint:gosimple
301 | case Callable:
302 | show = show.(Callable)()
303 | }
304 |
305 | if show.(bool) {
306 | return r.Remove()
307 | }
308 |
309 | return r
310 | }
311 |
312 | // Remove If
313 | func (r *application) RemoveIf(show interface{}) *application {
314 | return r.RemoveWhen(show)
315 | }
316 |
317 | // Set the host application is running
318 | func (r *application) SendRequest(ResponsePayloads ...payloads.Payload) *application {
319 | var payloadsMap []payloads.Payload
320 |
321 | // stack := utils.NewStacktrace()
322 | stackAbsPath := ""
323 | stackLineNo := ""
324 |
325 | /*if len(stack.Frames) > 0 {
326 | stackAbsPath = stack.Frames[0].AbsPath
327 | stackLineNo = strconv.Itoa(stack.Frames[0].Lineno)
328 | }*/
329 |
330 | for _, payload := range ResponsePayloads {
331 | payload.Origin = map[string]string{
332 | "file": stackAbsPath,
333 | "line_number": stackLineNo,
334 | }
335 |
336 | payloadsMap = append(payloadsMap, payload)
337 | }
338 |
339 | applicationConfig.sentPayloads = payloadsMap
340 |
341 | requestPayload := request{
342 | Uuid: r.Uuid(),
343 | Payloads: payloadsMap,
344 | Meta: map[string]string{
345 | "ray_package_version": "0.1.4",
346 | },
347 | }
348 |
349 | if !r.enabled {
350 | return r
351 | }
352 |
353 | client := r.Client()
354 |
355 | _, err := client.Sent(requestPayload)
356 |
357 | //Handle Error
358 | if err != nil {
359 | panic("Couldn't connect to Ray It doesn't seem to be running at " + Ray().Host() + ":" + strconv.Itoa(Ray().Port()))
360 | }
361 |
362 | return r
363 | }
364 |
365 |
--------------------------------------------------------------------------------
/utils/stacktrace.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | /**
4 | * Thanks to Sentry Team and Sentry Go SDK
5 | *
6 | * @see https://github.com/getsentry/sentry-go/blob/master/stacktrace.go
7 | */
8 |
9 | import (
10 | "go/build"
11 | "path/filepath"
12 | "reflect"
13 | "runtime"
14 | "strings"
15 | )
16 |
17 | const unknown string = "unknown"
18 |
19 | // The module download is split into two parts: downloading the go.mod and downloading the actual code.
20 | // If you have dependencies only needed for tests, then they will show up in your go.mod,
21 | // and go get will download their go.mods, but it will not download their code.
22 | // The test-only dependencies get downloaded only when you need it, such as the first time you run go test.
23 | //
24 | // https://github.com/golang/go/issues/26913#issuecomment-411976222
25 |
26 | // Stacktrace holds information about the frames of the stack.
27 | type Stacktrace struct {
28 | Frames []Frame `json:"frames,omitempty"`
29 | FramesOmitted []uint `json:"frames_omitted,omitempty"`
30 | }
31 |
32 | // NewStacktrace creates a stacktrace using runtime.Callers.
33 | func NewStacktrace() *Stacktrace {
34 | pcs := make([]uintptr, 100)
35 | n := runtime.Callers(1, pcs)
36 |
37 | if n == 0 {
38 | return nil
39 | }
40 |
41 | frames := extractFrames(pcs[:n])
42 | frames = filterFrames(frames)
43 |
44 | stacktrace := Stacktrace{
45 | Frames: frames,
46 | }
47 |
48 | return &stacktrace
49 | }
50 |
51 | // TODO: Make it configurable so that anyone can provide their own implementation?
52 | // Use of reflection allows us to not have a hard dependency on any given
53 | // package, so we don't have to import it.
54 |
55 | // ExtractStacktrace creates a new Stacktrace based on the given error.
56 | func ExtractStacktrace(err error) *Stacktrace {
57 | method := extractReflectedStacktraceMethod(err)
58 |
59 | var pcs []uintptr
60 |
61 | if method.IsValid() {
62 | pcs = extractPcs(method)
63 | } else {
64 | pcs = extractXErrorsPC(err)
65 | }
66 |
67 | if len(pcs) == 0 {
68 | return nil
69 | }
70 |
71 | frames := extractFrames(pcs)
72 | frames = filterFrames(frames)
73 |
74 | stacktrace := Stacktrace{
75 | Frames: frames,
76 | }
77 |
78 | return &stacktrace
79 | }
80 |
81 | func extractReflectedStacktraceMethod(err error) reflect.Value {
82 | var method reflect.Value
83 |
84 | // https://github.com/pingcap/errors
85 | methodGetStackTracer := reflect.ValueOf(err).MethodByName("GetStackTracer")
86 | // https://github.com/pkg/errors
87 | methodStackTrace := reflect.ValueOf(err).MethodByName("StackTrace")
88 | // https://github.com/go-errors/errors
89 | methodStackFrames := reflect.ValueOf(err).MethodByName("StackFrames")
90 |
91 | if methodGetStackTracer.IsValid() {
92 | stacktracer := methodGetStackTracer.Call(make([]reflect.Value, 0))[0]
93 | stacktracerStackTrace := reflect.ValueOf(stacktracer).MethodByName("StackTrace")
94 |
95 | if stacktracerStackTrace.IsValid() {
96 | method = stacktracerStackTrace
97 | }
98 | }
99 |
100 | if methodStackTrace.IsValid() {
101 | method = methodStackTrace
102 | }
103 |
104 | if methodStackFrames.IsValid() {
105 | method = methodStackFrames
106 | }
107 |
108 | return method
109 | }
110 |
111 | func extractPcs(method reflect.Value) []uintptr {
112 | var pcs []uintptr
113 |
114 | stacktrace := method.Call(make([]reflect.Value, 0))[0]
115 |
116 | if stacktrace.Kind() != reflect.Slice {
117 | return nil
118 | }
119 |
120 | for i := 0; i < stacktrace.Len(); i++ {
121 | pc := stacktrace.Index(i)
122 |
123 | if pc.Kind() == reflect.Uintptr {
124 | pcs = append(pcs, uintptr(pc.Uint()))
125 | continue
126 | }
127 |
128 | if pc.Kind() == reflect.Struct {
129 | field := pc.FieldByName("ProgramCounter")
130 | if field.IsValid() && field.Kind() == reflect.Uintptr {
131 | pcs = append(pcs, uintptr(field.Uint()))
132 | continue
133 | }
134 | }
135 | }
136 |
137 | return pcs
138 | }
139 |
140 | // extractXErrorsPC extracts program counters from error values compatible with
141 | // the error types from golang.org/x/xerrors.
142 | //
143 | // It returns nil if err is not compatible with errors from that package or if
144 | // no program counters are stored in err.
145 | func extractXErrorsPC(err error) []uintptr {
146 | // This implementation uses the reflect package to avoid a hard dependency
147 | // on third-party packages.
148 |
149 | // We don't know if err matches the expected type. For simplicity, instead
150 | // of trying to account for all possible ways things can go wrong, some
151 | // assumptions are made and if they are violated the code will panic. We
152 | // recover from any panic and ignore it, returning nil.
153 | //nolint: errcheck
154 | defer func() { recover() }()
155 |
156 | field := reflect.ValueOf(err).Elem().FieldByName("frame") // type Frame struct{ frames [3]uintptr }
157 | field = field.FieldByName("frames")
158 | field = field.Slice(1, field.Len()) // drop first pc pointing to xerrors.New
159 | pc := make([]uintptr, field.Len())
160 | for i := 0; i < field.Len(); i++ {
161 | pc[i] = uintptr(field.Index(i).Uint())
162 | }
163 | return pc
164 | }
165 |
166 | // Frame represents a function call and it's metadata. Frames are associated
167 | // with a Stacktrace.
168 | type Frame struct {
169 | Function string `json:"function,omitempty"`
170 | Symbol string `json:"symbol,omitempty"`
171 | Module string `json:"module,omitempty"`
172 | Package string `json:"package,omitempty"`
173 | Filename string `json:"filename,omitempty"`
174 | AbsPath string `json:"abs_path,omitempty"`
175 | Lineno int `json:"lineno,omitempty"`
176 | Colno int `json:"colno,omitempty"`
177 | PreContext []string `json:"pre_context,omitempty"`
178 | ContextLine string `json:"context_line,omitempty"`
179 | PostContext []string `json:"post_context,omitempty"`
180 | InApp bool `json:"in_app,omitempty"`
181 | Vars map[string]interface{} `json:"vars,omitempty"`
182 | }
183 |
184 | // NewFrame assembles a stacktrace frame out of runtime.Frame.
185 | func NewFrame(f runtime.Frame) Frame {
186 | var abspath, relpath string
187 | // NOTE: f.File paths historically use forward slash as path separator even
188 | // on Windows, though this is not yet documented, see
189 | // https://golang.org/issues/3335. In any case, filepath.IsAbs can work with
190 | // paths with either slash or backslash on Windows.
191 | switch {
192 | case f.File == "":
193 | relpath = unknown
194 | // Leave abspath as the empty string to be omitted when serializing
195 | // event as JSON.
196 | abspath = ""
197 | case filepath.IsAbs(f.File):
198 | abspath = f.File
199 | // TODO: in the general case, it is not trivial to come up with a
200 | // "project relative" path with the data we have in run time.
201 | // We shall not use filepath.Base because it creates ambiguous paths and
202 | // affects the "Suspect Commits" feature.
203 | // For now, leave relpath empty to be omitted when serializing the event
204 | // as JSON. Improve this later.
205 | relpath = ""
206 | default:
207 | // f.File is a relative path. This may happen when the binary is built
208 | // with the -trimpath flag.
209 | relpath = f.File
210 | // Omit abspath when serializing the event as JSON.
211 | abspath = ""
212 | }
213 |
214 | function := f.Function
215 | var pkg string
216 |
217 | if function != "" {
218 | pkg, function = splitQualifiedFunctionName(function)
219 | }
220 |
221 | frame := Frame{
222 | AbsPath: abspath,
223 | Filename: relpath,
224 | Lineno: f.Line,
225 | Module: pkg,
226 | Function: function,
227 | }
228 |
229 | frame.InApp = isInAppFrame(frame)
230 |
231 | return frame
232 | }
233 |
234 | // splitQualifiedFunctionName splits a package path-qualified function name into
235 | // package name and function name. Such qualified names are found in
236 | // runtime.Frame.Function values.
237 | func splitQualifiedFunctionName(name string) (pkg string, fun string) {
238 | pkg = packageName(name)
239 | fun = strings.TrimPrefix(name, pkg+".")
240 | return
241 | }
242 |
243 | func extractFrames(pcs []uintptr) []Frame {
244 | var frames []Frame
245 | callersFrames := runtime.CallersFrames(pcs)
246 |
247 | for {
248 | callerFrame, more := callersFrames.Next()
249 |
250 | frames = append([]Frame{
251 | NewFrame(callerFrame),
252 | }, frames...)
253 |
254 | if !more {
255 | break
256 | }
257 | }
258 |
259 | return frames
260 | }
261 |
262 | // filterFrames filters out stack frames that are not meant to be reported to
263 | // Sentry. Those are frames internal to the SDK or Go.
264 | func filterFrames(frames []Frame) []Frame {
265 | if len(frames) == 0 {
266 | return nil
267 | }
268 |
269 | filteredFrames := make([]Frame, 0, len(frames))
270 |
271 | for _, frame := range frames {
272 | // Skip Go internal frames.
273 | if frame.Module == "runtime" || frame.Module == "testing" {
274 | continue
275 | }
276 | // Skip Sentry internal frames, except for frames in _test packages (for testing).
277 | if strings.HasPrefix(frame.Module, "github.com/octoper/go-ray") &&
278 | !strings.HasSuffix(frame.Module, "_test") {
279 | continue
280 | }
281 | filteredFrames = append(filteredFrames, frame)
282 | }
283 |
284 | return filteredFrames
285 | }
286 |
287 | func isInAppFrame(frame Frame) bool {
288 | if strings.HasPrefix(frame.AbsPath, build.Default.GOROOT) ||
289 | strings.Contains(frame.Module, "vendor") ||
290 | strings.Contains(frame.Module, "third_party") {
291 | return false
292 | }
293 |
294 | return true
295 | }
296 |
297 | // packageName returns the package part of the symbol name, or the empty string
298 | // if there is none.
299 | // It replicates https://golang.org/pkg/debug/gosym/#Sym.PackageName, avoiding a
300 | // dependency on debug/gosym.
301 | func packageName(name string) string {
302 | // A prefix of "type." and "go." is a compiler-generated symbol that doesn't belong to any package.
303 | // See variable reservedimports in cmd/compile/internal/gc/subr.go
304 | if strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.") {
305 | return ""
306 | }
307 |
308 | pathend := strings.LastIndex(name, "/")
309 | if pathend < 0 {
310 | pathend = 0
311 | }
312 |
313 | if i := strings.Index(name[pathend:], "."); i != -1 {
314 | return name[:pathend+i]
315 | }
316 | return ""
317 | }
318 |
--------------------------------------------------------------------------------