├── .github
├── CODEOWNERS
└── workflows
│ └── main.yml
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── epsagon
├── aws_sdk_v2_factories
│ ├── common_utils.go
│ ├── dynamodb_factories.go
│ ├── s3_factory.go
│ └── sts_factory.go
├── aws_v2_wrapper.go
├── aws_v2_wrapper_factories_test.go
├── common_utils.go
├── concurrent_generic_wrapper_test.go
├── custom_trace_data_test.go
├── generic_lambda_handler.go
├── generic_lambda_handler_test.go
├── generic_wrapper.go
├── generic_wrapper_test.go
├── lambda_trigger.go
├── lambda_trigger_test.go
├── lambda_wrapper.go
├── lambda_wrapper_test.go
└── tracer_helpers.go
├── example
├── api_gateway
│ ├── README.md
│ ├── main.go
│ ├── makefile
│ └── serverless.yml
├── concurrent_generic_go_functions
│ ├── main.go
│ └── makefile
├── custom_error_example
│ ├── main.go
│ └── makefile
├── ddb_example
│ ├── makefile
│ ├── serverless.yml
│ ├── trigger
│ │ └── main.go
│ ├── write
│ │ └── main.go
│ └── write_sdk_v2
│ │ └── main.go
├── exp_example
│ ├── main.go
│ └── serverless.yml
├── fiber_middleware
│ ├── main.go
│ └── makefile
├── generic_go_function
│ ├── main.go
│ └── makefile
├── gin_wrapper
│ ├── main.go
│ └── makefile
├── http_mux
│ └── main.go
├── ignored_keys
│ └── main.go
├── label_example
│ ├── main.go
│ └── makefile
├── mongo_example
│ └── main.go
├── mux_error
│ └── main.go
├── redis_wrapper
│ └── main.go
├── s3_example
│ ├── README.md
│ ├── serverless.yml
│ ├── trigger
│ │ └── main.go
│ ├── write
│ │ └── main.go
│ └── write_v2
│ │ └── main.go
├── simple_error
│ ├── README.md
│ ├── main.go
│ ├── makefile
│ └── serverless.yml
├── simple_lambda
│ ├── README.md
│ ├── main.go
│ ├── makefile
│ └── serverless.yml
├── sqs_trigger
│ ├── hello
│ │ └── main.go
│ ├── makefile
│ ├── serverless.yml
│ └── triggered
│ │ └── main.go
└── sts_example
│ └── main.go
├── go.mod
├── go.sum
├── internal
└── fake_collector.go
├── protocol
├── error_code.pb.go
├── event.pb.go
├── exception.pb.go
├── timestamp.pb.go
└── trace.pb.go
├── trace.png
├── tracer
├── common_utils.go
├── mask_ignored_keys.go
├── mask_ignored_keys_test.go
├── mocked_tracer.go
├── tracer.go
├── tracer_test.go
└── version.go
└── wrappers
├── aws
└── aws-sdk-go
│ └── aws
│ ├── aws.go
│ ├── aws_test.go
│ ├── common_utils.go
│ ├── dynamodb_factories.go
│ ├── kinesis_factory.go
│ ├── lambda_factory.go
│ ├── s3_factory.go
│ ├── ses_factory.go
│ ├── sfn_factory.go
│ ├── sns_factory.go
│ └── sqs_factories.go
├── fiber
├── fiber.go
└── fiber_test.go
├── gin
├── gin.go
└── gin_test.go
├── mongo
├── common_utils.go
├── mongo.go
└── mongo_test.go
├── net
└── http
│ ├── client.go
│ ├── client_test.go
│ ├── handler_wrapper.go
│ ├── handler_wrapper_test.go
│ └── response_writer.go
└── redis
├── redis.go
└── redis_test.go
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Each line is a file pattern followed by one or more owners.
2 | * @epsagon/the-fabulous-team
3 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | # Controls when the action will run. Triggers the workflow on push or pull request
4 | # events but only for the main branch
5 | on:
6 | push:
7 | branches:
8 | - master
9 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
10 | jobs:
11 | # This workflow contains a single job called "build"
12 | build:
13 | # The type of runner that the job will run on
14 | runs-on: ubuntu-latest
15 | if: "!contains(github.event.head_commit.message, 'Increasing version')"
16 | # Steps represent a sequence of tasks that will be executed as part of the job
17 | steps:
18 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
19 | - uses: actions/checkout@v2
20 | with:
21 | token: ${{ secrets.INCREASE_VERSION_TOKEN }}
22 | # Runs a set of commands using the runners shell
23 | - name: Update version
24 | env:
25 | # This is necessary in order to push a commit to the repo
26 | GITHUB_TOKEN: ${{ secrets.INCREASE_VERSION_TOKEN }}
27 | run: |
28 | git remote add github "https://maorlx:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.git"
29 | git pull github ${GITHUB_REF} --ff-only
30 | git config --global user.email "maor@epsagon.com"
31 | git config --global user.name "Maor Levi"
32 | version=`cat tracer/version.go | egrep "const VERSION = " | tr -s ' ' | cut -d ' ' -f 4`
33 | minor=`echo $version | cut -d. -f2`
34 | major=`echo $version | cut -d. -f1`
35 | patch=`echo $version | cut -d. -f3`
36 | new_minor=`echo "$((minor+1))"`
37 | new_version="${major}.${new_minor}.${patch}"
38 | echo $new_version
39 | sed -i "s/${version}/${new_version}/g" tracer/version.go
40 | git commit -m "Increasing version to $new_version" tracer/version.go
41 | git push github HEAD:${GITHUB_REF}
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # GoLand IDE
2 | .idea
3 |
4 | # Binaries for programs and plugins
5 | *.exe
6 | *.exe~
7 | *.dll
8 | *.so
9 | *.dylib
10 |
11 | # Test binary, build with `go test -c`
12 | *.test
13 | test
14 |
15 | # Output of the go coverage tool, specifically when used with LiteIDE
16 | Gopkg.lock
17 | *.out
18 |
19 | # vendor directory are not needed here
20 | vendor
21 | example/simple_lambda/bin/hello
22 | example/simple_lambda/main
23 | example/sqs_trigger/hello/main
24 | example/sqs_trigger/triggered/main
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - "1.13.x"
4 | - "tip"
5 |
6 | env:
7 | - GO111MODULE=on
8 |
9 | before_install:
10 | - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
11 |
12 | install:
13 | - go mod download
14 | - go get golang.org/x/lint/golint
15 | - go install golang.org/x/lint/golint
16 | - go mod tidy
17 |
18 | script:
19 | - go test ./...
20 | - go vet -v ./...
21 | - golint $(go list ./...)
22 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at dev@epsagon.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Epsagon
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 |
--------------------------------------------------------------------------------
/epsagon/aws_sdk_v2_factories/common_utils.go:
--------------------------------------------------------------------------------
1 | package epsagonawsv2factories
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/aws/aws-sdk-go-v2/aws"
7 | "github.com/epsagon/epsagon-go/protocol"
8 | "github.com/epsagon/epsagon-go/tracer"
9 | "reflect"
10 | "strconv"
11 | )
12 |
13 | type specificOperationHandler func(
14 | r *aws.Request,
15 | res *protocol.Resource,
16 | metadataOnly bool,
17 | currentTracer tracer.Tracer,
18 | )
19 |
20 | func handleSpecificOperation(
21 | r *aws.Request,
22 | res *protocol.Resource,
23 | metadataOnly bool,
24 | handlers map[string]specificOperationHandler,
25 | defaultHandler specificOperationHandler,
26 | currentTracer tracer.Tracer,
27 | ) {
28 | handler := handlers[res.Operation]
29 | if handler == nil {
30 | handler = defaultHandler
31 | }
32 | if handler != nil {
33 | handler(r, res, metadataOnly, currentTracer)
34 | }
35 | }
36 |
37 | func getFieldStringPtr(value reflect.Value, fieldName string) (string, bool) {
38 | field := value.FieldByName(fieldName)
39 | if field == (reflect.Value{}) {
40 | return "", false
41 | }
42 | return field.Elem().String(), true
43 | }
44 |
45 | func updateMetadataField(data reflect.Value, key string, res *protocol.Resource) {
46 | value, ok := getFieldStringPtr(data, key)
47 | if ok {
48 | res.Metadata[key] = value
49 | }
50 | }
51 |
52 | func updateMetadataFromBytes(
53 | value reflect.Value, fieldName string, targetKey string, metadata map[string]string) {
54 | field := value.FieldByName(fieldName)
55 | if field == (reflect.Value{}) {
56 | return
57 | }
58 | metadata[targetKey] = string(field.Bytes())
59 | }
60 |
61 | func updateMetadataFromValue(
62 | value reflect.Value, fieldName string, targetKey string, metadata map[string]string) {
63 | fieldValue, ok := getFieldStringPtr(value, fieldName)
64 | if ok {
65 | metadata[targetKey] = fieldValue
66 | }
67 | }
68 |
69 | func updateMetadataFromInt64(
70 | value reflect.Value, fieldName string, targetKey string, metadata map[string]string) {
71 | field := value.FieldByName(fieldName)
72 | if field == (reflect.Value{}) {
73 | return
74 | }
75 | metadata[targetKey] = strconv.FormatInt(field.Elem().Int(), 10)
76 | }
77 |
78 | func updateMetadataWithFieldToJSON(
79 | value reflect.Value,
80 | fieldName string,
81 | targetKey string,
82 | metadata map[string]string,
83 | currentTracer tracer.Tracer,
84 | ) {
85 | field := value.FieldByName(fieldName)
86 | if field == (reflect.Value{}) {
87 | return
88 | }
89 | stream, err := json.Marshal(field.Interface())
90 | if err != nil {
91 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go", fmt.Sprintf("%v", err))
92 | return
93 | }
94 | metadata[targetKey] = string(stream)
95 | }
96 |
97 | func getResourceNameFromField(res *protocol.Resource, value reflect.Value, fieldName string) {
98 | fieldValue, ok := getFieldStringPtr(value, fieldName)
99 | if ok {
100 | res.Name = fieldValue
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/epsagon/aws_sdk_v2_factories/dynamodb_factories.go:
--------------------------------------------------------------------------------
1 | package epsagonawsv2factories
2 |
3 | import (
4 | "crypto/md5"
5 | "encoding/hex"
6 | "encoding/json"
7 | "fmt"
8 | "github.com/aws/aws-sdk-go-v2/aws"
9 | "github.com/aws/aws-sdk-go-v2/service/dynamodb"
10 | "github.com/epsagon/epsagon-go/protocol"
11 | "github.com/epsagon/epsagon-go/tracer"
12 | "reflect"
13 | )
14 |
15 | // DynamodbEventDataFactory to create epsagon Resource from aws.Request to DynamoDB
16 | func DynamodbEventDataFactory(
17 | r *aws.Request,
18 | res *protocol.Resource,
19 | metadataOnly bool,
20 | currentTracer tracer.Tracer,
21 | ) {
22 | inputValue := reflect.ValueOf(r.Params).Elem()
23 | tableName, ok := getFieldStringPtr(inputValue, "TableName")
24 | if ok {
25 | res.Name = tableName
26 | }
27 | handleSpecificOperations := map[string]specificOperationHandler{
28 | "PutItem": handleDynamoDBPutItem,
29 | "GetItem": handleDynamoDBGetItem,
30 | "DeleteItem": handleDynamoDBDeleteItem,
31 | "UpdateItem": handleDynamoDBUpdateItem,
32 | "Scan": handleDynamoDBScan,
33 | "BatchWriteItem": handleDynamoDBBatchWriteItem,
34 | }
35 | handleSpecificOperation(r, res, metadataOnly, handleSpecificOperations, nil, currentTracer)
36 | }
37 |
38 | func deserializeAttributeMap(inputField reflect.Value) map[string]string {
39 | formattedItem := make(map[string]string)
40 | input := inputField.Interface().(map[string]dynamodb.AttributeValue)
41 | for k, v := range input {
42 | formattedItem[k] = v.String()
43 | }
44 | return formattedItem
45 | }
46 |
47 | func jsonAttributeMap(inputField reflect.Value, currentTracer tracer.Tracer) string {
48 | if inputField == (reflect.Value{}) {
49 | return ""
50 | }
51 | formattedMap := deserializeAttributeMap(inputField)
52 | stream, err := json.Marshal(formattedMap)
53 | if err != nil {
54 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go", fmt.Sprintf("%v", err))
55 | return ""
56 | }
57 | return string(stream)
58 | }
59 |
60 | func handleDynamoDBPutItem(
61 | r *aws.Request,
62 | res *protocol.Resource,
63 | metadataOnly bool,
64 | _ tracer.Tracer,
65 | ) {
66 | inputValue := reflect.ValueOf(r.Params).Elem()
67 | itemField := inputValue.FieldByName("Item")
68 | if itemField == (reflect.Value{}) {
69 | return
70 | }
71 | formattedItem := deserializeAttributeMap(itemField)
72 | formattedItemStream, err := json.Marshal(formattedItem)
73 | if err != nil {
74 | // TODO send tracer exception?
75 | return
76 | }
77 | if !metadataOnly {
78 | res.Metadata["Item"] = string(formattedItemStream)
79 | }
80 | h := md5.New()
81 | h.Write(formattedItemStream)
82 | res.Metadata["item_hash"] = hex.EncodeToString(h.Sum(nil))
83 | }
84 |
85 | func handleDynamoDBGetItem(
86 | r *aws.Request,
87 | res *protocol.Resource,
88 | metadataOnly bool,
89 | currentTracer tracer.Tracer,
90 | ) {
91 | inputValue := reflect.ValueOf(r.Params).Elem()
92 | jsonKeyField := jsonAttributeMap(inputValue.FieldByName("Key"), currentTracer)
93 | res.Metadata["Key"] = jsonKeyField
94 |
95 | if !metadataOnly {
96 | outputValue := reflect.ValueOf(r.Data).Elem()
97 | jsonItemField := jsonAttributeMap(outputValue.FieldByName("Item"), currentTracer)
98 | res.Metadata["Item"] = jsonItemField
99 | }
100 | }
101 |
102 | func handleDynamoDBDeleteItem(
103 | r *aws.Request,
104 | res *protocol.Resource,
105 | metadataOnly bool,
106 | currentTracer tracer.Tracer,
107 | ) {
108 | inputValue := reflect.ValueOf(r.Params).Elem()
109 | jsonKeyField := jsonAttributeMap(inputValue.FieldByName("Key"), currentTracer)
110 | res.Metadata["Key"] = jsonKeyField
111 | }
112 |
113 | func handleDynamoDBUpdateItem(
114 | r *aws.Request,
115 | res *protocol.Resource,
116 | metadataOnly bool,
117 | currentTracer tracer.Tracer,
118 | ) {
119 | inputValue := reflect.ValueOf(r.Params).Elem()
120 | eavField := inputValue.FieldByName("ExpressionAttributeValues")
121 | eav := deserializeAttributeMap(eavField)
122 | eavStream, err := json.Marshal(eav)
123 | if err != nil {
124 | return
125 | }
126 | updateParameters := map[string]string{
127 | "Expression Attribute Values": string(eavStream),
128 | }
129 | jsonKeyField := jsonAttributeMap(inputValue.FieldByName("Key"), currentTracer)
130 | updateParameters["Key"] = jsonKeyField
131 | updateMetadataFromValue(inputValue,
132 | "UpdateExpression", "UpdateExpression", updateParameters)
133 | updateParamsStream, err := json.Marshal(updateParameters)
134 | if err != nil {
135 | return
136 | }
137 | res.Metadata["Update Parameters"] = string(updateParamsStream)
138 | }
139 |
140 | func deserializeItems(itemsField reflect.Value, currentTracer tracer.Tracer) string {
141 | if itemsField == (reflect.Value{}) {
142 | return ""
143 | }
144 | formattedItems := make([]map[string]string, itemsField.Len())
145 | for ind := 0; ind < itemsField.Len(); ind++ {
146 | formattedItems = append(formattedItems,
147 | deserializeAttributeMap(itemsField.Index(ind)))
148 | }
149 | formattedItemsStream, err := json.Marshal(formattedItems)
150 | if err != nil {
151 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go",
152 | fmt.Sprintf("sederializeItems: %v", err))
153 | }
154 | return string(formattedItemsStream)
155 | }
156 |
157 | func handleDynamoDBScan(
158 | r *aws.Request,
159 | res *protocol.Resource,
160 | metadataOnly bool,
161 | currentTracer tracer.Tracer,
162 | ) {
163 | outputValue := reflect.ValueOf(r.Params).Elem()
164 | updateMetadataFromInt64(outputValue, "Count", "Items Count", res.Metadata)
165 | updateMetadataFromInt64(outputValue, "ScannedCount", "Scanned Items Count", res.Metadata)
166 | itemsField := outputValue.FieldByName("Items")
167 | if !metadataOnly {
168 | res.Metadata["Items"] = deserializeItems(itemsField, currentTracer)
169 | }
170 | }
171 |
172 | func handleDynamoDBBatchWriteItem(
173 | r *aws.Request,
174 | res *protocol.Resource,
175 | metadataOnly bool,
176 | currentTracer tracer.Tracer,
177 | ) {
178 | inputValue := reflect.ValueOf(r.Params).Elem()
179 | requestItemsField := inputValue.FieldByName("RequestItems")
180 | if requestItemsField != (reflect.Value{}) {
181 | var tableName string
182 | requestItems, ok := requestItemsField.Interface().(map[string][]*dynamodb.WriteRequest)
183 | if !ok {
184 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go",
185 | "handleDynamoDBBatchWriteItem: Failed to cast RequestItems")
186 | return
187 | }
188 | for k := range requestItems {
189 | tableName = k
190 | break
191 | }
192 | res.Name = tableName
193 | // TODO not ignore other tables
194 | if !metadataOnly {
195 | items := make([]map[string]dynamodb.AttributeValue, len(requestItems))
196 | for _, writeRequest := range requestItems[tableName] {
197 | items = append(items, writeRequest.PutRequest.Item)
198 | }
199 | itemsValue := reflect.ValueOf(items)
200 | res.Metadata["Items"] = deserializeItems(itemsValue, currentTracer)
201 | }
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/epsagon/aws_sdk_v2_factories/s3_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawsv2factories
2 |
3 | import (
4 | "fmt"
5 | "github.com/aws/aws-sdk-go-v2/aws"
6 | "github.com/epsagon/epsagon-go/protocol"
7 | "github.com/epsagon/epsagon-go/tracer"
8 | "reflect"
9 | "strings"
10 | "time"
11 | )
12 |
13 | // S3EventDataFactory creats an Epsagon Resource from aws.Request to S3
14 | func S3EventDataFactory(
15 | r *aws.Request,
16 | res *protocol.Resource,
17 | metadataOnly bool,
18 | currentTracer tracer.Tracer,
19 | ) {
20 | inputValue := reflect.ValueOf(r.Params).Elem()
21 | getResourceNameFromField(res, inputValue, "Bucket")
22 | handleSpecificOperations := map[string]specificOperationHandler{
23 | "HeadObject": handleS3GetOrHeadObject,
24 | "GetObject": handleS3GetOrHeadObject,
25 | "PutObject": handleS3PutObject,
26 | "ListObjects": handleS3ListObject,
27 | }
28 | handleSpecificOperation(r, res, metadataOnly, handleSpecificOperations, nil, currentTracer)
29 | }
30 |
31 | func commonS3OpertionHandler(r *aws.Request, res *protocol.Resource, metadataOnly bool) {
32 | inputValue := reflect.ValueOf(r.Params).Elem()
33 | updateMetadataFromValue(inputValue, "Key", "key", res.Metadata)
34 | outputValue := reflect.ValueOf(r.Data).Elem()
35 | etag, ok := getFieldStringPtr(outputValue, "ETag")
36 | if ok {
37 | etag = strings.Trim(etag, "\"")
38 | res.Metadata["etag"] = etag
39 | }
40 | }
41 |
42 | func handleS3GetOrHeadObject(
43 | r *aws.Request,
44 | res *protocol.Resource,
45 | metadataOnly bool,
46 | _ tracer.Tracer,
47 | ) {
48 | commonS3OpertionHandler(r, res, metadataOnly)
49 | outputValue := reflect.ValueOf(r.Data).Elem()
50 | updateMetadataFromValue(outputValue, "ContentLength", "file_size", res.Metadata)
51 |
52 | lastModifiedField := outputValue.FieldByName("LastModified")
53 | if lastModifiedField == (reflect.Value{}) {
54 | return
55 | }
56 | lastModified := lastModifiedField.Elem().Interface().(time.Time)
57 | res.Metadata["last_modified"] = lastModified.String()
58 | }
59 |
60 | func handleS3PutObject(
61 | r *aws.Request,
62 | res *protocol.Resource,
63 | metadataOnly bool,
64 | _ tracer.Tracer,
65 | ) {
66 | commonS3OpertionHandler(r, res, metadataOnly)
67 | }
68 |
69 | type s3File struct {
70 | key string
71 | size int64
72 | etag string
73 | }
74 |
75 | func handleS3ListObject(
76 | r *aws.Request,
77 | res *protocol.Resource,
78 | metadataOnly bool,
79 | _ tracer.Tracer,
80 | ) {
81 | if metadataOnly {
82 | return
83 | }
84 |
85 | outputValue := reflect.ValueOf(r.Data).Elem()
86 | contentsField := outputValue.FieldByName("Contents")
87 | if contentsField == (reflect.Value{}) {
88 | return
89 | }
90 | length := contentsField.Len()
91 | files := make([]s3File, length)
92 | for i := 0; i < length; i++ {
93 | var key, etag string
94 | var size int64
95 | fileObject := contentsField.Index(i).Elem()
96 | etag = fileObject.FieldByName("ETag").Elem().String()
97 | key = fileObject.FieldByName("Key").Elem().String()
98 | size = fileObject.FieldByName("Size").Elem().Int()
99 |
100 | files = append(files, s3File{key, size, etag})
101 | }
102 | res.Metadata["files"] = fmt.Sprintf("%+v", files)
103 | }
104 |
--------------------------------------------------------------------------------
/epsagon/aws_sdk_v2_factories/sts_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawsv2factories
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go-v2/aws"
5 | "github.com/epsagon/epsagon-go/protocol"
6 | "github.com/epsagon/epsagon-go/tracer"
7 | "reflect"
8 | )
9 |
10 | // STSEventDataFactory to create epsagon Resource from aws.Request to STS
11 | func StsDataFactory(
12 | r *aws.Request,
13 | res *protocol.Resource,
14 | metadataOnly bool,
15 | currentTracer tracer.Tracer,
16 | ) {
17 | handleSpecificOperations := map[string]specificOperationHandler{
18 | "GetCallerIdentity": handleStsGetCallerIdentityRequest,
19 | }
20 | handleSpecificOperation(r, res, metadataOnly, handleSpecificOperations, nil, currentTracer)
21 | }
22 |
23 | func handleStsGetCallerIdentityRequest(
24 | r *aws.Request,
25 | res *protocol.Resource,
26 | metadataOnly bool,
27 | _ tracer.Tracer,
28 | ) {
29 | if !metadataOnly {
30 | outputValue := reflect.ValueOf(r.Data).Elem()
31 | for _, key := range []string{"Account", "Arn", "UserId"} {
32 | updateMetadataField(outputValue, key, res)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/epsagon/aws_v2_wrapper.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/aws/aws-sdk-go-v2/aws"
8 | "github.com/epsagon/epsagon-go/epsagon/aws_sdk_v2_factories"
9 | "github.com/epsagon/epsagon-go/protocol"
10 | "github.com/epsagon/epsagon-go/tracer"
11 | "log"
12 | "reflect"
13 | "time"
14 | )
15 |
16 | // WrapAwsV2Service wrap aws service with epsgon
17 | // svc := epsagon.WrapAwsV2Service(dynamodb.New(cfg)).(*dynamodb.Client)
18 | func WrapAwsV2Service(svcClient interface{}, args ...context.Context) interface{} {
19 | awsClient := reflect.ValueOf(svcClient).Elem().FieldByName("Client").Interface().(*aws.Client)
20 | awsClient.Handlers.Complete.PushFrontNamed(
21 | aws.NamedHandler{
22 | Name: "epsagon-aws-sdk-v2",
23 | Fn: func(r *aws.Request) {
24 | currentTracer := ExtractTracer(args)
25 | completeEventData(r, currentTracer)
26 | },
27 | },
28 | )
29 | return svcClient
30 | }
31 |
32 | func getTimeStampFromRequest(r *aws.Request) float64 {
33 | return float64(r.Time.UTC().UnixNano()) / float64(time.Millisecond) / float64(time.Nanosecond) / 1000.0
34 | }
35 |
36 | func completeEventData(r *aws.Request, currentTracer tracer.Tracer) {
37 | defer GeneralEpsagonRecover("aws-sdk-go wrapper", "", currentTracer)
38 | if currentTracer.GetConfig().Debug {
39 | log.Printf("EPSAGON DEBUG OnComplete current tracer: %+v\n", currentTracer)
40 | log.Printf("EPSAGON DEBUG OnComplete request response: %+v\n", r.HTTPResponse)
41 | log.Printf("EPSAGON DEBUG OnComplete request Operation: %+v\n", r.Operation)
42 | log.Printf("EPSAGON DEBUG OnComplete request Endpoint: %+v\n", r.Endpoint)
43 | log.Printf("EPSAGON DEBUG OnComplete request Params: %+v\n", r.Params)
44 | log.Printf("EPSAGON DEBUG OnComplete request Data: %+v\n", r.Data)
45 | }
46 |
47 | endTime := tracer.GetTimestamp()
48 | event := protocol.Event{
49 | Id: r.RequestID,
50 | StartTime: getTimeStampFromRequest(r),
51 | Origin: "aws-sdk",
52 | Resource: extractResourceInformation(r, currentTracer),
53 | }
54 | event.Duration = endTime - event.StartTime
55 | currentTracer.AddEvent(&event)
56 | }
57 |
58 | type factory func(*aws.Request, *protocol.Resource, bool, tracer.Tracer)
59 |
60 | var awsResourceEventFactories = map[string]factory{
61 | "s3": epsagonawsv2factories.S3EventDataFactory,
62 | "dynamodb": epsagonawsv2factories.DynamodbEventDataFactory,
63 | "sts": epsagonawsv2factories.StsDataFactory,
64 | }
65 |
66 | func extractResourceInformation(r *aws.Request, currentTracer tracer.Tracer) *protocol.Resource {
67 | res := protocol.Resource{
68 | Type: r.Endpoint.SigningName,
69 | Operation: r.Operation.Name,
70 | Metadata: make(map[string]string),
71 | }
72 | factory := awsResourceEventFactories[res.Type]
73 | if factory != nil {
74 | factory(r, &res, currentTracer.GetConfig().MetadataOnly, currentTracer)
75 | } else {
76 | defaultFactory(r, &res, currentTracer.GetConfig().MetadataOnly, currentTracer)
77 | }
78 | return &res
79 | }
80 |
81 | func defaultFactory(r *aws.Request, res *protocol.Resource, metadataOnly bool, currentTracer tracer.Tracer) {
82 | if currentTracer.GetConfig().Debug {
83 | log.Println("EPSAGON DEBUG:: entering defaultFactory")
84 | }
85 | if !metadataOnly {
86 | extractInterfaceToMetadata(r.Data, res)
87 | extractInterfaceToMetadata(r.Params, res)
88 | }
89 | }
90 |
91 | func extractInterfaceToMetadata(input interface{}, res *protocol.Resource) {
92 | var data map[string]interface{}
93 | rawJSON, err := json.Marshal(input)
94 | if err != nil {
95 | log.Printf("EPSAGON DEBUG: Failed to marshal input: %+v\n", input)
96 | return
97 | }
98 | err = json.Unmarshal(rawJSON, &data)
99 | if err != nil {
100 | log.Printf("EPSAGON DEBUG: Failed to unmarshal input: %+v\n", rawJSON)
101 | return
102 | }
103 | for key, value := range data {
104 | res.Metadata[key] = fmt.Sprintf("%v", value)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/epsagon/aws_v2_wrapper_factories_test.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go-v2/aws"
5 | epsagonawsv2factories "github.com/epsagon/epsagon-go/epsagon/aws_sdk_v2_factories"
6 | "github.com/epsagon/epsagon-go/protocol"
7 | "github.com/epsagon/epsagon-go/tracer"
8 | . "github.com/onsi/ginkgo"
9 | . "github.com/onsi/gomega"
10 | )
11 |
12 | const TEST_ACCOUNT = "test_account"
13 | const TEST_USER_ID = "test_user_id"
14 | const TEST_ARN = "test_arn"
15 |
16 | type CallerIdentityMock struct {
17 | Account *string
18 | Arn *string
19 | UserId *string
20 | }
21 |
22 | var _ = Describe("aws_sdk_v2_factories", func() {
23 | Describe("sts_factory", func() {
24 | Context("Happy Flows", func() {
25 | var (
26 | request *aws.Request
27 | resource *protocol.Resource
28 | )
29 | BeforeEach(func() {
30 | request = &aws.Request{}
31 | resource = &protocol.Resource{
32 | Metadata: map[string]string{},
33 | Operation: "GetCallerIdentity",
34 | }
35 | })
36 | It("Metadata Only is false, partial data", func() {
37 | account_data := TEST_ACCOUNT
38 | request.Data = &CallerIdentityMock{
39 | Account: &account_data,
40 | }
41 | epsagonawsv2factories.StsDataFactory(request, resource, false, tracer.GlobalTracer)
42 | Expect(resource.Metadata["Account"]).To(Equal(TEST_ACCOUNT))
43 | })
44 | It("Metadata Only is false, full data", func() {
45 | account_data := TEST_ACCOUNT
46 | user_id_data := TEST_USER_ID
47 | arn_data := TEST_ARN
48 | request.Data = &CallerIdentityMock{
49 | Account: &account_data,
50 | Arn: &arn_data,
51 | UserId: &user_id_data,
52 | }
53 | epsagonawsv2factories.StsDataFactory(request, resource, false, tracer.GlobalTracer)
54 | Expect(len(resource.Metadata)).To(Equal(3))
55 | Expect(resource.Metadata["Account"]).To(Equal(TEST_ACCOUNT))
56 | Expect(resource.Metadata["Arn"]).To(Equal(TEST_ARN))
57 | Expect(resource.Metadata["UserId"]).To(Equal(TEST_USER_ID))
58 | })
59 | It("Metadata Only is true", func() {
60 | account_data := TEST_ACCOUNT
61 | user_id_data := TEST_USER_ID
62 | arn_data := TEST_ARN
63 | request.Data = &CallerIdentityMock{
64 | Account: &account_data,
65 | Arn: &arn_data,
66 | UserId: &user_id_data,
67 | }
68 | epsagonawsv2factories.StsDataFactory(request, resource, true, tracer.GlobalTracer)
69 | Expect(len(resource.Metadata)).To(Equal(0))
70 | })
71 | })
72 | })
73 | })
74 |
--------------------------------------------------------------------------------
/epsagon/common_utils.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "log"
11 | "net/http"
12 | "reflect"
13 | "strings"
14 |
15 | "github.com/epsagon/epsagon-go/tracer"
16 | "github.com/onsi/gomega/types"
17 | )
18 |
19 | // DefaultErrorType Default custom error type
20 | const DefaultErrorType = "Error"
21 |
22 | // MaxMetadataSize Maximum size of event metadata
23 | const MaxMetadataSize = 10 * 1024
24 |
25 | // HTTP Content types to ignore
26 | var ignoredContentTypes = [...]string{
27 | "image",
28 | "audio",
29 | "video",
30 | "font",
31 | "zip",
32 | "css",
33 | }
34 |
35 | // HTTP request file types to ignore
36 | var ignoredFileTypes = [...]string{
37 | ".js",
38 | ".jsx",
39 | ".woff",
40 | ".woff2",
41 | ".ttf",
42 | ".eot",
43 | ".ico",
44 | }
45 |
46 | // Config is the configuration for Epsagon's tracer
47 | type Config struct {
48 | tracer.Config
49 | }
50 |
51 | // GeneralEpsagonRecover recover function that will send exception to epsagon
52 | // exceptionType, msg are strings that will be added to the exception
53 | func GeneralEpsagonRecover(exceptionType, msg string, currentTracer tracer.Tracer) {
54 | if r := recover(); r != nil && currentTracer != nil {
55 | currentTracer.AddExceptionTypeAndMessage(exceptionType, fmt.Sprintf("%s:%+v", msg, r))
56 | }
57 | }
58 |
59 | // NewTracerConfig creates a new tracer Config
60 | func NewTracerConfig(applicationName, token string) *Config {
61 | return &Config{
62 | Config: tracer.Config{
63 | ApplicationName: applicationName,
64 | Token: token,
65 | MetadataOnly: true,
66 | Debug: false,
67 | SendTimeout: "1s",
68 | MaxTraceSize: tracer.DefaultMaxTraceSize,
69 | },
70 | }
71 | }
72 |
73 | // Label adds a label to the sent trace
74 | func Label(key string, value interface{}, args ...context.Context) {
75 | currentTracer := ExtractTracer(args)
76 | if currentTracer != nil {
77 | currentTracer.AddLabel(key, value)
78 | }
79 | }
80 |
81 | // Error adds an error to the sent trace
82 | func Error(value interface{}, args ...context.Context) {
83 | currentTracer := ExtractTracer(args)
84 | if currentTracer != nil {
85 | currentTracer.AddError(DefaultErrorType, value)
86 | }
87 | }
88 |
89 | // TypeError adds an error to the sent trace with specific error type
90 | func TypeError(value interface{}, errorType string, args ...context.Context) {
91 | currentTracer := ExtractTracer(args)
92 | if currentTracer != nil {
93 | currentTracer.AddError(errorType, value)
94 | }
95 | }
96 |
97 | // FormatHeaders format HTTP headers to string - using first header value, ignoring the rest
98 | func FormatHeaders(headers http.Header) (string, error) {
99 | headersToFormat := make(map[string]string)
100 | for headerKey, headerValues := range headers {
101 | if len(headerValues) > 0 {
102 | headersToFormat[headerKey] = headerValues[0]
103 | }
104 | }
105 | headersJSON, err := json.Marshal(headersToFormat)
106 | if err != nil {
107 | return "", err
108 | }
109 | return string(headersJSON), nil
110 | }
111 |
112 | // ExtractRequestData extracts headers and body from http.Request
113 | func ExtractRequestData(req *http.Request) (headers string, body string) {
114 | headers, err := FormatHeaders(req.Header)
115 | if err != nil {
116 | headers = ""
117 | }
118 |
119 | if req.Body == nil {
120 | return
121 | }
122 |
123 | buf, err := ioutil.ReadAll(req.Body)
124 | req.Body = NewReadCloser(buf, err)
125 | if err != nil {
126 | return
127 | }
128 | // truncates request body to the first 64KB
129 | trimmed := buf
130 | if len(buf) > MaxMetadataSize {
131 | trimmed = buf[0:MaxMetadataSize]
132 | }
133 | body = string(trimmed)
134 | return
135 | }
136 |
137 | // ShouldIgnoreRequest checks whether HTTP request should be ignored according
138 | // to given content type and request path
139 | func ShouldIgnoreRequest(contentType string, path string) bool {
140 | if len(contentType) > 0 {
141 | for _, ignoredContentType := range ignoredContentTypes {
142 | if strings.Contains(contentType, ignoredContentType) {
143 | return true
144 | }
145 | }
146 | }
147 | if len(path) > 0 {
148 | for _, ignoredFileSuffix := range ignoredFileTypes {
149 | if strings.HasSuffix(path, ignoredFileSuffix) {
150 | return true
151 | }
152 | }
153 | }
154 | return false
155 | }
156 |
157 | // NewReadCloser returns an io.ReadCloser
158 | // will mimick read from body depending on given error
159 | func NewReadCloser(body []byte, err error) io.ReadCloser {
160 | if err != nil {
161 | return &errorReader{err: err}
162 | }
163 | return ioutil.NopCloser(bytes.NewReader(body))
164 | }
165 |
166 | // DebugLog logs helpful debugging messages
167 |
168 | func DebugLog(debugMode bool, args ...interface{}) {
169 | if debugMode {
170 | log.Println("[EPSAGON]", args)
171 | }
172 | }
173 |
174 | type errorReader struct {
175 | err error
176 | }
177 |
178 | func (er *errorReader) Read([]byte) (int, error) {
179 | return 0, er.err
180 | }
181 | func (er *errorReader) Close() error {
182 | return er.err
183 | }
184 |
185 | type matchUserError struct {
186 | exception interface{}
187 | }
188 |
189 | func (matcher *matchUserError) Match(actual interface{}) (bool, error) {
190 | uErr, ok := actual.(userError)
191 | if !ok {
192 | return false, fmt.Errorf("excpects userError, got %v", actual)
193 | }
194 | if !reflect.DeepEqual(uErr.exception, matcher.exception) {
195 | return false, fmt.Errorf("expected\n\t%v\nexception, got\n\t%v", matcher.exception, uErr.exception)
196 | }
197 |
198 | return true, nil
199 | }
200 |
201 | func (matcher *matchUserError) FailureMessage(actual interface{}) string {
202 | return fmt.Sprintf("Expected\n\t%#v\nto be userError with exception\n\t%#v", actual, matcher.exception)
203 | }
204 |
205 | func (matcher *matchUserError) NegatedFailureMessage(actual interface{}) string {
206 | return fmt.Sprintf("NegatedFailureMessage")
207 | }
208 |
209 | // MatchUserError matches epsagon exceptions
210 | func MatchUserError(exception interface{}) types.GomegaMatcher {
211 | return &matchUserError{
212 | exception: exception,
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/epsagon/concurrent_generic_wrapper_test.go:
--------------------------------------------------------------------------------
1 | package epsagon_test
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "io/ioutil"
8 | "math/rand"
9 | "net/http"
10 | "net/http/httptest"
11 | "net/url"
12 | "strconv"
13 | "sync"
14 | "testing"
15 | "time"
16 |
17 | "github.com/epsagon/epsagon-go/epsagon"
18 | "github.com/epsagon/epsagon-go/protocol"
19 | "github.com/epsagon/epsagon-go/tracer"
20 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
21 | . "github.com/onsi/ginkgo"
22 | . "github.com/onsi/gomega"
23 | )
24 |
25 | const RunnerLabelKey = "test_identifier"
26 |
27 | func TestEpsagonConcurrentWrapper(t *testing.T) {
28 | RegisterFailHandler(Fail)
29 | RunSpecs(t, "Multiple Traces")
30 | }
31 |
32 | func sendRequest(wg *sync.WaitGroup, path string, testServer *httptest.Server) {
33 | defer wg.Done()
34 | time.Sleep(time.Duration(rand.Intn(500)) * time.Microsecond)
35 | client := http.Client{}
36 | response, err := client.Get(testServer.URL + path)
37 | Expect(err).To(BeNil())
38 | defer response.Body.Close()
39 | responseData, err := ioutil.ReadAll(response.Body)
40 | Expect(err).To(BeNil())
41 | responseString := string(responseData)
42 | Expect(responseString).To(Equal(path))
43 | }
44 |
45 | func convertRequestUriToInt(requestUri string) (identifier int) {
46 | identifier, err := strconv.Atoi(requestUri[1:])
47 | if err != nil {
48 | panic("failed to parse request uri - bad trace")
49 | }
50 | return
51 | }
52 |
53 | func parseEventID(event *protocol.Event) (identifier int) {
54 | resourceName := event.Resource.GetName()
55 | resourceURL, err := url.Parse(resourceName)
56 | if err != nil {
57 | panic("failed to parse event URL - bad trace")
58 | }
59 | identifier = convertRequestUriToInt(resourceURL.RequestURI())
60 | return
61 | }
62 |
63 | func validateAgainstRunnerEvent(runnerEvent *protocol.Event, identifier int) {
64 | labels, ok := runnerEvent.Resource.Metadata[tracer.LabelsKey]
65 | if !ok {
66 | panic("no labels in runner event!")
67 | }
68 | var labelsMap map[string]interface{}
69 | err := json.Unmarshal([]byte(labels), &labelsMap)
70 | if err != nil {
71 | panic("bad labels map in runner event")
72 | }
73 | labelValue, ok := labelsMap[RunnerLabelKey]
74 | if !ok {
75 | panic("no identifier in runner event labels")
76 | }
77 | runnerIdentifier := convertRequestUriToInt(labelValue.(string))
78 | Expect(runnerIdentifier).To(Equal(identifier))
79 | }
80 |
81 | func waitForTraces(start int, end int, traceChannel chan *protocol.Trace, resourceName string, wg *sync.WaitGroup) {
82 | defer wg.Done()
83 | var trace *protocol.Trace
84 | receivedTraces := map[int]bool{}
85 | for i := start; i < end; i++ {
86 | receivedTraces[i] = false
87 | }
88 | ticker := time.NewTicker(8 * time.Second)
89 | for len(receivedTraces) > 0 {
90 | select {
91 | case trace = <-traceChannel:
92 | func() {
93 | Expect(len(trace.Events)).To(Equal(2))
94 | if len(resourceName) > 0 {
95 | Expect(trace.Events[1].Resource.Name).To(Equal(resourceName))
96 | }
97 | identifier := parseEventID(trace.Events[0])
98 | if identifier < start || identifier >= end {
99 | panic("received unexpected event")
100 | }
101 | _, exists := receivedTraces[identifier]
102 | if !exists {
103 | panic("received duplicated event")
104 | }
105 | validateAgainstRunnerEvent(trace.Events[1], identifier)
106 | delete(receivedTraces, identifier)
107 | }()
108 | case <-ticker.C:
109 | panic("timeout while receiving traces")
110 | }
111 | }
112 | }
113 |
114 | type HandlerFunc func(res http.ResponseWriter, req *http.Request)
115 |
116 | func handleResponse(ctx context.Context, res http.ResponseWriter, req *http.Request) {
117 | client := http.Client{Transport: epsagonhttp.NewTracingTransport(ctx)}
118 | client.Get(fmt.Sprintf("https://www.google.com%s", req.RequestURI))
119 | epsagon.Label(RunnerLabelKey, req.RequestURI, ctx)
120 | res.Write([]byte(req.RequestURI))
121 | }
122 |
123 | func createTestHTTPServer(config *epsagon.Config, resourceName string) *httptest.Server {
124 | var concurrentWrapper epsagon.GenericFunction
125 | if len(resourceName) > 0 {
126 | concurrentWrapper = epsagon.ConcurrentGoWrapper(
127 | config,
128 | handleResponse,
129 | resourceName,
130 | )
131 | } else {
132 | concurrentWrapper = epsagon.ConcurrentGoWrapper(
133 | config,
134 | handleResponse,
135 | )
136 | }
137 | return httptest.NewServer(http.HandlerFunc(
138 | func(res http.ResponseWriter, req *http.Request) {
139 | concurrentWrapper(res, req)
140 | },
141 | ))
142 | }
143 |
144 | var _ = Describe("multiple_traces", func() {
145 | Describe("http_server_tests", func() {
146 | Context("Happy Flows", func() {
147 | var (
148 | traceCollectorServer *httptest.Server
149 | testServer *httptest.Server
150 | config *epsagon.Config
151 | traceChannel chan *protocol.Trace
152 | )
153 | BeforeEach(func() {
154 | traceChannel = make(chan *protocol.Trace)
155 | traceCollectorServer = httptest.NewServer(http.HandlerFunc(
156 | func(res http.ResponseWriter, req *http.Request) {
157 | buf, err := ioutil.ReadAll(req.Body)
158 | if err != nil {
159 | panic(err)
160 | }
161 | var receivedTrace protocol.Trace
162 | err = json.Unmarshal(buf, &receivedTrace)
163 | if err != nil {
164 | panic(err)
165 | }
166 | traceChannel <- &receivedTrace
167 | res.Write([]byte(""))
168 | },
169 | ))
170 | config = epsagon.NewTracerConfig("test", "test token")
171 | config.CollectorURL = traceCollectorServer.URL
172 | })
173 | AfterEach(func() {
174 | testServer.Close()
175 | traceCollectorServer.Close()
176 | })
177 | It("Multiple requests to test server", func() {
178 | resourceName := ""
179 | testServer = createTestHTTPServer(config, "")
180 | var wg sync.WaitGroup
181 | go waitForTraces(0, 50, traceChannel, resourceName, &wg)
182 | wg.Add(1)
183 | for i := 0; i < 50; i++ {
184 | wg.Add(1)
185 | go sendRequest(&wg, fmt.Sprintf("/%d", i), testServer)
186 |
187 | }
188 | wg.Wait()
189 | go waitForTraces(51, 100, traceChannel, resourceName, &wg)
190 | wg.Add(1)
191 | for i := 51; i < 100; i++ {
192 | wg.Add(1)
193 | go sendRequest(&wg, fmt.Sprintf("/%d", i), testServer)
194 | }
195 | wg.Wait()
196 | })
197 | It("Custom runner resource name", func() {
198 | resourceName := "test-resource-name"
199 | testServer = createTestHTTPServer(config, resourceName)
200 | var wg sync.WaitGroup
201 | go waitForTraces(0, 1, traceChannel, resourceName, &wg)
202 | wg.Add(1)
203 | for i := 0; i < 1; i++ {
204 | wg.Add(1)
205 | go sendRequest(&wg, fmt.Sprintf("/%d", i), testServer)
206 |
207 | }
208 | wg.Wait()
209 | })
210 |
211 | })
212 | })
213 | })
214 |
--------------------------------------------------------------------------------
/epsagon/generic_lambda_handler.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/epsagon/epsagon-go/protocol"
8 | "github.com/epsagon/epsagon-go/tracer"
9 | "reflect"
10 | )
11 |
12 | func errorHandler(e error) genericLambdaHandler {
13 | return func(ctx context.Context, payload json.RawMessage) (interface{}, error) {
14 | tracer.AddException(&protocol.Exception{
15 | Type: "wrapper",
16 | Message: fmt.Sprintf("Error in wrapper: %v", e),
17 | Time: tracer.GetTimestamp(),
18 | })
19 | return nil, e
20 | }
21 | }
22 |
23 | // validateArguments returns an error if the handler's arguments are
24 | // not compatible with aws lambda handlers
25 | // the boolean return value is wether or not the handler accepts context.Context
26 | // in its first argument.
27 | func validateArguments(handler reflect.Type) (bool, error) {
28 | handlerTakesContext := false
29 | if handler.NumIn() > 2 {
30 | return false, fmt.Errorf("handlers may not take more than two arguments, but handler takes %d", handler.NumIn())
31 | } else if handler.NumIn() > 0 {
32 | contextType := reflect.TypeOf((*context.Context)(nil)).Elem()
33 | argumentType := handler.In(0)
34 | handlerTakesContext = argumentType.Implements(contextType)
35 | if handler.NumIn() > 1 && !handlerTakesContext {
36 | return false, fmt.Errorf("handler takes two arguments, but the first is not Context. got %s", argumentType.Kind())
37 | }
38 | }
39 |
40 | return handlerTakesContext, nil
41 | }
42 |
43 | func validateReturns(handler reflect.Type) error {
44 | errorType := reflect.TypeOf((*error)(nil)).Elem()
45 | if handler.NumOut() > 2 {
46 | return fmt.Errorf("handler may not return more than two values")
47 | } else if handler.NumOut() > 1 {
48 | if !handler.Out(1).Implements(errorType) {
49 | return fmt.Errorf("handler returns two values, but the second does not implement error")
50 | }
51 | } else if handler.NumOut() == 1 {
52 | if !handler.Out(0).Implements(errorType) {
53 | return fmt.Errorf("handler returns a single value, but it does not implement error")
54 | }
55 | }
56 | return nil
57 | }
58 |
59 | func makeGenericHandler(handlerSymbol interface{}) genericLambdaHandler {
60 | if handlerSymbol == nil {
61 | return errorHandler(fmt.Errorf("handler is nil"))
62 | }
63 | handler := reflect.ValueOf(handlerSymbol)
64 | handlerType := reflect.TypeOf(handlerSymbol)
65 | if handlerType.Kind() != reflect.Func {
66 | return errorHandler(fmt.Errorf("handler kind %s is not %s", handlerType.Kind(), reflect.Func))
67 | }
68 |
69 | takesContext, err := validateArguments(handlerType)
70 | if err != nil {
71 | return errorHandler(err)
72 | }
73 |
74 | if err := validateReturns(handlerType); err != nil {
75 | return errorHandler(err)
76 | }
77 |
78 | return func(ctx context.Context, payload json.RawMessage) (interface{}, error) {
79 | // construct arguments
80 | var args []reflect.Value
81 | if takesContext {
82 | args = append(args, reflect.ValueOf(ctx))
83 | }
84 | if (handlerType.NumIn() == 1 && !takesContext) || handlerType.NumIn() == 2 {
85 | argType := handlerType.In(handlerType.NumIn() - 1)
86 | arg := reflect.New(argType)
87 |
88 | if err := json.Unmarshal(payload, arg.Interface()); err != nil {
89 | tracer.AddException(&protocol.Exception{
90 | Type: "wrapper",
91 | Message: fmt.Sprintf("Error in wrapper: failed to convert arguments: %v", err),
92 | Time: tracer.GetTimestamp(),
93 | })
94 | return nil, err
95 | }
96 |
97 | args = append(args, arg.Elem())
98 | }
99 |
100 | response := handler.Call(args)
101 |
102 | // convert return values into (interface{}, error)
103 | var err error
104 | if len(response) > 0 {
105 | if errVal, ok := response[len(response)-1].Interface().(error); ok {
106 | err = errVal
107 | }
108 | }
109 | var val interface{}
110 | if len(response) > 1 {
111 | val = response[0].Interface()
112 | }
113 |
114 | return val, err
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/epsagon/generic_lambda_handler_test.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | protocol "github.com/epsagon/epsagon-go/protocol"
8 | "github.com/epsagon/epsagon-go/tracer"
9 | . "github.com/onsi/ginkgo"
10 | . "github.com/onsi/gomega"
11 | "reflect"
12 | )
13 |
14 | var _ = Describe("GenericHandler suite", func() {
15 | Describe("validateArguments", func() {
16 | Context("Bad Handlers", func() {
17 | It("fails on too many arguments", func() {
18 | badHandler := func(ctx context.Context, a, b, c string) error {
19 | return nil
20 | }
21 | _, err := validateArguments(reflect.TypeOf(badHandler))
22 | Expect(err).NotTo(Equal(nil))
23 | })
24 | It("fails if two arguments but first is not context", func() {
25 | badHandler := func(a string, ctx context.Context) error {
26 | return nil
27 | }
28 | _, err := validateArguments(reflect.TypeOf(badHandler))
29 | Expect(err).NotTo(Equal(nil))
30 | })
31 | })
32 | Context("Happy Handlers", func() {
33 | It("accepts no arguments", func() {
34 | goodHandler := func() error {
35 | return nil
36 | }
37 | hasCtx, err := validateArguments(reflect.TypeOf(goodHandler))
38 | Expect(err).To(BeNil())
39 | Expect(hasCtx).To(Equal(false))
40 | })
41 | It("accepts one argument not context", func() {
42 | goodHandler := func(a string) error {
43 | return nil
44 | }
45 | hasCtx, err := validateArguments(reflect.TypeOf(goodHandler))
46 | Expect(err).To(BeNil())
47 | Expect(hasCtx).To(Equal(false))
48 | })
49 | It("accepts two arguments when the first is context", func() {
50 | goodHandler := func(ctx context.Context, a string) error {
51 | return nil
52 | }
53 | hasCtx, err := validateArguments(reflect.TypeOf(goodHandler))
54 | Expect(err).To(BeNil())
55 | Expect(hasCtx).To(Equal(true))
56 | })
57 | })
58 | })
59 | Describe("validateReturns", func() {
60 | Context("Bad Handlers", func() {
61 | It("fails on too many returns", func() {
62 | badHandler := func() (string, error, string) {
63 | return "", nil, ""
64 | }
65 | err := validateReturns(reflect.TypeOf(badHandler))
66 | Expect(err).NotTo(Equal(nil))
67 | })
68 | It("fails if last return is not error", func() {
69 | badHandler := func() (string, string) {
70 | return "", ""
71 | }
72 | err := validateReturns(reflect.TypeOf(badHandler))
73 | Expect(err).NotTo(Equal(nil))
74 | })
75 | It("fails if retuns one thing that is not error", func() {
76 | badHandler := func() string {
77 | return ""
78 | }
79 | err := validateReturns(reflect.TypeOf(badHandler))
80 | Expect(err).NotTo(Equal(nil))
81 | })
82 | })
83 | Context("Good Handlers", func() {
84 | It("succeeds if no returns", func() {
85 | goodHandler := func() {
86 | return
87 | }
88 | err := validateReturns(reflect.TypeOf(goodHandler))
89 | Expect(err).To(BeNil())
90 | })
91 | It("suceeds if only returns error", func() {
92 | goodHandler := func() error {
93 | return nil
94 | }
95 | err := validateReturns(reflect.TypeOf(goodHandler))
96 | Expect(err).To(BeNil())
97 | })
98 | It("succeeds if returns something and error", func() {
99 | goodHandler := func() (string, error) {
100 | return "", nil
101 | }
102 | err := validateReturns(reflect.TypeOf(goodHandler))
103 | Expect(err).To(BeNil())
104 | })
105 | })
106 | })
107 |
108 | Describe("makeGenericHandler", func() {
109 | var (
110 | events []*protocol.Event
111 | exceptions []*protocol.Exception
112 | )
113 | BeforeEach(func() {
114 | events = make([]*protocol.Event, 0)
115 | exceptions = make([]*protocol.Exception, 0)
116 | tracer.GlobalTracer = &tracer.MockedEpsagonTracer{
117 | Events: &events,
118 | Exceptions: &exceptions,
119 | }
120 | })
121 |
122 | Context("Bad Handlers", func() {
123 | It("fails if handlers return types are bad", func() {
124 | badHandler := func() (string, string) {
125 | return "", ""
126 | }
127 | generic := makeGenericHandler(badHandler)
128 | bg := context.Background()
129 | _, err := generic(bg, []byte{})
130 |
131 | Expect(err).NotTo(BeNil())
132 | Expect(len(exceptions)).To(BeNumerically("==", 1))
133 | })
134 | It("fails if handler is not a function", func() {
135 | badHandler := "not a function"
136 | generic := makeGenericHandler(badHandler)
137 | bg := context.Background()
138 | _, err := generic(bg, []byte{})
139 |
140 | Expect(err).NotTo(BeNil())
141 | Expect(len(exceptions)).To(BeNumerically("==", 1))
142 | })
143 | It("fails if handler is nil", func() {
144 | generic := makeGenericHandler(nil)
145 | bg := context.Background()
146 | _, err := generic(bg, []byte{})
147 |
148 | Expect(err).NotTo(BeNil())
149 | Expect(len(exceptions)).To(BeNumerically("==", 1))
150 | })
151 | It("fails if handlers arguments are bad", func() {
152 | badHandler := func(string, string) {}
153 | generic := makeGenericHandler(badHandler)
154 | bg := context.Background()
155 | _, err := generic(bg, []byte{})
156 |
157 | Expect(err).NotTo(BeNil())
158 | Expect(len(exceptions)).To(BeNumerically("==", 1))
159 | })
160 | })
161 | Context("Handler arguments are not json compatible to input", func() {
162 | type t1 struct {
163 | a1 string
164 | b1 string
165 | }
166 | type t2 struct {
167 | a2 int
168 | }
169 | It("fails when target type is not JSONable", func() {
170 | msg, err := json.Marshal(t1{a1: "hello", b1: "world"})
171 | if err != nil {
172 | Fail(fmt.Sprintf("Failed to marshal json %v", err))
173 | }
174 | type myFuncType func(int, int) error
175 | generic := makeGenericHandler(func(f myFuncType) { f(1, 2) })
176 | bg := context.Background()
177 | _, err = generic(bg, msg)
178 | Expect(err).NotTo(BeNil())
179 | Expect(len(exceptions)).To(BeNumerically("==", 1))
180 | })
181 | It("constructs available fields when not everything is available in json", func() {
182 | msg, err := json.Marshal(t1{b1: "world"})
183 | if err != nil {
184 | Fail(fmt.Sprintf("Failed to marshal json %v", err))
185 | }
186 | generic := makeGenericHandler(func(x t1) {})
187 | bg := context.Background()
188 | _, err = generic(bg, msg)
189 | Expect(err).To(BeNil())
190 | Expect(len(exceptions)).To(BeNumerically("==", 0))
191 | })
192 | })
193 | })
194 | })
195 |
--------------------------------------------------------------------------------
/epsagon/generic_wrapper.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "runtime"
7 | "runtime/debug"
8 |
9 | "github.com/epsagon/epsagon-go/protocol"
10 | "github.com/epsagon/epsagon-go/tracer"
11 | "github.com/google/uuid"
12 | )
13 |
14 | type userError struct {
15 | exception interface{}
16 | stack string
17 | }
18 |
19 | // GenericWrapper is a generic lambda function type
20 | type GenericWrapper struct {
21 | handler reflect.Value
22 | config *Config
23 | tracer tracer.Tracer
24 | runner *protocol.Event
25 | thrownError interface{}
26 | resourceName string
27 | invoked bool
28 | invoking bool
29 | dontAddRunner bool
30 | injectContext bool
31 | }
32 |
33 | // WrapGenericFunction return an epsagon wrapper for a generic function
34 | func WrapGenericFunction(
35 | handler interface{}, config *Config, tracer tracer.Tracer, injectContext bool, resourceName string) *GenericWrapper {
36 | return &GenericWrapper{
37 | config: config,
38 | handler: reflect.ValueOf(handler),
39 | tracer: tracer,
40 | injectContext: injectContext,
41 | resourceName: resourceName,
42 | }
43 | }
44 |
45 | // GetRunnerEvent returns the wrapper runner event
46 | func (wrapper *GenericWrapper) GetRunnerEvent() *protocol.Event {
47 | return wrapper.runner
48 | }
49 |
50 | // createRunner creates a runner event but does not add it to the tracer
51 | // the runner is saved for further manipulations at wrapper.runner
52 | func (wrapper *GenericWrapper) createRunner() {
53 | resourceName := wrapper.resourceName
54 | if len(resourceName) == 0 {
55 | resourceName = runtime.FuncForPC(wrapper.handler.Pointer()).Name()
56 | }
57 | wrapper.runner = &protocol.Event{
58 | Id: uuid.New().String(),
59 | Origin: "runner",
60 | StartTime: tracer.GetTimestamp(),
61 | Resource: &protocol.Resource{
62 | Name: resourceName,
63 | Type: "go-function",
64 | Operation: "invoke",
65 | Metadata: make(map[string]string),
66 | },
67 | ErrorCode: protocol.ErrorCode_OK,
68 | }
69 | }
70 |
71 | // For instances when you want to add event but can't risk exception
72 | func (wrapper *GenericWrapper) safeAddRunnerEvent() {
73 | defer func() {
74 | recover()
75 | }()
76 | wrapper.addRunnerEvent()
77 | }
78 |
79 | func (wrapper *GenericWrapper) addRunnerEvent() {
80 | if wrapper.dontAddRunner {
81 | return
82 | }
83 | endTime := tracer.GetTimestamp()
84 | wrapper.runner.Duration = endTime - wrapper.runner.StartTime
85 | wrapper.tracer.AddEvent(wrapper.runner)
86 | }
87 |
88 | // Change the arguments from interface{} to reflect.Value array
89 | func (wrapper *GenericWrapper) transformArguments(args ...interface{}) []reflect.Value {
90 | actualLength := len(args)
91 | if wrapper.injectContext {
92 | actualLength += 1
93 | }
94 | if wrapper.handler.Type().NumIn() != actualLength {
95 | msg := fmt.Sprintf(
96 | "Wrong number of args: %d, expected: %d",
97 | actualLength, wrapper.handler.Type().NumIn())
98 | wrapper.createRunner()
99 | wrapper.runner.Exception = &protocol.Exception{
100 | Type: "Runtime Error",
101 | Message: fmt.Sprintf("%v", msg),
102 | Time: tracer.GetTimestamp(),
103 | }
104 | wrapper.safeAddRunnerEvent()
105 | panic(msg)
106 | }
107 | // add new context to inputs
108 | inputs := make([]reflect.Value, actualLength)
109 | argsInputs := inputs
110 | if wrapper.injectContext {
111 | inputs[0] = reflect.ValueOf(ContextWithTracer(wrapper.tracer))
112 | argsInputs = argsInputs[1:]
113 | }
114 | for k, in := range args {
115 | argsInputs[k] = reflect.ValueOf(in)
116 | }
117 | return inputs
118 | }
119 |
120 | // Call the wrapped function
121 | func (wrapper *GenericWrapper) Call(args ...interface{}) (results []reflect.Value) {
122 | inputs := wrapper.transformArguments(args...)
123 | defer func() {
124 | wrapper.thrownError = recover()
125 | if wrapper.thrownError != nil {
126 | exception := &protocol.Exception{
127 | Type: "Runtime Error",
128 | Message: fmt.Sprintf("%v", wrapper.thrownError),
129 | Traceback: string(debug.Stack()),
130 | Time: tracer.GetTimestamp(),
131 | }
132 | if wrapper.invoking {
133 | wrapper.runner.Exception = exception
134 | wrapper.runner.ErrorCode = protocol.ErrorCode_EXCEPTION
135 | wrapper.safeAddRunnerEvent()
136 | panic(userError{
137 | exception: wrapper.thrownError,
138 | stack: exception.Traceback,
139 | })
140 | } else {
141 | exception.Type = "GenericEpsagonWrapper"
142 | wrapper.tracer.AddException(exception)
143 | if !wrapper.invoked { // attempt to run the user's function untraced
144 | results = wrapper.handler.Call(inputs)
145 | }
146 | }
147 | }
148 | }()
149 |
150 | wrapper.createRunner()
151 | wrapper.invoking = true
152 | wrapper.invoked = true
153 | results = wrapper.handler.Call(inputs)
154 | wrapper.invoking = false
155 | wrapper.addRunnerEvent()
156 | return results
157 | }
158 |
159 | // GenericFunction type
160 | type GenericFunction func(args ...interface{}) []reflect.Value
161 |
162 | func getResourceName(args []string) (resourceName string) {
163 | if len(args) > 0 {
164 | resourceName = args[0]
165 | }
166 | return
167 | }
168 |
169 | // GoWrapper wraps the function with epsagon's tracer
170 | func GoWrapper(config *Config, wrappedFunction interface{}, args ...string) GenericFunction {
171 | resourceName := getResourceName(args)
172 | return func(args ...interface{}) []reflect.Value {
173 | if config == nil {
174 | config = &Config{}
175 | }
176 | wrapperTracer := tracer.CreateGlobalTracer(&config.Config)
177 | wrapperTracer.Start()
178 | defer wrapperTracer.Stop()
179 |
180 | wrapper := &GenericWrapper{
181 | config: config,
182 | handler: reflect.ValueOf(wrappedFunction),
183 | tracer: wrapperTracer,
184 | resourceName: resourceName,
185 | }
186 | return wrapper.Call(args...)
187 | }
188 | }
189 |
190 | // ConcurrentGoWrapper wraps the function with epsagon's tracer
191 | func ConcurrentGoWrapper(config *Config, wrappedFunction interface{}, args ...string) GenericFunction {
192 | resourceName := getResourceName(args)
193 | return func(args ...interface{}) []reflect.Value {
194 | if config == nil {
195 | config = &Config{}
196 | }
197 | wrapperTracer := tracer.CreateTracer(&config.Config)
198 | wrapperTracer.Start()
199 | defer wrapperTracer.Stop()
200 |
201 | wrapper := &GenericWrapper{
202 | config: config,
203 | handler: reflect.ValueOf(wrappedFunction),
204 | tracer: wrapperTracer,
205 | injectContext: true,
206 | resourceName: resourceName,
207 | }
208 | return wrapper.Call(args...)
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/epsagon/generic_wrapper_test.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "reflect"
5 |
6 | "github.com/epsagon/epsagon-go/protocol"
7 | "github.com/epsagon/epsagon-go/tracer"
8 | . "github.com/onsi/ginkgo"
9 | . "github.com/onsi/gomega"
10 | )
11 |
12 | var _ = Describe("generic_wrapper", func() {
13 | Describe("GoWrapper", func() {
14 | Context("called with nil config", func() {
15 | It("Returns a valid function", func() {
16 | wrapper := GoWrapper(nil, func() {})
17 | wrapperType := reflect.TypeOf(wrapper)
18 | Expect(wrapperType.Kind()).To(Equal(reflect.Func))
19 | })
20 | })
21 | })
22 | Describe("GenericWrapper", func() {
23 | Context("Happy Flows", func() {
24 | var (
25 | events []*protocol.Event
26 | exceptions []*protocol.Exception
27 | )
28 | BeforeEach(func() {
29 | events = make([]*protocol.Event, 0)
30 | exceptions = make([]*protocol.Exception, 0)
31 | tracer.GlobalTracer = &tracer.MockedEpsagonTracer{
32 | Events: &events,
33 | Exceptions: &exceptions,
34 | }
35 | })
36 | It("Calls the user function", func() {
37 | called := false
38 | wrapper := &GenericWrapper{
39 | config: &Config{},
40 | handler: reflect.ValueOf(func() { called = true }),
41 | tracer: tracer.GlobalTracer,
42 | }
43 | wrapper.Call()
44 | Expect(called).To(Equal(true))
45 | Expect(len(events)).To(Equal(1))
46 | })
47 | It("Calls the user function with custom resource name", func() {
48 | called := false
49 | resourceName := "test-resource-name"
50 | wrapper := &GenericWrapper{
51 | config: &Config{},
52 | handler: reflect.ValueOf(func() { called = true }),
53 | tracer: tracer.GlobalTracer,
54 | resourceName: resourceName,
55 | }
56 | wrapper.Call()
57 | Expect(called).To(Equal(true))
58 | Expect(len(events)).To(Equal(1))
59 | Expect(events[0].Resource.Name).To(Equal(resourceName))
60 | })
61 | It("Retuns and accepts arguments", func() {
62 | called := false
63 | result := false
64 | wrapper := &GenericWrapper{
65 | config: &Config{},
66 | handler: reflect.ValueOf(
67 | func(x bool) bool {
68 | called = x
69 | return x
70 | }),
71 | tracer: tracer.GlobalTracer,
72 | }
73 | result = wrapper.Call(true)[0].Bool()
74 | Expect(called).To(Equal(true))
75 | Expect(result).To(Equal(true))
76 | Expect(len(events)).To(Equal(1))
77 | })
78 | })
79 | Context("Error Flows", func() {
80 | var (
81 | events []*protocol.Event
82 | exceptions []*protocol.Exception
83 | )
84 | BeforeEach(func() {
85 | events = make([]*protocol.Event, 0)
86 | exceptions = make([]*protocol.Exception, 0)
87 | tracer.GlobalTracer = &tracer.MockedEpsagonTracer{
88 | Events: &events,
89 | Exceptions: &exceptions,
90 | }
91 | })
92 | It("Panics for wrong number of arguments", func() {
93 | called := false
94 | wrapper := &GenericWrapper{
95 | config: &Config{},
96 | handler: reflect.ValueOf(func(x bool) { called = x }),
97 | tracer: tracer.GlobalTracer,
98 | }
99 | Expect(func() { wrapper.Call() }).To(Panic())
100 | Expect(called).To(Equal(false))
101 | Expect(len(events)).To(Equal(1))
102 | Expect(events[0].Exception).NotTo(Equal(nil))
103 | })
104 | It("Failed to add event", func() {
105 | tracer.GlobalTracer.(*tracer.MockedEpsagonTracer).PanicAddEvent = true
106 | called := false
107 | wrapper := &GenericWrapper{
108 | config: &Config{},
109 | handler: reflect.ValueOf(func() { called = true }),
110 | tracer: tracer.GlobalTracer,
111 | }
112 | wrapper.Call()
113 | Expect(called).To(Equal(true))
114 | Expect(len(exceptions)).To(Equal(1))
115 | })
116 | It("User function panics", func() {
117 | wrapper := &GenericWrapper{
118 | config: &Config{},
119 | handler: reflect.ValueOf(func() { panic("boom") }),
120 | tracer: tracer.GlobalTracer,
121 | }
122 | Expect(func() { wrapper.Call() }).To(
123 | PanicWith(MatchUserError("boom")))
124 | Expect(len(events)).To(Equal(1))
125 | Expect(events[0].Exception).NotTo(Equal(nil))
126 | })
127 | })
128 | })
129 | })
130 |
--------------------------------------------------------------------------------
/epsagon/lambda_trigger_test.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | // "fmt"
5 | "encoding/json"
6 | "time"
7 |
8 | lambdaEvents "github.com/aws/aws-lambda-go/events"
9 | "github.com/epsagon/epsagon-go/protocol"
10 | "github.com/epsagon/epsagon-go/tracer"
11 | . "github.com/onsi/ginkgo"
12 | . "github.com/onsi/gomega"
13 | )
14 |
15 | type inventedEvent struct {
16 | Name string
17 | Job string
18 | DateOfBirth time.Time
19 | }
20 |
21 | var (
22 | exampleAPIGateWay = lambdaEvents.APIGatewayProxyRequest{
23 | Resource: "test-resource",
24 | Path: "/hello",
25 | HTTPMethod: "GET",
26 | Body: "hello world",
27 | IsBase64Encoded: false,
28 | Headers: map[string]string{
29 | "hello": "world",
30 | },
31 | StageVariables: map[string]string{
32 | "hello": "world",
33 | },
34 | PathParameters: map[string]string{
35 | "hello": "world",
36 | },
37 | QueryStringParameters: map[string]string{
38 | "hello": "world",
39 | },
40 | }
41 | exampleAPIGatewayV2HTTP = lambdaEvents.APIGatewayV2HTTPRequest{
42 | RawPath: "/hello",
43 | RequestContext: lambdaEvents.APIGatewayV2HTTPRequestContext{
44 | APIID: "test-api",
45 | HTTP: lambdaEvents.APIGatewayV2HTTPRequestContextHTTPDescription{
46 | Method: "GET",
47 | },
48 | },
49 | Body: "hello world",
50 | IsBase64Encoded: false,
51 | Headers: map[string]string{
52 | "hello": "world",
53 | },
54 | StageVariables: map[string]string{
55 | "hello": "world",
56 | },
57 | PathParameters: map[string]string{
58 | "hello": "world",
59 | },
60 | QueryStringParameters: map[string]string{
61 | "hello": "world",
62 | },
63 | }
64 | exampleDDB = lambdaEvents.DynamoDBEvent{
65 | Records: []lambdaEvents.DynamoDBEventRecord{
66 | lambdaEvents.DynamoDBEventRecord{
67 | AWSRegion: "us-east-1",
68 | EventSourceArn: "test/1/2",
69 | EventSource: "aws:dynamodb",
70 | EventName: "PutItem",
71 | Change: lambdaEvents.DynamoDBStreamRecord{
72 | SequenceNumber: "test_sequence_number",
73 | NewImage: map[string]lambdaEvents.DynamoDBAttributeValue{
74 | "test2": lambdaEvents.NewStringAttribute("2"),
75 | },
76 | OldImage: map[string]lambdaEvents.DynamoDBAttributeValue{
77 | "test1": lambdaEvents.NewStringAttribute("1"),
78 | },
79 | },
80 | },
81 | },
82 | }
83 | exampleInventedEvent = inventedEvent{
84 | Name: "Erez Freiberger",
85 | Job: "Software Engineer",
86 | DateOfBirth: time.Now(),
87 | }
88 | )
89 |
90 | func verifyLabelValue(key string, value string, labelsMap map[string]string) {
91 | labelValue, ok := labelsMap[key]
92 | Expect(ok).To(BeTrue())
93 | Expect(labelValue).To(Equal(value))
94 | }
95 |
96 | var _ = Describe("epsagon trigger suite", func() {
97 | Describe("addLambdaTrigger", func() {
98 | var (
99 | events []*protocol.Event
100 | exceptions []*protocol.Exception
101 | )
102 | BeforeEach(func() {
103 | events = make([]*protocol.Event, 0)
104 | exceptions = make([]*protocol.Exception, 0)
105 | tracer.GlobalTracer = &tracer.MockedEpsagonTracer{
106 | Events: &events,
107 | Exceptions: &exceptions,
108 | }
109 | })
110 |
111 | Context("Handling of known trigger - API Gateway", func() {
112 | It("Identifies the first known handler, API Gateway - REST", func() {
113 | exampleJSON, err := json.Marshal(exampleAPIGateWay)
114 | if err != nil {
115 | Fail("Failed to marshal json")
116 | }
117 | addLambdaTrigger(json.RawMessage(exampleJSON), false, triggerFactories, tracer.GlobalTracer)
118 | Expect(len(events)).To(BeNumerically("==", 1))
119 | Expect(events[0].Resource.Type).To(Equal("api_gateway"))
120 | })
121 |
122 | It("Identifies the first known handler, API Gateway - HTTP", func() {
123 | exampleJSON, err := json.Marshal(exampleAPIGatewayV2HTTP)
124 | if err != nil {
125 | Fail("Failed to marshal json")
126 | }
127 | addLambdaTrigger(json.RawMessage(exampleJSON), false, triggerFactories, tracer.GlobalTracer)
128 | Expect(len(events)).To(BeNumerically("==", 1))
129 | Expect(events[0].Resource.Type).To(Equal("api_gateway"))
130 | })
131 | })
132 | Context("Handling of known trigger - DynamoDB", func() {
133 | It("Identifies the first known handler, DynamoDB", func() {
134 | exampleJSON, err := json.Marshal(exampleDDB)
135 | if err != nil {
136 | Fail("Failed to marshal json")
137 | }
138 | addLambdaTrigger(json.RawMessage(exampleJSON), false, triggerFactories, tracer.GlobalTracer)
139 | Expect(len(events)).To(BeNumerically("==", 1))
140 | Expect(events[0].Resource.Type).To(Equal("dynamodb"))
141 | verifyLabelValue("region", "us-east-1", events[0].Resource.Metadata)
142 | verifyLabelValue(
143 | "New Image",
144 | "{\"test2\":\"{\\n S: \\\"2\\\"\\n}\"}",
145 | events[0].Resource.Metadata,
146 | )
147 | verifyLabelValue(
148 | "Old Image",
149 | "{\"test1\":\"{\\n S: \\\"1\\\"\\n}\"}",
150 | events[0].Resource.Metadata,
151 | )
152 | })
153 | })
154 | Context("Handling of known trigger with extra fields", func() {
155 | It("Identifies the first known handler", func() {
156 | exampleJSON, err := json.Marshal(exampleAPIGateWay)
157 | if err != nil {
158 | Fail("Failed to marshal json")
159 | }
160 | var rawEvent map[string]interface{}
161 | err = json.Unmarshal(exampleJSON, &rawEvent)
162 | if err != nil {
163 | Fail("Failed to unmarshal json into rawEvent map[string]interface{}")
164 | }
165 | rawEvent["ExtraFields"] = "BOOOM"
166 | exampleJSON, err = json.Marshal(rawEvent)
167 | if err != nil {
168 | Fail("Failed to marshal json from rawEvent")
169 | }
170 | addLambdaTrigger(json.RawMessage(exampleJSON), false, triggerFactories, tracer.GlobalTracer)
171 | Expect(len(events)).To(BeNumerically("==", 1))
172 | Expect(events[0].Resource.Type).To(Equal("api_gateway"))
173 | })
174 | })
175 |
176 | Context("Handling of unknown trigger", func() {
177 | It("Adds a JSON Event", func() {
178 | exampleJSON, err := json.Marshal(exampleInventedEvent)
179 | if err != nil {
180 | Fail("Failed to marshal json")
181 | }
182 | addLambdaTrigger(json.RawMessage(exampleJSON), false, triggerFactories, tracer.GlobalTracer)
183 | Expect(len(events)).To(BeNumerically("==", 1))
184 | Expect(events[0].Resource.Type).To(Equal("json"))
185 | })
186 | })
187 | })
188 | })
189 |
--------------------------------------------------------------------------------
/epsagon/lambda_wrapper.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 | "runtime/debug"
9 | "strconv"
10 | "strings"
11 | "time"
12 |
13 | "github.com/aws/aws-lambda-go/lambdacontext"
14 | "github.com/epsagon/epsagon-go/protocol"
15 | "github.com/epsagon/epsagon-go/tracer"
16 | )
17 |
18 | var (
19 | coldStart = true
20 | )
21 |
22 | const TimeoutErrorCode protocol.ErrorCode = 3
23 |
24 | type genericLambdaHandler func(context.Context, json.RawMessage) (interface{}, error)
25 |
26 | // epsagonLambdaWrapper is a generic lambda function type
27 | type epsagonLambdaWrapper struct {
28 | handler genericLambdaHandler
29 | config *Config
30 | tracer tracer.Tracer
31 | invoked bool
32 | invoking bool
33 | timeout bool
34 | }
35 |
36 | type preInvokeData struct {
37 | InvocationMetadata map[string]string
38 | LambdaContext *lambdacontext.LambdaContext
39 | StartTime float64
40 | }
41 |
42 | type invocationData struct {
43 | ExceptionInfo *protocol.Exception
44 | errorStatus protocol.ErrorCode
45 | result interface{}
46 | err error
47 | thrownError interface{}
48 | }
49 |
50 | func getAWSAccount(lc *lambdacontext.LambdaContext) string {
51 | arnParts := strings.Split(lc.InvokedFunctionArn, ":")
52 | if len(arnParts) >= 4 {
53 | return arnParts[4]
54 | }
55 | return ""
56 | }
57 |
58 | func createLambdaEvent(preInvokeInfo *preInvokeData) *protocol.Event {
59 | endTime := tracer.GetTimestamp()
60 | duration := endTime - preInvokeInfo.StartTime
61 |
62 | return &protocol.Event{
63 | Id: preInvokeInfo.LambdaContext.AwsRequestID,
64 | StartTime: preInvokeInfo.StartTime,
65 | Resource: &protocol.Resource{
66 | Name: lambdacontext.FunctionName,
67 | Type: "lambda",
68 | Operation: "invoke",
69 | Metadata: preInvokeInfo.InvocationMetadata,
70 | },
71 | Origin: "runner",
72 | Duration: duration,
73 | }
74 | }
75 |
76 | func (wrapper *epsagonLambdaWrapper) preInvokeOps(
77 | ctx context.Context, payload json.RawMessage) (info *preInvokeData) {
78 | startTime := tracer.GetTimestamp()
79 | metadata := map[string]string{}
80 | lc, ok := lambdacontext.FromContext(ctx)
81 | if !ok {
82 | lc = &lambdacontext.LambdaContext{}
83 | }
84 | defer func() {
85 | if r := recover(); r != nil {
86 | wrapper.tracer.AddExceptionTypeAndMessage("LambdaWrapper",
87 | fmt.Sprintf("preInvokeOps:%+v", r))
88 | info = &preInvokeData{
89 | LambdaContext: lc,
90 | StartTime: startTime,
91 | InvocationMetadata: metadata,
92 | }
93 | }
94 | }()
95 |
96 | metadata = map[string]string{
97 | "log_stream_name": lambdacontext.LogStreamName,
98 | "log_group_name": lambdacontext.LogGroupName,
99 | "function_version": lambdacontext.FunctionVersion,
100 | "memory": strconv.Itoa(lambdacontext.MemoryLimitInMB),
101 | "cold_start": strconv.FormatBool(coldStart),
102 | "aws_account": getAWSAccount(lc),
103 | "region": os.Getenv("AWS_REGION"),
104 | }
105 | coldStart = false
106 |
107 | addLambdaTrigger(payload, wrapper.config.MetadataOnly, triggerFactories, wrapper.tracer)
108 |
109 | return &preInvokeData{
110 | InvocationMetadata: metadata,
111 | LambdaContext: lc,
112 | StartTime: startTime,
113 | }
114 | }
115 |
116 | func (wrapper *epsagonLambdaWrapper) postInvokeOps(
117 | preInvokeInfo *preInvokeData,
118 | invokeInfo *invocationData) {
119 | defer func() {
120 | if r := recover(); r != nil {
121 | wrapper.tracer.AddExceptionTypeAndMessage("LambdaWrapper", fmt.Sprintf("postInvokeOps:%+v", r))
122 | }
123 | }()
124 |
125 | lambdaEvent := createLambdaEvent(preInvokeInfo)
126 | lambdaEvent.ErrorCode = invokeInfo.errorStatus
127 | lambdaEvent.Exception = invokeInfo.ExceptionInfo
128 |
129 | if !wrapper.config.MetadataOnly {
130 | result, err := json.Marshal(invokeInfo.result)
131 | if err == nil {
132 | lambdaEvent.Resource.Metadata["return_value"] = string(result)
133 | } else {
134 | lambdaEvent.Resource.Metadata["return_value"] = fmt.Sprintf("%+v", invokeInfo.result)
135 | }
136 | }
137 |
138 | wrapper.tracer.AddEvent(lambdaEvent)
139 | }
140 |
141 | // Invoke calls the wrapper, and creates a tracer for that duration.
142 | func (wrapper *epsagonLambdaWrapper) Invoke(ctx context.Context, payload json.RawMessage) (result interface{}, err error) {
143 | invokeInfo := &invocationData{}
144 | wrapper.invoked = false
145 | wrapper.invoking = false
146 | defer func() {
147 | if !wrapper.invoking {
148 | recover()
149 | }
150 | if !wrapper.invoked {
151 | result, err = wrapper.handler(ctx, payload)
152 | }
153 | if invokeInfo.thrownError != nil {
154 | panic(userError{
155 | exception: invokeInfo.thrownError,
156 | stack: invokeInfo.ExceptionInfo.Traceback,
157 | })
158 | }
159 | }()
160 |
161 | preInvokeInfo := wrapper.preInvokeOps(ctx, payload)
162 | go wrapper.trackTimeout(ctx, preInvokeInfo)
163 | wrapper.InvokeClientLambda(ctx, payload, invokeInfo)
164 | if !wrapper.timeout {
165 | wrapper.postInvokeOps(preInvokeInfo, invokeInfo)
166 | }
167 |
168 | return invokeInfo.result, invokeInfo.err
169 | }
170 |
171 | func (wrapper *epsagonLambdaWrapper) trackTimeout(ctx context.Context, preInvokeInfo *preInvokeData) {
172 | deadline, isDeadlineSet := ctx.Deadline()
173 | if isDeadlineSet {
174 | thresholdDuration := time.Duration(tracer.GetLambdaTimeoutThresholdMs())
175 | deadline = deadline.Add(-thresholdDuration * time.Millisecond)
176 | timeoutChannel := time.After(time.Until(deadline))
177 |
178 | for range timeoutChannel {
179 | if wrapper.invoking {
180 | wrapper.timeout = true
181 |
182 | lambdaEvent := createLambdaEvent(preInvokeInfo)
183 | lambdaEvent.ErrorCode = TimeoutErrorCode
184 |
185 | wrapper.tracer.AddEvent(lambdaEvent)
186 | wrapper.tracer.Stop()
187 | }
188 | }
189 | }
190 | }
191 |
192 | func (wrapper *epsagonLambdaWrapper) InvokeClientLambda(
193 | ctx context.Context, payload json.RawMessage, invokeInfo *invocationData) {
194 | defer func() {
195 | invokeInfo.thrownError = recover()
196 | if invokeInfo.thrownError != nil {
197 | invokeInfo.ExceptionInfo = &protocol.Exception{
198 | Type: "Runtime Error",
199 | Message: fmt.Sprintf("%v", invokeInfo.thrownError),
200 | Traceback: string(debug.Stack()),
201 | Time: tracer.GetTimestamp(),
202 | }
203 | invokeInfo.errorStatus = protocol.ErrorCode_EXCEPTION
204 | }
205 | }()
206 |
207 | invokeInfo.errorStatus = protocol.ErrorCode_OK
208 | // calling the actual function:
209 | wrapper.invoked = true
210 | wrapper.invoking = true
211 | result, err := wrapper.handler(ctx, payload)
212 | wrapper.invoking = false
213 | if err != nil {
214 | invokeInfo.errorStatus = protocol.ErrorCode_ERROR
215 | invokeInfo.ExceptionInfo = &protocol.Exception{
216 | Type: "Error Result",
217 | Message: err.Error(),
218 | Traceback: "",
219 | Time: tracer.GetTimestamp(),
220 | }
221 | }
222 | invokeInfo.result = result
223 | invokeInfo.err = err
224 | }
225 |
226 | // WrapLambdaHandler wraps a generic wrapper for lambda function with epsagon tracing
227 | func WrapLambdaHandler(config *Config, handler interface{}) interface{} {
228 | return func(ctx context.Context, payload json.RawMessage) (interface{}, error) {
229 | wrapperTracer := tracer.CreateGlobalTracer(&config.Config)
230 | wrapperTracer.Start()
231 |
232 | wrapper := &epsagonLambdaWrapper{
233 | config: config,
234 | handler: makeGenericHandler(handler),
235 | tracer: wrapperTracer,
236 | }
237 |
238 | defer func() {
239 | if !wrapper.timeout {
240 | wrapperTracer.Stop()
241 | }
242 | }()
243 |
244 | return wrapper.Invoke(ctx, payload)
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/epsagon/tracer_helpers.go:
--------------------------------------------------------------------------------
1 | package epsagon
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/epsagon/epsagon-go/tracer"
7 | )
8 |
9 | type tracerKey string
10 |
11 | const tracerKeyValue tracerKey = "tracer"
12 |
13 | // ContextWithTracer creates a context with given tracer
14 | func ContextWithTracer(t tracer.Tracer, ctx ...context.Context) context.Context {
15 | if len(ctx) == 1 {
16 | return context.WithValue(ctx[0], tracerKeyValue, t)
17 | }
18 | return context.WithValue(context.Background(), tracerKeyValue, t)
19 | }
20 |
21 | // ExtractTracer Extracts the tracer from given contexts (using first context),
22 | // returns Global tracer if no context is given and GlobalTracer is valid (= non nil, not stopped)
23 | func ExtractTracer(ctx []context.Context) tracer.Tracer {
24 | if len(ctx) == 0 {
25 | if tracer.GlobalTracer == nil || tracer.GlobalTracer.Stopped() {
26 | return nil
27 | }
28 | return tracer.GlobalTracer
29 | }
30 | rawValue := ctx[0].Value(tracerKeyValue)
31 | if rawValue == nil {
32 | panic("Invalid context, see Epsagon Concurrent Generic GO function example")
33 | }
34 | tracerValue, ok := rawValue.(tracer.Tracer)
35 | if !ok {
36 | panic("Invalid context value, see Epsagon Concurrent Generic GO function example")
37 | }
38 | if tracerValue == nil || tracerValue.Stopped() {
39 | return nil
40 | }
41 | return tracerValue
42 | }
43 |
--------------------------------------------------------------------------------
/example/api_gateway/README.md:
--------------------------------------------------------------------------------
1 | GOOS=linux go build main.go
2 |
--------------------------------------------------------------------------------
/example/api_gateway/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "io/ioutil"
5 | "log"
6 | "net/http"
7 |
8 | "github.com/aws/aws-lambda-go/events"
9 | "github.com/aws/aws-lambda-go/lambda"
10 | "github.com/epsagon/epsagon-go/epsagon"
11 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
12 | )
13 |
14 | // Response is an API gateway response type
15 | type Response events.APIGatewayProxyResponse
16 |
17 | func myHandler(request events.APIGatewayProxyRequest) (Response, error) {
18 | log.Println("In myHandler, received body: ", request.Body)
19 | client := http.Client{Transport: epsagonhttp.NewTracingTransport()}
20 | resp, err := client.Get("https://api.randomuser.me")
21 | var body string
22 | if err == nil {
23 | defer resp.Body.Close()
24 | bodyBytes, err := ioutil.ReadAll(resp.Body)
25 | if err == nil {
26 | body = string(bodyBytes)
27 | }
28 | } else {
29 | log.Printf("Error in getting response: %+v\n", err)
30 | }
31 | return Response{Body: "yes: random user: " + body, StatusCode: 200}, nil
32 | }
33 |
34 | func main() {
35 | log.Println("enter main")
36 | config := epsagon.NewTracerConfig("epsagon-test-go", "")
37 | lambda.Start(epsagon.WrapLambdaHandler(config, myHandler))
38 | }
39 |
--------------------------------------------------------------------------------
/example/api_gateway/makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 | deploy: build
4 | sls deploy
5 |
6 | build: main
7 |
8 | main: main.go
9 | go build -o main main.go
10 |
11 | .PHONY: all build main
12 |
--------------------------------------------------------------------------------
/example/api_gateway/serverless.yml:
--------------------------------------------------------------------------------
1 | service: example-go-app-2
2 |
3 | provider:
4 | name: aws
5 | runtime: go1.x
6 | region: eu-west-1
7 | environment:
8 | EPSAGON_TOKEN: ${env:EPSAGON_TOKEN}
9 | EPSAGON_COLLECTOR_URL: ${env:EPSAGON_COLLECTOR_URL}
10 | EPSAGON_METADATA: FALSE
11 | EPSAGON_DEBUG: TRUE
12 |
13 | functions:
14 | hello:
15 | handler: main
16 | events:
17 | - http:
18 | path: hello
19 | method: post
20 |
--------------------------------------------------------------------------------
/example/concurrent_generic_go_functions/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "log"
7 | "net/http"
8 | "sync"
9 | "time"
10 |
11 | "github.com/epsagon/epsagon-go/epsagon"
12 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
13 | )
14 |
15 | func doTask(ctx context.Context, a int, b string, wg *sync.WaitGroup) (int, error) {
16 | defer wg.Done()
17 | log.Printf("inside doTask: b = %s", b)
18 | client := http.Client{Transport: epsagonhttp.NewTracingTransport(ctx)}
19 | client.Get("https://epsagon.com/")
20 | return a + 1, fmt.Errorf("boom")
21 | }
22 |
23 | func main() {
24 | config := epsagon.NewTracerConfig("generic-go-wrapper", "")
25 | config.Debug = true
26 | var wg sync.WaitGroup
27 | for i := 0; i < 5; i++ {
28 | go epsagon.ConcurrentGoWrapper(config, doTask)(i, "hello", &wg)
29 | }
30 | wg.Wait()
31 | time.Sleep(2 * time.Second)
32 | }
33 |
--------------------------------------------------------------------------------
/example/concurrent_generic_go_functions/makefile:
--------------------------------------------------------------------------------
1 | build: main
2 |
3 | main: main.go
4 | go build -o main main.go
5 |
6 | .PHONY: all build main
7 |
--------------------------------------------------------------------------------
/example/custom_error_example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/epsagon/epsagon-go/epsagon"
8 | )
9 |
10 | func doTask(a int, b string) (int, error) {
11 | log.Printf("inside doTask: b = %s", b)
12 | if a < 10 {
13 | // This error with its type will be viewable in Epsagon dashboard
14 | epsagon.TypeError("value must be bigger than 10", "InputError")
15 | }
16 |
17 | return a + 1, fmt.Errorf("boom")
18 | }
19 |
20 | func main() {
21 | // Normal call
22 | res, err := doTask(3, "world")
23 | if err != nil {
24 | log.Printf("First result is %d", res)
25 | } else {
26 | log.Printf("error was: %v", err)
27 | }
28 |
29 | // With Epsagon instrumentation
30 | config := epsagon.NewTracerConfig("generic-go-wrapper", "")
31 | config.Debug = true
32 | response := epsagon.GoWrapper(config, doTask)(5, "hello")
33 | res2 := response[0].Int()
34 | errInterface := response[1].Interface()
35 | if errInterface == nil {
36 | log.Printf("Result was %d", res2)
37 | } else {
38 | err := errInterface.(error)
39 | log.Printf("error was: %v", err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/example/custom_error_example/makefile:
--------------------------------------------------------------------------------
1 | build: main
2 |
3 | main: main.go
4 | go build -o main main.go
5 |
6 | .PHONY: all build main
7 |
--------------------------------------------------------------------------------
/example/ddb_example/makefile:
--------------------------------------------------------------------------------
1 | deploy: build
2 | sls deploy
3 |
4 | build: trigger/main write/main write_sdk_v2/main
5 |
6 | local: trigger/local_main write/local_main
7 |
8 | trigger/main: trigger/main.go
9 | GOOS=linux go build -o trigger/main trigger/main.go
10 |
11 | write/main: write/main.go
12 | GOOS=linux go build -o write/main write/main.go
13 |
14 | write_sdk_v2/main: write_sdk_v2/main.go
15 | GOOS=linux go build -o write_sdk_v2/main write_sdk_v2/main.go
16 |
17 | trigger/local_main: trigger/main.go
18 | GOOS=darwin go build -o trigger/local_main trigger/main.go
19 |
20 | write/local_main: write/main.go
21 | GOOS=darwin go build -o write/local_main write/main.go
22 |
23 | .PHONY: all build trigger write
24 |
--------------------------------------------------------------------------------
/example/ddb_example/serverless.yml:
--------------------------------------------------------------------------------
1 | service: example-go-app-ddb
2 |
3 | custom:
4 | TableName: golang-test-table
5 |
6 | provider:
7 | name: aws
8 | runtime: go1.x
9 | region: eu-west-1
10 | environment:
11 | EPSAGON_TOKEN: ${env:EPSAGON_TOKEN}
12 | EPSAGON_COLLECTOR_URL: ${env:EPSAGON_COLLECTOR_URL}
13 |
14 | iamRoleStatements:
15 | - Effect: "Allow"
16 | Action:
17 | - "*"
18 | Resource: "arn:aws:dynamodb:eu-west-1:*:table/golang-test-table"
19 |
20 | functions:
21 | write:
22 | handler: write/main
23 | events:
24 | - http:
25 | path: write
26 | method: post
27 | environment:
28 | TABLE_NAME: ${self:custom.TableName}
29 | write-v2:
30 | handler: write_sdk_v2/main
31 | events:
32 | - http:
33 | path: write_v2
34 | method: post
35 | environment:
36 | TABLE_NAME: ${self:custom.TableName}
37 | triggered:
38 | handler: trigger/main
39 | events:
40 | - stream:
41 | type: dynamodb
42 | batchSize: 20
43 | startingPosition: TRIM_HORIZON
44 | arn:
45 | Fn::GetAtt:
46 | - GoLangTestTable
47 | - StreamArn
48 |
49 | resources:
50 | Resources:
51 | GoLangTestTable:
52 | Type: "AWS::DynamoDB::Table"
53 | DeletionPolicy: Delete
54 | Properties:
55 | AttributeDefinitions:
56 | -
57 | AttributeName: item
58 | AttributeType: S
59 | KeySchema:
60 | -
61 | AttributeName: item
62 | KeyType: HASH
63 | ProvisionedThroughput:
64 | ReadCapacityUnits: 1
65 | WriteCapacityUnits: 1
66 | StreamSpecification:
67 | StreamViewType: NEW_IMAGE
68 | TableName: ${self:custom.TableName}
69 |
--------------------------------------------------------------------------------
/example/ddb_example/trigger/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/aws/aws-lambda-go/events"
5 | "github.com/aws/aws-lambda-go/lambda"
6 | "github.com/epsagon/epsagon-go/epsagon"
7 | "log"
8 | )
9 |
10 | func ddbHandler(ddbEvent events.DynamoDBEvent) error {
11 | log.Println("In ddbHandler, received body: ", ddbEvent)
12 | return nil
13 | }
14 |
15 | func main() {
16 | log.Println("enter main")
17 | config := epsagon.NewTracerConfig("ddb-test-go", "")
18 | config.Debug = true
19 | lambda.Start(epsagon.WrapLambdaHandler(config, ddbHandler))
20 | log.Println("exit main")
21 | }
22 |
--------------------------------------------------------------------------------
/example/ddb_example/write/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/aws/aws-lambda-go/events"
6 | "github.com/aws/aws-lambda-go/lambda"
7 | "github.com/aws/aws-sdk-go/aws"
8 | "github.com/aws/aws-sdk-go/aws/session"
9 | "github.com/aws/aws-sdk-go/service/dynamodb"
10 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
11 | "github.com/epsagon/epsagon-go/epsagon"
12 | "github.com/epsagon/epsagon-go/wrappers/aws/aws-sdk-go/aws"
13 | "github.com/google/uuid"
14 | "log"
15 | "os"
16 | )
17 |
18 | // Item example
19 | type Item struct {
20 | Item string `json:"item"`
21 | }
22 |
23 | func ddbHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
24 | log.Println("In ddbHandler, received body: ", request.Body)
25 |
26 | session := epsagonawswrapper.WrapSession(session.Must(session.NewSession(&aws.Config{
27 | Region: aws.String("eu-west-1")},
28 | )))
29 |
30 | svc := dynamodb.New(session)
31 |
32 | item := Item{
33 | Item: uuid.New().String(),
34 | }
35 |
36 | av, marshalErr := dynamodbattribute.MarshalMap(item)
37 |
38 | if marshalErr != nil {
39 | marshErrMsg := fmt.Sprintf("Failed to marshal table: %v", marshalErr)
40 | log.Println(marshErrMsg)
41 | return events.APIGatewayProxyResponse{Body: marshErrMsg, StatusCode: 500}, marshalErr
42 | }
43 |
44 | input := &dynamodb.PutItemInput{
45 | Item: av,
46 | TableName: aws.String(os.Getenv("TABLE_NAME")),
47 | }
48 |
49 | _, err := svc.PutItem(input)
50 |
51 | if err != nil {
52 | fmt.Println("Failed calling PutItem:")
53 | errMsg := fmt.Sprintf("Failed calling PutItem: %v", err.Error())
54 | return events.APIGatewayProxyResponse{Body: errMsg, StatusCode: 500}, err
55 | }
56 |
57 | fmt.Println("Successfully written item to table")
58 | return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
59 | }
60 |
61 | func main() {
62 | log.Println("enter main")
63 | config := epsagon.NewTracerConfig("ddb-test-go-v2", "")
64 | config.Debug = true
65 | lambda.Start(epsagon.WrapLambdaHandler(config, ddbHandler))
66 | log.Println("exit main")
67 | }
68 |
--------------------------------------------------------------------------------
/example/ddb_example/write_sdk_v2/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/aws/aws-lambda-go/events"
7 | "github.com/aws/aws-lambda-go/lambda"
8 | "github.com/aws/aws-sdk-go-v2/aws"
9 | "github.com/aws/aws-sdk-go-v2/aws/external"
10 | "github.com/aws/aws-sdk-go-v2/service/dynamodb"
11 | "github.com/epsagon/epsagon-go/epsagon"
12 | "github.com/google/uuid"
13 | "log"
14 | "os"
15 | )
16 |
17 | func ddbHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
18 | cfg, err := external.LoadDefaultAWSConfig()
19 | if err != nil {
20 | panic("Failed to load default aws config")
21 | }
22 | cfg.Region = "eu-west-1"
23 | svc := epsagon.WrapAwsV2Service(dynamodb.New(cfg)).(*dynamodb.Client)
24 | putItemInput := dynamodb.PutItemInput{
25 | Item: map[string]dynamodb.AttributeValue{
26 | "item": {S: aws.String(uuid.New().String())},
27 | "request": {S: &request.Body},
28 | },
29 | TableName: aws.String(os.Getenv("TABLE_NAME")),
30 | }
31 | req := svc.PutItemRequest(&putItemInput)
32 |
33 | resp, err := req.Send(context.Background())
34 | if err != nil {
35 | return events.APIGatewayProxyResponse{
36 | Body: fmt.Sprintf("PutItem Failed: %s\n%s",
37 | resp.String(), err.Error()),
38 | StatusCode: 500,
39 | }, nil
40 | }
41 |
42 | log.Println("Successfully written item to table")
43 | return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
44 | }
45 |
46 | func main() {
47 | log.Println("enter main")
48 | config := epsagon.NewTracerConfig("ddb-test-go-v2", "")
49 | config.Debug = true
50 | lambda.Start(epsagon.WrapLambdaHandler(config, ddbHandler))
51 | log.Println("exit main")
52 | }
53 |
--------------------------------------------------------------------------------
/example/exp_example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/aws/aws-lambda-go/events"
5 | "github.com/aws/aws-lambda-go/lambda"
6 | "github.com/epsagon/epsagon-go/epsagon"
7 | "log"
8 | )
9 |
10 | func expHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
11 | log.Println("In expHandler, received body: ", request.Body)
12 | zero := 0
13 | _ = 1 / zero
14 | return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
15 | }
16 |
17 | func main() {
18 | log.Println("enter main")
19 | config := epsagon.NewTracerConfig("exp-test-go", "")
20 | lambda.Start(epsagon.WrapLambdaHandler(config, expHandler))
21 | log.Println("exit main")
22 | }
23 |
--------------------------------------------------------------------------------
/example/exp_example/serverless.yml:
--------------------------------------------------------------------------------
1 | service: example-go-app-exp
2 |
3 | provider:
4 | name: aws
5 | runtime: go1.x
6 | region: eu-west-1
7 | environment:
8 | EPSAGON_TOKEN: ${env:EPSAGON_TOKEN}
9 | EPSAGON_COLLECTOR_URL: ${env:EPSAGON_COLLECTOR_URL}
10 |
11 | functions:
12 | write:
13 | handler: main
14 | events:
15 | - http:
16 | path: exp
17 | method: post
18 |
--------------------------------------------------------------------------------
/example/fiber_middleware/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 |
7 | "github.com/epsagon/epsagon-go/epsagon"
8 | epsagonfiber "github.com/epsagon/epsagon-go/wrappers/fiber"
9 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
10 | "github.com/gofiber/fiber/v2"
11 | )
12 |
13 | func main() {
14 | config := epsagon.NewTracerConfig(
15 | "fiber-example", "",
16 | )
17 | config.MetadataOnly = false
18 | app := fiber.New()
19 | // Match all routes
20 | epsagonMiddleware := &epsagonfiber.FiberEpsagonMiddleware{
21 | Config: config,
22 | }
23 | app.Use(epsagonMiddleware.HandlerFunc())
24 | app.Post("/", func(c *fiber.Ctx) error {
25 | // any call wrapped by Epsagon should receive the user context from the fiber context
26 | client := http.Client{Transport: epsagonhttp.NewTracingTransport(c.UserContext())}
27 | client.Get(fmt.Sprintf("https://epsagon.com/"))
28 | return c.SendString("Hello, World 👋!")
29 | })
30 |
31 | app.Listen("0.0.0.0:3000")
32 | }
33 |
--------------------------------------------------------------------------------
/example/fiber_middleware/makefile:
--------------------------------------------------------------------------------
1 | build: main
2 |
3 | main: main.go
4 | go build -o main main.go
5 |
6 | .PHONY: all build main
7 |
--------------------------------------------------------------------------------
/example/generic_go_function/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/epsagon/epsagon-go/epsagon"
8 | )
9 |
10 | func doTask(a int, b string) (int, error) {
11 | log.Printf("inside doTask: b = %s", b)
12 | return a + 1, fmt.Errorf("boom")
13 | }
14 |
15 | func main() {
16 | // Normal call
17 | res, err := doTask(3, "world")
18 | if err != nil {
19 | log.Printf("First result is %d", res)
20 | } else {
21 | log.Printf("error was: %v", err)
22 | }
23 |
24 | // With Epsagon instrumentation
25 | config := epsagon.NewTracerConfig("generic-go-wrapper", "")
26 | config.Debug = true
27 | response := epsagon.GoWrapper(config, doTask)(5, "hello")
28 | res2 := response[0].Int()
29 | errInterface := response[1].Interface()
30 | if errInterface == nil {
31 | log.Printf("Result was %d", res2)
32 | } else {
33 | err := errInterface.(error)
34 | log.Printf("error was: %v", err)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/example/generic_go_function/makefile:
--------------------------------------------------------------------------------
1 | build: main
2 |
3 | main: main.go
4 | go build -o main main.go
5 |
6 | .PHONY: all build main
7 |
--------------------------------------------------------------------------------
/example/gin_wrapper/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "net/http"
7 | "time"
8 |
9 | "github.com/epsagon/epsagon-go/epsagon"
10 | epsagongin "github.com/epsagon/epsagon-go/wrappers/gin"
11 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
12 | "github.com/gin-gonic/gin"
13 | )
14 |
15 | func main() {
16 | config := epsagon.NewTracerConfig(
17 | "gin-wrapper-test", "",
18 | )
19 | config.MetadataOnly = false
20 | r := epsagongin.GinRouterWrapper{
21 | IRouter: gin.Default(),
22 | Hostname: "my-site",
23 | Config: config,
24 | }
25 |
26 | r.GET("/ping", func(c *gin.Context) {
27 | time.Sleep(time.Second * 1)
28 | fmt.Println("hello world")
29 | c.JSON(200, gin.H{
30 | "message": "pong",
31 | })
32 | })
33 | r.POST("/ping", func(c *gin.Context) {
34 | time.Sleep(time.Second * 1)
35 | body, err := ioutil.ReadAll(c.Request.Body)
36 | if err == nil {
37 | fmt.Println("Recieved body: ", string(body))
38 | } else {
39 | fmt.Println("Error reading body: ", err)
40 | }
41 |
42 | client := http.Client{
43 | Transport: epsagonhttp.NewTracingTransport(epsagongin.EpsagonContext(c))}
44 | resp, err := client.Get("http://www.example.com")
45 |
46 | if err == nil {
47 | respBody, err := ioutil.ReadAll(resp.Body)
48 | if err == nil {
49 | fmt.Println("First 1000 bytes recieved: ", string(respBody[:1000]))
50 | }
51 | }
52 |
53 | c.JSON(200, gin.H{
54 | "message": "pong",
55 | })
56 | })
57 |
58 | /* listen and serve on 0.0.0.0: */
59 | /* for windows - localhost: */
60 | /* Port arg format: ":" "*/
61 | err := r.Run(":3001")
62 |
63 | if err != nil {
64 | fmt.Println(err)
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/example/gin_wrapper/makefile:
--------------------------------------------------------------------------------
1 | build: main
2 |
3 | main: main.go
4 | go build -o main main.go
5 |
6 | .PHONY: all build main
7 |
--------------------------------------------------------------------------------
/example/http_mux/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "net/http"
8 |
9 | "github.com/epsagon/epsagon-go/epsagon"
10 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
11 | )
12 |
13 | func main() {
14 | mux := http.NewServeMux()
15 | mux.HandleFunc("/ping", epsagonhttp.WrapHandleFunc(
16 | epsagon.NewTracerConfig("test-http-mux", ""),
17 | func(w http.ResponseWriter, req *http.Request) {
18 |
19 | client := http.Client{
20 | Transport: epsagonhttp.NewTracingTransport(req.Context())}
21 | resp, err := client.Get("http://example.com")
22 |
23 | if err == nil {
24 | respBody, err := ioutil.ReadAll(resp.Body)
25 | if err == nil {
26 | fmt.Println("First 1000 bytes recieved: ", string(respBody[:1000]))
27 | }
28 | }
29 | io.WriteString(w, "pong\n")
30 | },
31 | "my-test-function",
32 | "test.hostname.com",
33 | ))
34 |
35 | http.ListenAndServe(":8080", mux)
36 | }
37 |
--------------------------------------------------------------------------------
/example/ignored_keys/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "io/ioutil"
8 | "net/http"
9 |
10 | "github.com/epsagon/epsagon-go/epsagon"
11 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
12 | )
13 |
14 | func doTask(ctx context.Context) {
15 | client := http.Client{Transport: epsagonhttp.NewTracingTransport(ctx)}
16 | // This password will be masked in the sent trace:
17 | decodedJSON, err := json.Marshal(map[string]string{"password": "abcde", "animal": "lion"})
18 | if err != nil {
19 | epsagon.Label("animal", "lion", ctx)
20 | epsagon.TypeError(err, "json decoding error", ctx)
21 | }
22 | resp, err := client.Post("http://example.com/upload", "application/json", bytes.NewReader(decodedJSON))
23 | if err != nil {
24 | epsagon.Label("animal", "lion", ctx)
25 | epsagon.TypeError(err, "post", ctx)
26 | }
27 | if resp.StatusCode != 200 {
28 | body, _ := ioutil.ReadAll(resp.Body)
29 | if err == nil {
30 | epsagon.TypeError(string(body), "post status code", ctx)
31 | } else {
32 | epsagon.TypeError(err, "post status code", ctx)
33 | }
34 | epsagon.Label("animal", "lion", ctx)
35 | }
36 | }
37 |
38 | func main() {
39 | // With Epsagon instrumentation
40 | config := epsagon.NewTracerConfig("test-ignored-keys", "")
41 | config.Debug = true
42 | config.MetadataOnly = false
43 | config.IgnoredKeys = []string{"password"}
44 | epsagon.ConcurrentGoWrapper(config, doTask)()
45 | }
46 |
--------------------------------------------------------------------------------
/example/label_example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 |
7 | "github.com/epsagon/epsagon-go/epsagon"
8 | )
9 |
10 | func doTask(a int, b string) (int, error) {
11 | log.Printf("inside doTask: b = %s", b)
12 |
13 | // This label will be viewable in Epsagon dashboard
14 | epsagon.Label("my_key", 100.12)
15 | return a + 1, fmt.Errorf("boom")
16 | }
17 |
18 | func main() {
19 | // Normal call
20 | res, err := doTask(3, "world")
21 | if err != nil {
22 | log.Printf("First result is %d", res)
23 | } else {
24 | log.Printf("error was: %v", err)
25 | }
26 |
27 | // With Epsagon instrumentation
28 | config := epsagon.NewTracerConfig("generic-go-wrapper", "")
29 | config.Debug = true
30 | response := epsagon.GoWrapper(config, doTask)(5, "hello")
31 | res2 := response[0].Int()
32 | errInterface := response[1].Interface()
33 | if errInterface == nil {
34 | log.Printf("Result was %d", res2)
35 | } else {
36 | err := errInterface.(error)
37 | log.Printf("error was: %v", err)
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/example/label_example/makefile:
--------------------------------------------------------------------------------
1 | build: main
2 |
3 | main: main.go
4 | go build -o main main.go
5 |
6 | .PHONY: all build main
7 |
--------------------------------------------------------------------------------
/example/mongo_example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/epsagon/epsagon-go/epsagon"
7 | epsagonmongo "github.com/epsagon/epsagon-go/wrappers/mongo"
8 | "time"
9 |
10 | "go.mongodb.org/mongo-driver/bson"
11 | "go.mongodb.org/mongo-driver/mongo"
12 | "go.mongodb.org/mongo-driver/mongo/options"
13 | "go.mongodb.org/mongo-driver/mongo/readpref"
14 | )
15 |
16 | func dbAPI() {
17 |
18 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
19 | defer cancel()
20 |
21 | client, err := mongo.Connect(
22 | ctx,
23 | options.Client().ApplyURI("mongodb://localhost:27017"),
24 | )
25 |
26 | defer func() {
27 | if err = client.Disconnect(ctx); err != nil {
28 | panic(err)
29 | }
30 | }()
31 |
32 | err = client.Ping(ctx, readpref.Primary())
33 |
34 | db := client.Database("DB")
35 |
36 | // WRAP THE MONGO COLLECTION with WrapMongoCollection()
37 | coll := epsagonmongo.WrapMongoCollection(
38 | db.Collection("COLL"),
39 | )
40 |
41 | type doc struct {
42 | Name string
43 | }
44 | var res interface{}
45 |
46 | fmt.Println("##InsertOne")
47 | _, err = coll.InsertOne(
48 | context.Background(),
49 | doc{Name: "bon"},
50 | )
51 | if err != nil {
52 | panic(err)
53 | }
54 |
55 | fmt.Println("##InsertMany")
56 | _, err = coll.InsertMany(
57 | context.Background(),
58 | []interface{}{
59 | bson.D{
60 | {Key: "name", Value: "hello"},
61 | {Key: "age", Value: "33"},
62 | },
63 | bson.D{
64 | {Key: "name", Value: "world"},
65 | {Key: "age", Value: "44"},
66 | },
67 | },
68 | )
69 | if err != nil {
70 | panic(err)
71 | }
72 |
73 | fmt.Println("##FindOne")
74 | coll.FindOne(
75 | context.Background(),
76 | bson.D{{Key: "name", Value: "bon"}},
77 | )
78 |
79 | fmt.Println("##Find")
80 | coll.Find(context.Background(), bson.M{})
81 |
82 | fmt.Println("##Aggregate")
83 | res, err = coll.Aggregate(
84 | context.Background(),
85 | mongo.Pipeline{
86 | bson.D{{Key: "$match", Value: bson.D{{Key: "name", Value: "bon"}}}},
87 | },
88 | )
89 | if err != nil || err == mongo.ErrNoDocuments {
90 | panic(err)
91 | }
92 |
93 | fmt.Println("##CountDocuments")
94 | res, err = coll.CountDocuments(
95 | context.Background(),
96 | bson.D{{Key: "name", Value: "bon"}},
97 | )
98 | fmt.Println(res)
99 | if err != nil || err == mongo.ErrNoDocuments {
100 | panic(err)
101 | }
102 |
103 | fmt.Println("##DeleteOne")
104 | res, err = coll.DeleteOne(
105 | context.Background(),
106 | bson.D{{Key: "name", Value: "bon"}},
107 | )
108 | fmt.Println(res)
109 | if err != nil || err == mongo.ErrNoDocuments {
110 | panic(err)
111 | }
112 |
113 | fmt.Println("##UpdateOne")
114 | res, err = coll.UpdateOne(
115 | context.Background(),
116 | bson.D{{Key: "name", Value: "bon"}},
117 | bson.D{{Key: "$set", Value: bson.D{{Key: "name", Value: "son"}}}},
118 | )
119 | fmt.Println(res)
120 | if err != nil || err == mongo.ErrNoDocuments {
121 | panic(err)
122 | }
123 | }
124 |
125 | func main() {
126 |
127 | config := epsagon.NewTracerConfig(
128 | "skate-shop",
129 | "token",
130 | )
131 | config.MetadataOnly = false
132 | config.Debug = true
133 |
134 | epsagon.GoWrapper(
135 | config,
136 | dbAPI,
137 | )()
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/example/mux_error/main.go:
--------------------------------------------------------------------------------
1 |
2 | package main
3 |
4 | import (
5 | "github.com/epsagon/epsagon-go/epsagon"
6 | "github.com/epsagon/epsagon-go/wrappers/net/http"
7 | "net/http"
8 | )
9 |
10 |
11 | func SetEpsagonConfig() *epsagon.Config {
12 | appName := "simple-error-go"
13 | token := ""
14 | config := epsagon.NewTracerConfig(appName, token)
15 | config.Debug = true
16 | config.MetadataOnly = false
17 | config.SendTimeout = "10s"
18 |
19 | return config
20 | }
21 |
22 |
23 | func handler(res http.ResponseWriter, req *http.Request) {
24 | println("/test pinged")
25 | epsagon.Error("Unknown timezone", req.Context())
26 | res.Write([]byte("Pong.\n"))
27 | }
28 |
29 | func main() {
30 | config := SetEpsagonConfig()
31 | serveMux := http.NewServeMux()
32 | serveMux.HandleFunc(
33 | "/test",
34 | epsagonhttp.WrapHandleFunc(config, handler))
35 | server := http.Server{
36 | Addr: "localhost:8082",
37 | Handler: serveMux,
38 | }
39 | err := server.ListenAndServe()
40 | if err != nil {
41 | panic(err)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/example/redis_wrapper/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "io"
6 | "net/http"
7 |
8 | "github.com/epsagon/epsagon-go/epsagon"
9 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
10 | epsagonredis "github.com/epsagon/epsagon-go/wrappers/redis"
11 | "github.com/go-redis/redis/v8"
12 | )
13 |
14 | func main() {
15 | config := epsagon.NewTracerConfig("redis-wrapper-test", "")
16 | config.MetadataOnly = false
17 |
18 | mux := http.NewServeMux()
19 | mux.HandleFunc("/ping", epsagonhttp.WrapHandleFunc(
20 | config,
21 | func(w http.ResponseWriter, req *http.Request) {
22 | // initialize the redis client as usual
23 | // make sure to pass in the epsagon tracer context
24 | rdb := epsagonredis.NewClient(&redis.Options{
25 | Addr: "localhost:6379",
26 | Password: "",
27 | DB: 0,
28 | }, req.Context())
29 |
30 | ctx := context.Background()
31 |
32 | // pipeline operations
33 | pipe := rdb.Pipeline()
34 | pipe.Set(ctx, "somekey", "somevalue", 0)
35 | pipe.Get(ctx, "somekey")
36 | pipe.Exec(ctx)
37 |
38 | // single operation
39 | value, _ := rdb.Get(ctx, "somekey").Result()
40 |
41 | io.WriteString(w, value)
42 | }),
43 | )
44 |
45 | http.ListenAndServe(":8080", mux)
46 | }
47 |
--------------------------------------------------------------------------------
/example/s3_example/README.md:
--------------------------------------------------------------------------------
1 | GOOS=linux go build main.go
2 |
--------------------------------------------------------------------------------
/example/s3_example/serverless.yml:
--------------------------------------------------------------------------------
1 | service: s3-example-go-app
2 |
3 | custom:
4 | BucketName: s3-golang-test-bucket-epsagon
5 |
6 | provider:
7 | name: aws
8 | runtime: go1.x
9 | region: eu-west-1
10 | environment:
11 | EPSAGON_TOKEN: ${env:EPSAGON_TOKEN}
12 | EPSAGON_COLLECTOR_URL: ${env:EPSAGON_COLLECTOR_URL}
13 | BUCKET_NAME: ${self:custom.BucketName}
14 |
15 | iamRoleStatements:
16 | - Effect: Allow
17 | Action:
18 | - s3:*
19 | Resource: "arn:aws:s3::*:${self:custom.BucketName}/*"
20 | - Effect: Allow
21 | Action:
22 | - s3:*
23 | Resource: "arn:aws:s3::*:${self:custom.BucketName}"
24 |
25 | functions:
26 | write:
27 | handler: write/main
28 | events:
29 | - http:
30 | path: write
31 | method: post
32 | write_v2:
33 | handler: write_v2/main
34 | events:
35 | - http:
36 | path: write_v2
37 | method: post
38 | trigger:
39 | handler: trigger/main
40 | events:
41 | - s3:
42 | bucket: ${self:custom.BucketName}
43 | event: s3:ObjectCreated:*
44 |
--------------------------------------------------------------------------------
/example/s3_example/trigger/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/aws/aws-lambda-go/events"
6 | "github.com/aws/aws-lambda-go/lambda"
7 | "github.com/epsagon/epsagon-go/epsagon"
8 | "log"
9 | )
10 |
11 | func s3Handler(s3Event events.S3Event) {
12 | for _, record := range s3Event.Records {
13 | s3 := record.S3
14 | fmt.Printf("[%s - %s] Bucket = %s, Key = %s \n",
15 | record.EventSource, record.EventTime, s3.Bucket.Name, s3.Object.Key)
16 | }
17 | }
18 |
19 | func main() {
20 | log.Println("enter main")
21 | config := epsagon.NewTracerConfig("s3-test-go", "")
22 | config.Debug = true
23 | lambda.Start(epsagon.WrapLambdaHandler(config, s3Handler))
24 | log.Println("exit main")
25 | }
26 |
--------------------------------------------------------------------------------
/example/s3_example/write/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/aws/aws-lambda-go/events"
7 | "github.com/aws/aws-lambda-go/lambda"
8 | "github.com/aws/aws-sdk-go/aws"
9 | "github.com/aws/aws-sdk-go/aws/session"
10 | "github.com/aws/aws-sdk-go/service/s3/s3manager"
11 | "github.com/epsagon/epsagon-go/epsagon"
12 | "github.com/epsagon/epsagon-go/wrappers/aws/aws-sdk-go/aws"
13 | "github.com/google/uuid"
14 | "log"
15 | "os"
16 | )
17 |
18 | func s3WriteHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
19 | log.Println("In s3WriteHandler, received body: ", request.Body)
20 |
21 | session := epsagonawswrapper.WrapSession(session.Must(session.NewSession(&aws.Config{
22 | Region: aws.String("eu-west-1")},
23 | )))
24 |
25 | uploader := s3manager.NewUploader(session)
26 |
27 | byteData := []byte("Hello World")
28 | myBucket := os.Getenv("BUCKET_NAME")
29 | key := uuid.New().String()
30 |
31 | if len(myBucket) == 0 {
32 | errMsg := "ERROR: You need to assign BUCKET_NAME env"
33 | log.Println(errMsg)
34 | return events.APIGatewayProxyResponse{Body: errMsg, StatusCode: 500}, nil
35 | }
36 |
37 | result, err := uploader.Upload(&s3manager.UploadInput{
38 | Bucket: aws.String(myBucket),
39 | Key: aws.String(key),
40 | Body: bytes.NewReader(byteData),
41 | })
42 |
43 | if err != nil {
44 | errMsg := fmt.Sprintf("Failed calling Upload: %v", err.Error())
45 | log.Println(errMsg)
46 | return events.APIGatewayProxyResponse{Body: errMsg, StatusCode: 500}, err
47 | }
48 |
49 | log.Printf("Succesfully uploaded file to %s\n", result.Location)
50 | return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
51 | }
52 |
53 | func main() {
54 | log.Println("enter main")
55 | config := epsagon.NewTracerConfig("s3-test-go", "")
56 | config.Debug = true
57 | lambda.Start(epsagon.WrapLambdaHandler(config, s3WriteHandler))
58 | log.Println("exit main")
59 | }
60 |
--------------------------------------------------------------------------------
/example/s3_example/write_v2/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "log"
8 | "os"
9 | "strings"
10 | "time"
11 |
12 | "github.com/aws/aws-lambda-go/events"
13 | "github.com/aws/aws-lambda-go/lambda"
14 | "github.com/aws/aws-sdk-go-v2/aws"
15 | "github.com/aws/aws-sdk-go-v2/aws/external"
16 | "github.com/aws/aws-sdk-go-v2/service/s3"
17 | "github.com/epsagon/epsagon-go/epsagon"
18 | "github.com/google/uuid"
19 | )
20 |
21 | func s3WriteHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
22 | log.Println("In s3WriteHandler, received body: ", request.Body)
23 |
24 | cfg, err := external.LoadDefaultAWSConfig()
25 | if err != nil {
26 | panic("Failed to load default aws config")
27 | }
28 | cfg.Region = "eu-west-1"
29 | svc := epsagon.WrapAwsV2Service(s3.New(cfg)).(*s3.Client)
30 |
31 | // Create a context with a timeout that will abort the upload if it takes
32 | // more than the passed in timeout.
33 | ctx := context.Background()
34 | var cancelFn func()
35 | timeout := time.Minute * 2
36 | if timeout > 0 {
37 | ctx, cancelFn = context.WithTimeout(ctx, timeout)
38 | }
39 | // Ensure the context is canceled to prevent leaking.
40 | // See context package for more information, https://golang.org/pkg/context/
41 | defer cancelFn()
42 |
43 | data := "Hello World"
44 | myBucket := os.Getenv("BUCKET_NAME")
45 | key := uuid.New().String()
46 |
47 | if len(myBucket) == 0 {
48 | errMsg := "ERROR: You need to assign BUCKET_NAME env"
49 | log.Println(errMsg)
50 | return events.APIGatewayProxyResponse{Body: errMsg, StatusCode: 500}, nil
51 | }
52 |
53 | req := svc.PutObjectRequest(&s3.PutObjectInput{
54 | Bucket: &myBucket,
55 | Key: &key,
56 | Body: strings.NewReader(data),
57 | })
58 |
59 | if _, err := req.Send(ctx); err != nil {
60 | var cerr *aws.RequestCanceledError
61 | var errMsg string
62 | if errors.As(err, &cerr) {
63 | errMsg = fmt.Sprintf("upload caceled due to timeout, %v", err)
64 | } else {
65 | errMsg = fmt.Sprintf("Failed to upload object: %v", err)
66 | }
67 | log.Println(errMsg)
68 | return events.APIGatewayProxyResponse{Body: errMsg, StatusCode: 500}, err
69 | }
70 |
71 | log.Printf("Succesfully uploaded file.")
72 | return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
73 | }
74 |
75 | func main() {
76 | log.Println("enter main")
77 | config := epsagon.NewTracerConfig("s3-test-go", "")
78 | config.Debug = true
79 | lambda.Start(epsagon.WrapLambdaHandler(config, s3WriteHandler))
80 | log.Println("exit main")
81 | }
82 |
--------------------------------------------------------------------------------
/example/simple_error/README.md:
--------------------------------------------------------------------------------
1 | GOOS=linux go build main.go
2 |
--------------------------------------------------------------------------------
/example/simple_error/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/aws/aws-lambda-go/events"
6 | "github.com/aws/aws-lambda-go/lambda"
7 | "github.com/epsagon/epsagon-go/epsagon"
8 | "log"
9 | )
10 |
11 | // Response is an API gateway response type
12 | type Response events.APIGatewayProxyResponse
13 |
14 | func myHandler(request events.APIGatewayProxyRequest) (Response, error) {
15 | log.Println("In myHandler, received body: ", request.Body)
16 | return Response{StatusCode: 404, Body: "error"}, fmt.Errorf("example error")
17 | }
18 |
19 | func main() {
20 | log.Println("enter main")
21 | config := epsagon.NewTracerConfig("simple-error-go", "")
22 | lambda.Start(epsagon.WrapLambdaHandler(config, myHandler))
23 | }
24 |
--------------------------------------------------------------------------------
/example/simple_error/makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 | deploy: build
4 | sls deploy
5 |
6 | build: main
7 |
8 | main: main.go
9 | go build -o main main.go
10 |
11 | .PHONY: all build main
12 |
--------------------------------------------------------------------------------
/example/simple_error/serverless.yml:
--------------------------------------------------------------------------------
1 | service: example-go-simple-error
2 |
3 | provider:
4 | name: aws
5 | runtime: go1.x
6 | region: eu-west-1
7 | environment:
8 | EPSAGON_TOKEN: ${env:EPSAGON_TOKEN}
9 | EPSAGON_COLLECTOR_URL: ${env:EPSAGON_COLLECTOR_URL}
10 | EPSAGON_DEBUG: "TRUE"
11 |
12 | functions:
13 | simple_error:
14 | handler: main
15 | events:
16 | - http:
17 | path: hello
18 | method: post
19 |
--------------------------------------------------------------------------------
/example/simple_lambda/README.md:
--------------------------------------------------------------------------------
1 | GOOS=linux go build main.go
2 |
--------------------------------------------------------------------------------
/example/simple_lambda/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/aws/aws-lambda-go/events"
5 | "github.com/aws/aws-lambda-go/lambda"
6 | "github.com/epsagon/epsagon-go/epsagon"
7 | "log"
8 | )
9 |
10 | // Response is an API gateway response type
11 | type Response events.APIGatewayProxyResponse
12 |
13 | func myHandler(request events.APIGatewayProxyRequest) (Response, error) {
14 | log.Println("In myHandler, received body: ", request.Body)
15 | return Response{Body: "yes", StatusCode: 200}, nil
16 | }
17 |
18 | func main() {
19 | log.Println("enter main")
20 | config := epsagon.NewTracerConfig("epsagon-test-go", "")
21 | lambda.Start(epsagon.WrapLambdaHandler(config, myHandler))
22 | }
23 |
--------------------------------------------------------------------------------
/example/simple_lambda/makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 | deploy: build
4 | sls deploy
5 |
6 | build: main
7 |
8 | main: main.go
9 | go build -o main main.go
10 |
11 | .PHONY: all build main
12 |
--------------------------------------------------------------------------------
/example/simple_lambda/serverless.yml:
--------------------------------------------------------------------------------
1 | service: example-go-app
2 |
3 | provider:
4 | name: aws
5 | runtime: go1.x
6 | region: eu-west-1
7 | environment:
8 | EPSAGON_TOKEN: ${env:EPSAGON_TOKEN}
9 | EPSAGON_COLLECTOR_URL: ${env:EPSAGON_COLLECTOR_URL}
10 |
11 | functions:
12 | hello:
13 | handler: main
14 | events:
15 | - http:
16 | path: hello
17 | method: post
18 |
--------------------------------------------------------------------------------
/example/sqs_trigger/hello/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "os"
7 |
8 | "github.com/aws/aws-lambda-go/events"
9 | "github.com/aws/aws-lambda-go/lambda"
10 | "github.com/aws/aws-sdk-go/aws/session"
11 | "github.com/aws/aws-sdk-go/service/sqs"
12 |
13 | "github.com/epsagon/epsagon-go/epsagon"
14 | "github.com/epsagon/epsagon-go/wrappers/aws/aws-sdk-go/aws"
15 | )
16 |
17 | func myHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
18 | log.Println("In myHandler, received body: ", request.Body)
19 |
20 | // sess := session.Must(session.NewSession())
21 | // epsagon wrapper for aws-sdk-go
22 | sess := epsagonawswrapper.WrapSession(session.Must(session.NewSession()))
23 |
24 | svcSQS := sqs.New(sess)
25 |
26 | sqsQueueName := os.Getenv("SQS_NAME")
27 | queueURL, err := svcSQS.GetQueueUrl(&sqs.GetQueueUrlInput{QueueName: &sqsQueueName})
28 | if err != nil {
29 | errMsg := fmt.Sprintf("Failed to get SQS Queue Url: %v", err)
30 | log.Println(errMsg)
31 | return events.APIGatewayProxyResponse{Body: errMsg, StatusCode: 500}, err
32 | }
33 | _, err = svcSQS.SendMessage(&sqs.SendMessageInput{
34 | MessageBody: &request.Body,
35 | QueueUrl: queueURL.QueueUrl,
36 | })
37 | if err != nil {
38 | errMsg := fmt.Sprintf("Failed to send SQS message: %v", err)
39 | log.Println(errMsg)
40 | return events.APIGatewayProxyResponse{Body: errMsg, StatusCode: 500}, err
41 | }
42 | return events.APIGatewayProxyResponse{Body: request.Body, StatusCode: 200}, nil
43 | }
44 |
45 | func main() {
46 | log.Println("enter main")
47 | config := epsagon.NewTracerConfig("sqs-test-go", "")
48 | config.Debug = true
49 | lambda.Start(epsagon.WrapLambdaHandler(config, myHandler))
50 | }
51 |
--------------------------------------------------------------------------------
/example/sqs_trigger/makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 | deploy: build
4 | sls deploy
5 |
6 | build: triggered hello
7 |
8 | triggered: triggered/main.go
9 | go build -o triggered/main triggered/main.go
10 |
11 | hello: hello/main.go
12 | go build -o hello/main hello/main.go
13 |
14 | .PHONY: all build triggered hello
15 |
--------------------------------------------------------------------------------
/example/sqs_trigger/serverless.yml:
--------------------------------------------------------------------------------
1 | service: example-go-app-sqs
2 |
3 | custom:
4 | QueueName: GoExampleTestQueue
5 |
6 | provider:
7 | name: aws
8 | runtime: go1.x
9 | region: eu-west-1
10 | environment:
11 | EPSAGON_TOKEN: ${env:EPSAGON_TOKEN}
12 | EPSAGON_COLLECTOR_URL: ${env:EPSAGON_COLLECTOR_URL}
13 |
14 | queueArn:
15 | Fn::Join:
16 | - ":"
17 | - - "arn:aws:sqs"
18 | - ${self:provider.region}
19 | - Ref: AWS::AccountId
20 | - ${self:custom.QueueName}
21 |
22 | iamRoleStatements:
23 | - Effect: "Allow"
24 | Action:
25 | - "sqs:ListQueues"
26 | Resource:
27 | Fn::Join:
28 | - ":"
29 | - - "arn:aws:sqs"
30 | - ${self:provider.region}
31 | - Ref: AWS::AccountId
32 | - "*"
33 | - Effect: "Allow"
34 | Action:
35 | - "sqs:*"
36 | Resource: ${self:provider.queueArn}
37 |
38 | functions:
39 | hello:
40 | handler: hello/main
41 | events:
42 | - http:
43 | path: hello
44 | method: post
45 | environment:
46 | SQS_NAME: ${self:custom.QueueName}
47 | triggered:
48 | handler: triggered/main
49 | events:
50 | - sqs:
51 | arn:
52 | Fn::GetAtt:
53 | - ExampleTestQueue
54 | - Arn
55 | batchSize: 1
56 |
57 | resources:
58 | Resources:
59 | ExampleTestQueue:
60 | Type: AWS::SQS::Queue
61 | Properties:
62 | QueueName: ${self:custom.QueueName}
63 |
--------------------------------------------------------------------------------
/example/sqs_trigger/triggered/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/aws/aws-lambda-go/events"
5 | "github.com/aws/aws-lambda-go/lambda"
6 | "github.com/epsagon/epsagon-go/epsagon"
7 | "log"
8 | )
9 |
10 | func mySQSHandler(sqsEvents events.SQSEvent) error {
11 | log.Println("In mySQSHandler, received body: ", sqsEvents)
12 | return nil
13 | }
14 |
15 | func main() {
16 | log.Println("enter main")
17 | config := epsagon.NewTracerConfig("sqs-test-go", "")
18 | lambda.Start(epsagon.WrapLambdaHandler(config, mySQSHandler))
19 | }
20 |
--------------------------------------------------------------------------------
/example/sts_example/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/aws/aws-lambda-go/events"
7 | "github.com/aws/aws-lambda-go/lambda"
8 | "github.com/aws/aws-sdk-go-v2/aws/external"
9 | "github.com/aws/aws-sdk-go-v2/service/sts"
10 | "github.com/epsagon/epsagon-go/epsagon"
11 | "log"
12 | )
13 |
14 | func ddbHandler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
15 | cfg, err := external.LoadDefaultAWSConfig()
16 | if err != nil {
17 | panic("Failed to load default aws config")
18 | }
19 | cfg.Region = "eu-west-1"
20 | svc := epsagon.WrapAwsV2Service(sts.New(cfg)).(*sts.Client)
21 | req := svc.GetCallerIdentityRequest(&sts.GetCallerIdentityInput{})
22 |
23 | resp, err := req.Send(context.Background())
24 | if err != nil {
25 | return events.APIGatewayProxyResponse{
26 | Body: fmt.Sprintf("GetCallerIdentityRequest Failed: %s\n%s",
27 | resp.String(), err.Error()),
28 | StatusCode: 500,
29 | }, nil
30 | }
31 |
32 | log.Println("Successfully got caller identity request")
33 | return events.APIGatewayProxyResponse{Body: "", StatusCode: 200}, nil
34 | }
35 |
36 | func main() {
37 | log.Println("enter main")
38 | config := epsagon.NewTracerConfig("ddb-test-go-v2", "")
39 | config.Debug = true
40 | lambda.Start(epsagon.WrapLambdaHandler(config, ddbHandler))
41 | log.Println("exit main")
42 | }
43 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/epsagon/epsagon-go
2 |
3 | go 1.11
4 |
5 | require (
6 | github.com/alicebob/miniredis/v2 v2.15.1
7 | github.com/aws/aws-lambda-go v1.17.0
8 | github.com/aws/aws-sdk-go v1.36.30
9 | github.com/aws/aws-sdk-go-v2 v0.23.0
10 | github.com/benweissmann/memongo v0.1.1
11 | github.com/gin-gonic/gin v1.7.0
12 | github.com/go-redis/redis/v8 v8.11.0
13 | github.com/gofiber/fiber/v2 v2.11.0
14 | github.com/gogo/protobuf v1.3.1
15 | github.com/golang/protobuf v1.4.3
16 | github.com/google/uuid v1.1.1
17 | github.com/json-iterator/go v1.1.10 // indirect
18 | github.com/kr/text v0.2.0 // indirect
19 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
20 | github.com/modern-go/reflect2 v1.0.1 // indirect
21 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
22 | github.com/onsi/ginkgo v1.16.1
23 | github.com/onsi/gomega v1.11.0
24 | github.com/stretchr/testify v1.7.0 // indirect
25 | github.com/ugorji/go v1.1.13 // indirect
26 | github.com/valyala/fasthttp v1.26.0
27 | go.mongodb.org/mongo-driver v1.5.2
28 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
29 | google.golang.org/protobuf v1.25.0 // indirect
30 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
31 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
32 |
33 | )
34 |
--------------------------------------------------------------------------------
/internal/fake_collector.go:
--------------------------------------------------------------------------------
1 | package internal
2 |
3 | import (
4 | "encoding/json"
5 | "github.com/epsagon/epsagon-go/protocol"
6 | "net"
7 | )
8 |
9 | // FakeCollector implements a fake trace collector that will
10 | // listen on an endpoint untill a trace is received and then will
11 | // return that parsed trace
12 | type FakeCollector struct {
13 | Endpoint string
14 | }
15 |
16 | // Listen on the endpoint for one trace and push it to outChannel
17 | func (fc *FakeCollector) Listen(outChannel chan *protocol.Trace) {
18 | ln, err := net.Listen("tcp", fc.Endpoint)
19 | if err != nil {
20 | outChannel <- nil
21 | return
22 | }
23 | defer ln.Close()
24 | conn, err := ln.Accept()
25 | if err != nil {
26 | outChannel <- nil
27 | return
28 | }
29 | defer conn.Close()
30 | var buf = make([]byte, 0)
31 | _, err = conn.Read(buf)
32 | if err != nil {
33 | outChannel <- nil
34 | return
35 | }
36 | var receivedTrace protocol.Trace
37 | err = json.Unmarshal(buf, &receivedTrace)
38 | if err != nil {
39 | outChannel <- nil
40 | return
41 | }
42 | outChannel <- &receivedTrace
43 | }
44 |
--------------------------------------------------------------------------------
/protocol/error_code.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: error_code.proto
3 |
4 | package protocol
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
22 |
23 | type ErrorCode int32
24 |
25 | const (
26 | ErrorCode_OK ErrorCode = 0
27 | ErrorCode_ERROR ErrorCode = 1
28 | ErrorCode_EXCEPTION ErrorCode = 2
29 | )
30 |
31 | var ErrorCode_name = map[int32]string{
32 | 0: "OK",
33 | 1: "ERROR",
34 | 2: "EXCEPTION",
35 | }
36 |
37 | var ErrorCode_value = map[string]int32{
38 | "OK": 0,
39 | "ERROR": 1,
40 | "EXCEPTION": 2,
41 | }
42 |
43 | func (x ErrorCode) String() string {
44 | return proto.EnumName(ErrorCode_name, int32(x))
45 | }
46 |
47 | func (ErrorCode) EnumDescriptor() ([]byte, []int) {
48 | return fileDescriptor_c5513ac0a8e17e40, []int{0}
49 | }
50 |
51 | func init() {
52 | proto.RegisterEnum("protocol.ErrorCode", ErrorCode_name, ErrorCode_value)
53 | }
54 |
55 | func init() { proto.RegisterFile("error_code.proto", fileDescriptor_c5513ac0a8e17e40) }
56 |
57 | var fileDescriptor_c5513ac0a8e17e40 = []byte{
58 | // 99 bytes of a gzipped FileDescriptorProto
59 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x2d, 0x2a, 0xca,
60 | 0x2f, 0x8a, 0x4f, 0xce, 0x4f, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x00, 0x53,
61 | 0xc9, 0xf9, 0x39, 0x5a, 0xba, 0x5c, 0x9c, 0xae, 0x20, 0x59, 0xe7, 0xfc, 0x94, 0x54, 0x21, 0x36,
62 | 0x2e, 0x26, 0x7f, 0x6f, 0x01, 0x06, 0x21, 0x4e, 0x2e, 0x56, 0xd7, 0xa0, 0x20, 0xff, 0x20, 0x01,
63 | 0x46, 0x21, 0x5e, 0x2e, 0x4e, 0xd7, 0x08, 0x67, 0xd7, 0x80, 0x10, 0x4f, 0x7f, 0x3f, 0x01, 0xa6,
64 | 0x24, 0x36, 0xb0, 0x46, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xec, 0xf2, 0x6c, 0x0e, 0x53,
65 | 0x00, 0x00, 0x00,
66 | }
67 |
--------------------------------------------------------------------------------
/protocol/event.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: event.proto
3 |
4 | package protocol
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
22 |
23 | type Resource struct {
24 | Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
25 | Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
26 | Operation string `protobuf:"bytes,3,opt,name=operation,proto3" json:"operation,omitempty"`
27 | Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
28 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
29 | XXX_unrecognized []byte `json:"-"`
30 | XXX_sizecache int32 `json:"-"`
31 | }
32 |
33 | func (m *Resource) Reset() { *m = Resource{} }
34 | func (m *Resource) String() string { return proto.CompactTextString(m) }
35 | func (*Resource) ProtoMessage() {}
36 | func (*Resource) Descriptor() ([]byte, []int) {
37 | return fileDescriptor_2d17a9d3f0ddf27e, []int{0}
38 | }
39 |
40 | func (m *Resource) XXX_Unmarshal(b []byte) error {
41 | return xxx_messageInfo_Resource.Unmarshal(m, b)
42 | }
43 | func (m *Resource) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
44 | return xxx_messageInfo_Resource.Marshal(b, m, deterministic)
45 | }
46 | func (m *Resource) XXX_Merge(src proto.Message) {
47 | xxx_messageInfo_Resource.Merge(m, src)
48 | }
49 | func (m *Resource) XXX_Size() int {
50 | return xxx_messageInfo_Resource.Size(m)
51 | }
52 | func (m *Resource) XXX_DiscardUnknown() {
53 | xxx_messageInfo_Resource.DiscardUnknown(m)
54 | }
55 |
56 | var xxx_messageInfo_Resource proto.InternalMessageInfo
57 |
58 | func (m *Resource) GetName() string {
59 | if m != nil {
60 | return m.Name
61 | }
62 | return ""
63 | }
64 |
65 | func (m *Resource) GetType() string {
66 | if m != nil {
67 | return m.Type
68 | }
69 | return ""
70 | }
71 |
72 | func (m *Resource) GetOperation() string {
73 | if m != nil {
74 | return m.Operation
75 | }
76 | return ""
77 | }
78 |
79 | func (m *Resource) GetMetadata() map[string]string {
80 | if m != nil {
81 | return m.Metadata
82 | }
83 | return nil
84 | }
85 |
86 | type Event struct {
87 | Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
88 | StartTime float64 `protobuf:"fixed64,2,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
89 | Resource *Resource `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"`
90 | Origin string `protobuf:"bytes,4,opt,name=origin,proto3" json:"origin,omitempty"`
91 | Duration float64 `protobuf:"fixed64,5,opt,name=duration,proto3" json:"duration,omitempty"`
92 | ErrorCode ErrorCode `protobuf:"varint,6,opt,name=error_code,json=errorCode,proto3,enum=protocol.ErrorCode" json:"error_code,omitempty"`
93 | Exception *Exception `protobuf:"bytes,7,opt,name=exception,proto3" json:"exception,omitempty"`
94 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
95 | XXX_unrecognized []byte `json:"-"`
96 | XXX_sizecache int32 `json:"-"`
97 | }
98 |
99 | func (m *Event) Reset() { *m = Event{} }
100 | func (m *Event) String() string { return proto.CompactTextString(m) }
101 | func (*Event) ProtoMessage() {}
102 | func (*Event) Descriptor() ([]byte, []int) {
103 | return fileDescriptor_2d17a9d3f0ddf27e, []int{1}
104 | }
105 |
106 | func (m *Event) XXX_Unmarshal(b []byte) error {
107 | return xxx_messageInfo_Event.Unmarshal(m, b)
108 | }
109 | func (m *Event) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
110 | return xxx_messageInfo_Event.Marshal(b, m, deterministic)
111 | }
112 | func (m *Event) XXX_Merge(src proto.Message) {
113 | xxx_messageInfo_Event.Merge(m, src)
114 | }
115 | func (m *Event) XXX_Size() int {
116 | return xxx_messageInfo_Event.Size(m)
117 | }
118 | func (m *Event) XXX_DiscardUnknown() {
119 | xxx_messageInfo_Event.DiscardUnknown(m)
120 | }
121 |
122 | var xxx_messageInfo_Event proto.InternalMessageInfo
123 |
124 | func (m *Event) GetId() string {
125 | if m != nil {
126 | return m.Id
127 | }
128 | return ""
129 | }
130 |
131 | func (m *Event) GetStartTime() float64 {
132 | if m != nil {
133 | return m.StartTime
134 | }
135 | return 0
136 | }
137 |
138 | func (m *Event) GetResource() *Resource {
139 | if m != nil {
140 | return m.Resource
141 | }
142 | return nil
143 | }
144 |
145 | func (m *Event) GetOrigin() string {
146 | if m != nil {
147 | return m.Origin
148 | }
149 | return ""
150 | }
151 |
152 | func (m *Event) GetDuration() float64 {
153 | if m != nil {
154 | return m.Duration
155 | }
156 | return 0
157 | }
158 |
159 | func (m *Event) GetErrorCode() ErrorCode {
160 | if m != nil {
161 | return m.ErrorCode
162 | }
163 | return ErrorCode_OK
164 | }
165 |
166 | func (m *Event) GetException() *Exception {
167 | if m != nil {
168 | return m.Exception
169 | }
170 | return nil
171 | }
172 |
173 | func init() {
174 | proto.RegisterType((*Resource)(nil), "protocol.Resource")
175 | proto.RegisterMapType((map[string]string)(nil), "protocol.Resource.MetadataEntry")
176 | proto.RegisterType((*Event)(nil), "protocol.Event")
177 | }
178 |
179 | func init() { proto.RegisterFile("event.proto", fileDescriptor_2d17a9d3f0ddf27e) }
180 |
181 | var fileDescriptor_2d17a9d3f0ddf27e = []byte{
182 | // 317 bytes of a gzipped FileDescriptorProto
183 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x51, 0x4f, 0x4b, 0xfc, 0x30,
184 | 0x14, 0xa4, 0xdd, 0x3f, 0xbf, 0xf6, 0x2d, 0xbf, 0x75, 0x79, 0x8a, 0x84, 0xa2, 0x50, 0xf6, 0xb4,
185 | 0xa7, 0x82, 0xf5, 0x22, 0xea, 0x4d, 0xf6, 0xe8, 0x25, 0x78, 0x5f, 0x62, 0xfb, 0x90, 0xe0, 0xb6,
186 | 0x29, 0xd9, 0xec, 0x62, 0x8f, 0x7e, 0x3e, 0xbf, 0x94, 0x24, 0x4d, 0x5b, 0xc5, 0x53, 0xe7, 0xcd,
187 | 0xbc, 0xe9, 0x4c, 0x12, 0x58, 0xd0, 0x89, 0x6a, 0x93, 0x35, 0x5a, 0x19, 0x85, 0x91, 0xfb, 0x14,
188 | 0x6a, 0x9f, 0xac, 0x48, 0x6b, 0xa5, 0x77, 0x85, 0x2a, 0xa9, 0xd3, 0x92, 0x33, 0xfa, 0x28, 0xa8,
189 | 0x31, 0x52, 0xd5, 0x1d, 0xb1, 0xfe, 0x0a, 0x20, 0xe2, 0x74, 0x50, 0x47, 0x5d, 0x10, 0x22, 0x4c,
190 | 0x6b, 0x51, 0x11, 0x0b, 0xd2, 0x60, 0x13, 0x73, 0x87, 0x2d, 0x67, 0xda, 0x86, 0x58, 0xd8, 0x71,
191 | 0x16, 0xe3, 0x15, 0xc4, 0xaa, 0x21, 0x2d, 0xec, 0x7f, 0xd8, 0xc4, 0x09, 0x23, 0x81, 0x8f, 0x10,
192 | 0x55, 0x64, 0x44, 0x29, 0x8c, 0x60, 0xd3, 0x74, 0xb2, 0x59, 0xe4, 0x69, 0xd6, 0x57, 0xca, 0xfa,
193 | 0xac, 0xec, 0xd9, 0xaf, 0x6c, 0x6b, 0xa3, 0x5b, 0x3e, 0x38, 0x92, 0x07, 0xf8, 0xff, 0x4b, 0xc2,
194 | 0x15, 0x4c, 0xde, 0xa9, 0xf5, 0x9d, 0x2c, 0xc4, 0x0b, 0x98, 0x9d, 0xc4, 0xfe, 0xd8, 0x77, 0xea,
195 | 0x86, 0xfb, 0xf0, 0x2e, 0x58, 0x7f, 0x86, 0x30, 0xdb, 0xda, 0xab, 0xc0, 0x25, 0x84, 0xb2, 0xf4,
196 | 0xa6, 0x50, 0x96, 0x78, 0x0d, 0x70, 0x30, 0x42, 0x9b, 0x9d, 0x91, 0x55, 0x67, 0x0c, 0x78, 0xec,
197 | 0x98, 0x17, 0x59, 0x11, 0x66, 0x10, 0x69, 0xdf, 0xcc, 0x1d, 0x68, 0x91, 0xe3, 0xdf, 0xce, 0x7c,
198 | 0xd8, 0xc1, 0x4b, 0x98, 0x2b, 0x2d, 0xdf, 0x64, 0xcd, 0xa6, 0x2e, 0xc2, 0x4f, 0x98, 0x40, 0x54,
199 | 0x1e, 0xfd, 0xc5, 0xcc, 0x5c, 0xc8, 0x30, 0x63, 0x0e, 0x30, 0xbe, 0x07, 0x9b, 0xa7, 0xc1, 0x66,
200 | 0x99, 0x9f, 0x8f, 0x29, 0x5b, 0xab, 0x3d, 0xa9, 0x92, 0x78, 0x4c, 0x3d, 0xc4, 0x1b, 0x88, 0x87,
201 | 0x17, 0x63, 0xff, 0x5c, 0xb1, 0x9f, 0x96, 0x5e, 0xe2, 0xe3, 0xd6, 0xeb, 0xdc, 0xc9, 0xb7, 0xdf,
202 | 0x01, 0x00, 0x00, 0xff, 0xff, 0xad, 0xfc, 0xcc, 0x8a, 0x14, 0x02, 0x00, 0x00,
203 | }
204 |
--------------------------------------------------------------------------------
/protocol/exception.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: exception.proto
3 |
4 | package protocol
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
22 |
23 | type Exception struct {
24 | Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
25 | Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
26 | Traceback string `protobuf:"bytes,3,opt,name=traceback,proto3" json:"traceback,omitempty"`
27 | Time float64 `protobuf:"fixed64,4,opt,name=time,proto3" json:"time,omitempty"`
28 | AdditionalData map[string]string `protobuf:"bytes,5,rep,name=additional_data,json=additionalData,proto3" json:"additional_data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
29 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
30 | XXX_unrecognized []byte `json:"-"`
31 | XXX_sizecache int32 `json:"-"`
32 | }
33 |
34 | func (m *Exception) Reset() { *m = Exception{} }
35 | func (m *Exception) String() string { return proto.CompactTextString(m) }
36 | func (*Exception) ProtoMessage() {}
37 | func (*Exception) Descriptor() ([]byte, []int) {
38 | return fileDescriptor_fb6dc2e8aaa76c47, []int{0}
39 | }
40 |
41 | func (m *Exception) XXX_Unmarshal(b []byte) error {
42 | return xxx_messageInfo_Exception.Unmarshal(m, b)
43 | }
44 | func (m *Exception) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
45 | return xxx_messageInfo_Exception.Marshal(b, m, deterministic)
46 | }
47 | func (m *Exception) XXX_Merge(src proto.Message) {
48 | xxx_messageInfo_Exception.Merge(m, src)
49 | }
50 | func (m *Exception) XXX_Size() int {
51 | return xxx_messageInfo_Exception.Size(m)
52 | }
53 | func (m *Exception) XXX_DiscardUnknown() {
54 | xxx_messageInfo_Exception.DiscardUnknown(m)
55 | }
56 |
57 | var xxx_messageInfo_Exception proto.InternalMessageInfo
58 |
59 | func (m *Exception) GetType() string {
60 | if m != nil {
61 | return m.Type
62 | }
63 | return ""
64 | }
65 |
66 | func (m *Exception) GetMessage() string {
67 | if m != nil {
68 | return m.Message
69 | }
70 | return ""
71 | }
72 |
73 | func (m *Exception) GetTraceback() string {
74 | if m != nil {
75 | return m.Traceback
76 | }
77 | return ""
78 | }
79 |
80 | func (m *Exception) GetTime() float64 {
81 | if m != nil {
82 | return m.Time
83 | }
84 | return 0
85 | }
86 |
87 | func (m *Exception) GetAdditionalData() map[string]string {
88 | if m != nil {
89 | return m.AdditionalData
90 | }
91 | return nil
92 | }
93 |
94 | func init() {
95 | proto.RegisterType((*Exception)(nil), "protocol.Exception")
96 | proto.RegisterMapType((map[string]string)(nil), "protocol.Exception.AdditionalDataEntry")
97 | }
98 |
99 | func init() { proto.RegisterFile("exception.proto", fileDescriptor_fb6dc2e8aaa76c47) }
100 |
101 | var fileDescriptor_fb6dc2e8aaa76c47 = []byte{
102 | // 210 bytes of a gzipped FileDescriptorProto
103 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0xad, 0x48, 0x4e,
104 | 0x2d, 0x28, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x00, 0x53, 0xc9,
105 | 0xf9, 0x39, 0x4a, 0x0d, 0x4c, 0x5c, 0x9c, 0xae, 0x30, 0x59, 0x21, 0x21, 0x2e, 0x96, 0x92, 0xca,
106 | 0x82, 0x54, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x30, 0x5b, 0x48, 0x82, 0x8b, 0x3d, 0x37,
107 | 0xb5, 0xb8, 0x38, 0x31, 0x3d, 0x55, 0x82, 0x09, 0x2c, 0x0c, 0xe3, 0x0a, 0xc9, 0x70, 0x71, 0x96,
108 | 0x14, 0x25, 0x26, 0xa7, 0x26, 0x25, 0x26, 0x67, 0x4b, 0x30, 0x83, 0xe5, 0x10, 0x02, 0x60, 0xb3,
109 | 0x32, 0x73, 0x53, 0x25, 0x58, 0x14, 0x18, 0x35, 0x18, 0x83, 0xc0, 0x6c, 0xa1, 0x00, 0x2e, 0xfe,
110 | 0xc4, 0x94, 0x94, 0x4c, 0x90, 0x5d, 0x89, 0x39, 0xf1, 0x29, 0x89, 0x25, 0x89, 0x12, 0xac, 0x0a,
111 | 0xcc, 0x1a, 0xdc, 0x46, 0xea, 0x7a, 0x30, 0x17, 0xe9, 0xc1, 0x5d, 0xa3, 0xe7, 0x08, 0x57, 0xea,
112 | 0x92, 0x58, 0x92, 0xe8, 0x9a, 0x57, 0x52, 0x54, 0x19, 0xc4, 0x97, 0x88, 0x22, 0x28, 0xe5, 0xc8,
113 | 0x25, 0x8c, 0x45, 0x99, 0x90, 0x00, 0x17, 0x73, 0x76, 0x6a, 0x25, 0xd4, 0x1f, 0x20, 0xa6, 0x90,
114 | 0x08, 0x17, 0x6b, 0x59, 0x62, 0x4e, 0x29, 0xcc, 0x13, 0x10, 0x8e, 0x15, 0x93, 0x05, 0x63, 0x12,
115 | 0x1b, 0xd8, 0x6a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbb, 0x2e, 0x17, 0x93, 0x26, 0x01,
116 | 0x00, 0x00,
117 | }
118 |
--------------------------------------------------------------------------------
/protocol/timestamp.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: timestamp.proto
3 |
4 | package protocol
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
22 |
23 | type Timestamp struct {
24 | Seconds int64 `protobuf:"varint,1,opt,name=seconds,proto3" json:"seconds,omitempty"`
25 | Nanos int32 `protobuf:"varint,2,opt,name=nanos,proto3" json:"nanos,omitempty"`
26 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
27 | XXX_unrecognized []byte `json:"-"`
28 | XXX_sizecache int32 `json:"-"`
29 | }
30 |
31 | func (m *Timestamp) Reset() { *m = Timestamp{} }
32 | func (m *Timestamp) String() string { return proto.CompactTextString(m) }
33 | func (*Timestamp) ProtoMessage() {}
34 | func (*Timestamp) Descriptor() ([]byte, []int) {
35 | return fileDescriptor_edac929d8ae1e24f, []int{0}
36 | }
37 |
38 | func (m *Timestamp) XXX_Unmarshal(b []byte) error {
39 | return xxx_messageInfo_Timestamp.Unmarshal(m, b)
40 | }
41 | func (m *Timestamp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
42 | return xxx_messageInfo_Timestamp.Marshal(b, m, deterministic)
43 | }
44 | func (m *Timestamp) XXX_Merge(src proto.Message) {
45 | xxx_messageInfo_Timestamp.Merge(m, src)
46 | }
47 | func (m *Timestamp) XXX_Size() int {
48 | return xxx_messageInfo_Timestamp.Size(m)
49 | }
50 | func (m *Timestamp) XXX_DiscardUnknown() {
51 | xxx_messageInfo_Timestamp.DiscardUnknown(m)
52 | }
53 |
54 | var xxx_messageInfo_Timestamp proto.InternalMessageInfo
55 |
56 | func (m *Timestamp) GetSeconds() int64 {
57 | if m != nil {
58 | return m.Seconds
59 | }
60 | return 0
61 | }
62 |
63 | func (m *Timestamp) GetNanos() int32 {
64 | if m != nil {
65 | return m.Nanos
66 | }
67 | return 0
68 | }
69 |
70 | func init() {
71 | proto.RegisterType((*Timestamp)(nil), "protocol.Timestamp")
72 | }
73 |
74 | func init() { proto.RegisterFile("timestamp.proto", fileDescriptor_edac929d8ae1e24f) }
75 |
76 | var fileDescriptor_edac929d8ae1e24f = []byte{
77 | // 97 bytes of a gzipped FileDescriptorProto
78 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x2f, 0xc9, 0xcc, 0x4d,
79 | 0x2d, 0x2e, 0x49, 0xcc, 0x2d, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x00, 0x53, 0xc9,
80 | 0xf9, 0x39, 0x4a, 0xd6, 0x5c, 0x9c, 0x21, 0x30, 0x49, 0x21, 0x09, 0x2e, 0xf6, 0xe2, 0xd4, 0xe4,
81 | 0xfc, 0xbc, 0x94, 0x62, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xe6, 0x20, 0x18, 0x57, 0x48, 0x84, 0x8b,
82 | 0x35, 0x2f, 0x31, 0x2f, 0xbf, 0x58, 0x82, 0x49, 0x81, 0x51, 0x83, 0x35, 0x08, 0xc2, 0x49, 0x62,
83 | 0x03, 0x1b, 0x63, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x41, 0x20, 0x05, 0x1a, 0x60, 0x00, 0x00,
84 | 0x00,
85 | }
86 |
--------------------------------------------------------------------------------
/protocol/trace.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // source: trace.proto
3 |
4 | package protocol
5 |
6 | import (
7 | fmt "fmt"
8 | proto "github.com/golang/protobuf/proto"
9 | math "math"
10 | )
11 |
12 | // Reference imports to suppress errors if they are not otherwise used.
13 | var _ = proto.Marshal
14 | var _ = fmt.Errorf
15 | var _ = math.Inf
16 |
17 | // This is a compile-time assertion to ensure that this generated file
18 | // is compatible with the proto package it is being compiled against.
19 | // A compilation error at this line likely means your copy of the
20 | // proto package needs to be updated.
21 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
22 |
23 | type Trace struct {
24 | AppName string `protobuf:"bytes,1,opt,name=app_name,json=appName,proto3" json:"app_name,omitempty"`
25 | Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"`
26 | Events []*Event `protobuf:"bytes,3,rep,name=events,proto3" json:"events,omitempty"`
27 | Exceptions []*Exception `protobuf:"bytes,4,rep,name=exceptions,proto3" json:"exceptions,omitempty"`
28 | Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"`
29 | Platform string `protobuf:"bytes,6,opt,name=platform,proto3" json:"platform,omitempty"`
30 | XXX_NoUnkeyedLiteral struct{} `json:"-"`
31 | XXX_unrecognized []byte `json:"-"`
32 | XXX_sizecache int32 `json:"-"`
33 | }
34 |
35 | func (m *Trace) Reset() { *m = Trace{} }
36 | func (m *Trace) String() string { return proto.CompactTextString(m) }
37 | func (*Trace) ProtoMessage() {}
38 | func (*Trace) Descriptor() ([]byte, []int) {
39 | return fileDescriptor_0571941a1d628a80, []int{0}
40 | }
41 |
42 | func (m *Trace) XXX_Unmarshal(b []byte) error {
43 | return xxx_messageInfo_Trace.Unmarshal(m, b)
44 | }
45 | func (m *Trace) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
46 | return xxx_messageInfo_Trace.Marshal(b, m, deterministic)
47 | }
48 | func (m *Trace) XXX_Merge(src proto.Message) {
49 | xxx_messageInfo_Trace.Merge(m, src)
50 | }
51 | func (m *Trace) XXX_Size() int {
52 | return xxx_messageInfo_Trace.Size(m)
53 | }
54 | func (m *Trace) XXX_DiscardUnknown() {
55 | xxx_messageInfo_Trace.DiscardUnknown(m)
56 | }
57 |
58 | var xxx_messageInfo_Trace proto.InternalMessageInfo
59 |
60 | func (m *Trace) GetAppName() string {
61 | if m != nil {
62 | return m.AppName
63 | }
64 | return ""
65 | }
66 |
67 | func (m *Trace) GetToken() string {
68 | if m != nil {
69 | return m.Token
70 | }
71 | return ""
72 | }
73 |
74 | func (m *Trace) GetEvents() []*Event {
75 | if m != nil {
76 | return m.Events
77 | }
78 | return nil
79 | }
80 |
81 | func (m *Trace) GetExceptions() []*Exception {
82 | if m != nil {
83 | return m.Exceptions
84 | }
85 | return nil
86 | }
87 |
88 | func (m *Trace) GetVersion() string {
89 | if m != nil {
90 | return m.Version
91 | }
92 | return ""
93 | }
94 |
95 | func (m *Trace) GetPlatform() string {
96 | if m != nil {
97 | return m.Platform
98 | }
99 | return ""
100 | }
101 |
102 | func init() {
103 | proto.RegisterType((*Trace)(nil), "protocol.Trace")
104 | }
105 |
106 | func init() { proto.RegisterFile("trace.proto", fileDescriptor_0571941a1d628a80) }
107 |
108 | var fileDescriptor_0571941a1d628a80 = []byte{
109 | // 196 bytes of a gzipped FileDescriptorProto
110 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8e, 0x3f, 0xcf, 0x82, 0x30,
111 | 0x10, 0xc6, 0xc3, 0xcb, 0xcb, 0x1f, 0x8f, 0x81, 0xa4, 0x3a, 0x54, 0x26, 0xe2, 0x22, 0x13, 0x83,
112 | 0x7c, 0x06, 0x57, 0x07, 0xe2, 0x6e, 0x2a, 0x39, 0x13, 0x22, 0xb4, 0x97, 0xd2, 0x10, 0x3f, 0xa4,
113 | 0x1f, 0xca, 0xd0, 0x02, 0x61, 0x6a, 0x7e, 0xf7, 0x7b, 0x7a, 0xf7, 0x40, 0x62, 0xb4, 0x68, 0xb0,
114 | 0x24, 0xad, 0x8c, 0x62, 0xb1, 0x7d, 0x1a, 0xd5, 0x65, 0x29, 0x7e, 0x1a, 0x24, 0xd3, 0x2a, 0xe9,
115 | 0x54, 0x96, 0xe0, 0x88, 0xd2, 0x38, 0x38, 0x7d, 0x3d, 0x08, 0xee, 0xd3, 0x3f, 0x76, 0x84, 0x58,
116 | 0x10, 0x3d, 0xa4, 0xe8, 0x91, 0x7b, 0xb9, 0x57, 0xec, 0xea, 0x48, 0x10, 0xdd, 0x44, 0x8f, 0xec,
117 | 0x00, 0x81, 0x51, 0x6f, 0x94, 0xfc, 0xcf, 0xce, 0x1d, 0xb0, 0x33, 0x84, 0x76, 0xd3, 0xc0, 0xfd,
118 | 0xdc, 0x2f, 0x92, 0x4b, 0x5a, 0x2e, 0x37, 0xcb, 0xeb, 0x34, 0xaf, 0x67, 0xcd, 0x2a, 0x80, 0xb5,
119 | 0xc3, 0xc0, 0xff, 0x6d, 0x78, 0xbf, 0x09, 0x2f, 0xae, 0xde, 0xc4, 0x18, 0x87, 0x68, 0x44, 0x3d,
120 | 0xb4, 0x4a, 0xf2, 0xc0, 0xb5, 0x99, 0x91, 0x65, 0x10, 0x53, 0x27, 0xcc, 0x4b, 0xe9, 0x9e, 0x87,
121 | 0x56, 0xad, 0xfc, 0x0c, 0xed, 0xd6, 0xea, 0x17, 0x00, 0x00, 0xff, 0xff, 0x98, 0xec, 0x38, 0x25,
122 | 0x0c, 0x01, 0x00, 0x00,
123 | }
124 |
--------------------------------------------------------------------------------
/trace.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/epsagon/epsagon-go/d24be95976ce03ae884596af6b793e0027d60c92/trace.png
--------------------------------------------------------------------------------
/tracer/common_utils.go:
--------------------------------------------------------------------------------
1 | package tracer
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | // GetTimestamp returns the current time in miliseconds
8 | func GetTimestamp() float64 {
9 | return float64(time.Now().UnixNano()) / float64(time.Millisecond) / float64(time.Nanosecond) / 1000.0
10 | }
11 |
--------------------------------------------------------------------------------
/tracer/mask_ignored_keys.go:
--------------------------------------------------------------------------------
1 | package tracer
2 |
3 | import (
4 | "encoding/json"
5 | "reflect"
6 |
7 | "github.com/epsagon/epsagon-go/protocol"
8 | )
9 |
10 | const maskedValue = "****"
11 |
12 | func arrayToHitMap(arr []string) map[string]bool {
13 | hitMap := make(map[string]bool)
14 | for _, k := range arr {
15 | hitMap[k] = true
16 | }
17 | return hitMap
18 | }
19 |
20 | func maskNestedJSONKeys(decodedJSON interface{}, ignoredKeysMap map[string]bool) (interface{}, bool) {
21 | var changed bool
22 | decodedValue := reflect.ValueOf(decodedJSON)
23 | if decodedValue.Kind() == reflect.Invalid || decodedValue.IsZero() {
24 | return decodedJSON, false
25 | }
26 | switch decodedValue.Kind() {
27 | case reflect.Array, reflect.Slice:
28 | for i := 0; i < decodedValue.Len(); i++ {
29 | nestedValue := decodedValue.Index(i)
30 | newNestedValue, indexChanged := maskNestedJSONKeys(nestedValue.Interface(), ignoredKeysMap)
31 | if indexChanged {
32 | nestedValue.Set(reflect.ValueOf(newNestedValue))
33 | changed = true
34 | }
35 | }
36 | case reflect.Map:
37 | for _, key := range decodedValue.MapKeys() {
38 | if ignoredKeysMap[key.String()] {
39 | decodedValue.SetMapIndex(key, reflect.ValueOf(maskedValue))
40 | changed = true
41 | } else {
42 | nestedValue := decodedValue.MapIndex(key)
43 | newNestedValue, valueChanged := maskNestedJSONKeys(nestedValue.Interface(), ignoredKeysMap)
44 | if valueChanged {
45 | decodedValue.SetMapIndex(key, reflect.ValueOf(newNestedValue))
46 | changed = true
47 | }
48 | }
49 | }
50 | }
51 | return decodedValue.Interface(), changed
52 | }
53 |
54 | // maskIgnoredKeys masks all the keys in the
55 | // event resource metadata that are in ignoredKeys, swapping them with '****'.
56 | // Metadata values that are json decodable will have their nested keys masked as well.
57 | func (tracer *epsagonTracer) maskEventIgnoredKeys(event *protocol.Event, ignoredKeys []string) {
58 | ignoredKeysMap := arrayToHitMap(ignoredKeys)
59 | for key, value := range event.Resource.Metadata {
60 | if ignoredKeysMap[key] {
61 | event.Resource.Metadata[key] = maskedValue
62 | } else {
63 | var decodedJSON interface{}
64 | err := json.Unmarshal([]byte(value), &decodedJSON)
65 | if err == nil {
66 | newValue, changed := maskNestedJSONKeys(decodedJSON, ignoredKeysMap)
67 | if changed {
68 | encodedNewValue, err := json.Marshal(newValue)
69 | if err == nil {
70 | event.Resource.Metadata[key] = string(encodedNewValue)
71 | } else {
72 | exception := createException("internal json encode error", err.Error())
73 | if tracer.Stopped() {
74 | tracer.exceptions = append(tracer.exceptions, exception)
75 | } else {
76 | tracer.AddException(exception)
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tracer/mask_ignored_keys_test.go:
--------------------------------------------------------------------------------
1 | package tracer
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/epsagon/epsagon-go/protocol"
7 | . "github.com/onsi/ginkgo"
8 | . "github.com/onsi/gomega"
9 | )
10 |
11 | var _ = Describe("mask_ignored_keys", func() {
12 | Describe("maskEventIgnoredKeys", func() {
13 | config := &Config{
14 | Disable: true,
15 | }
16 | testTracer := CreateTracer(config).(*epsagonTracer)
17 | Context("sanity", func() {
18 | It("masks sanity test", func() {
19 | event := &protocol.Event{
20 | Resource: &protocol.Resource{
21 | Metadata: map[string]string{
22 | "not-ignored": "hello",
23 | "ignored": "bye",
24 | },
25 | },
26 | }
27 | ignoredKeys := []string{"ignored"}
28 | testTracer.Config.IgnoredKeys = ignoredKeys
29 | testTracer.maskEventIgnoredKeys(event, ignoredKeys)
30 | Expect(event.Resource.Metadata["not-ignored"]).To(Equal("hello"))
31 | Expect(event.Resource.Metadata["ignored"]).To(Equal(maskedValue))
32 | })
33 | })
34 | Context("test matrix", func() {
35 | type testCase struct {
36 | metadata map[string]string
37 | ignoredKeys []string
38 | expectedMetadata map[string]string
39 | }
40 | testMatrix := map[string]testCase{
41 | "handles empty metadata": {
42 | metadata: map[string]string{},
43 | ignoredKeys: []string{"ignored"},
44 | expectedMetadata: map[string]string{},
45 | },
46 | "handles empty ignoredKeys": {
47 | metadata: map[string]string{"ignored": "bye"},
48 | ignoredKeys: []string{},
49 | expectedMetadata: map[string]string{"ignored": "bye"},
50 | },
51 | "passes matrix sanity": {
52 | metadata: map[string]string{"to-be-ignored": "bye", "not-ignored": "hello"},
53 | ignoredKeys: []string{"to-be-ignored"},
54 | expectedMetadata: map[string]string{"to-be-ignored": maskedValue, "not-ignored": "hello"},
55 | },
56 | "handles json map without ignored keys": {
57 | metadata: map[string]string{"not-ignored": "hello", "other-not-ignored": "{\"hello\":\"world\"}"},
58 | ignoredKeys: []string{"to-be-ignored"},
59 | expectedMetadata: map[string]string{"not-ignored": "hello", "other-not-ignored": "{\"hello\":\"world\"}"},
60 | },
61 | "handles json map *with* ignored keys": {
62 | metadata: map[string]string{"not-ignored": "hello", "other-not-ignored": "{\"to-be-ignored\":\"world\"}"},
63 | ignoredKeys: []string{"to-be-ignored"},
64 | expectedMetadata: map[string]string{"not-ignored": "hello", "other-not-ignored": fmt.Sprintf("{\"to-be-ignored\":\"%s\"}", maskedValue)},
65 | },
66 | "handles json nested array and map without ignored keys": {
67 | metadata: map[string]string{"not-ignored": "hello", "other-not-ignored": "[{\"hello\":\"world\"},{\"erez\":\"is-cool\"}]"},
68 | ignoredKeys: []string{"to-be-ignored"},
69 | expectedMetadata: map[string]string{"not-ignored": "hello", "other-not-ignored": "[{\"hello\":\"world\"},{\"erez\":\"is-cool\"}]"},
70 | },
71 | "handles json nested array and map *with* ignored keys": {
72 | metadata: map[string]string{
73 | "not-ignored": "hello",
74 | "other-not-ignored": "[{\"to-be-ignored\":\"world\"},{\"erez\":\"is-cool\"}]",
75 | },
76 | ignoredKeys: []string{"to-be-ignored"},
77 | expectedMetadata: map[string]string{
78 | "not-ignored": "hello",
79 | "other-not-ignored": fmt.Sprintf(
80 | "[{\"to-be-ignored\":\"%s\"},{\"erez\":\"is-cool\"}]",
81 | maskedValue),
82 | },
83 | },
84 | "handles json nested map *with* ignored keys": {
85 | metadata: map[string]string{
86 | "not-ignored": "hello",
87 | "other-not-ignored": "{\"wait\":{\"for\":{\"it\":{\"to-be-ignored\":\"boom\"},\"not\":[\"I\",\"are\",\"baboon\"]},\"not\":\"it\"}}",
88 | },
89 | ignoredKeys: []string{"to-be-ignored"},
90 | expectedMetadata: map[string]string{
91 | "not-ignored": "hello",
92 | "other-not-ignored": fmt.Sprintf(
93 | "{\"wait\":{\"for\":{\"it\":{\"to-be-ignored\":\"%s\"},\"not\":[\"I\",\"are\",\"baboon\"]},\"not\":\"it\"}}",
94 | maskedValue,
95 | ),
96 | },
97 | },
98 | }
99 | for testName, value := range testMatrix {
100 | value := value
101 | It(testName, func() {
102 | testMetadata := make(map[string]string)
103 | for k, v := range value.metadata {
104 | testMetadata[k] = v
105 | }
106 | event := &protocol.Event{
107 | Resource: &protocol.Resource{
108 | Metadata: testMetadata,
109 | },
110 | }
111 | testTracer.maskEventIgnoredKeys(event, value.ignoredKeys)
112 | Expect(event.Resource.Metadata).To(Equal(value.expectedMetadata))
113 | })
114 | }
115 | })
116 | })
117 | })
118 |
--------------------------------------------------------------------------------
/tracer/mocked_tracer.go:
--------------------------------------------------------------------------------
1 | package tracer
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/epsagon/epsagon-go/protocol"
7 | )
8 |
9 | // MockedEpsagonTracer will not send traces if closed
10 | type MockedEpsagonTracer struct {
11 | Exceptions *[]*protocol.Exception
12 | Events *[]*protocol.Event
13 | Labels map[string]interface{}
14 | RunnerException *protocol.Exception
15 | Config *Config
16 |
17 | PanicStart bool
18 | PanicAddEvent bool
19 | PanicAddException bool
20 | PanicStop bool
21 | DelayAddEvent bool
22 | DelayedEventsChan chan bool
23 | stopped bool
24 | }
25 |
26 | // Start implementes mocked Start
27 | func (t *MockedEpsagonTracer) Start() {
28 | if t.PanicStart {
29 | panic("panic in Start()")
30 | }
31 | t.stopped = false
32 | }
33 |
34 | // Running implementes mocked Running
35 | func (t *MockedEpsagonTracer) Running() bool {
36 | return false
37 | }
38 |
39 | // Stop implementes mocked Stop
40 | func (t *MockedEpsagonTracer) SendStopSignal() {
41 | if t.PanicStop {
42 | panic("panic in Stop()")
43 | }
44 | t.stopped = true
45 | }
46 |
47 | // Stop implementes mocked Stop
48 | func (t *MockedEpsagonTracer) Stop() {
49 | if t.PanicStop {
50 | panic("panic in Stop()")
51 | }
52 | t.stopped = true
53 | }
54 |
55 | // Stopped implementes mocked Stopped
56 | func (t *MockedEpsagonTracer) Stopped() bool {
57 | return t.stopped
58 | }
59 |
60 | // AddEvent implementes mocked AddEvent
61 | func (t *MockedEpsagonTracer) AddEvent(e *protocol.Event) {
62 | if t.PanicAddEvent {
63 | panic("panic in AddEvent()")
64 | }
65 | if t.DelayAddEvent {
66 | go func() {
67 | time.Sleep(time.Second)
68 | *t.Events = append(*t.Events, e)
69 | t.DelayedEventsChan <- true
70 | }()
71 | } else {
72 | *t.Events = append(*t.Events, e)
73 | }
74 | }
75 |
76 | // AddException implementes mocked AddEvent
77 | func (t *MockedEpsagonTracer) AddException(e *protocol.Exception) {
78 | if t.PanicAddException {
79 | panic("panic in AddException()")
80 | }
81 | *t.Exceptions = append(*t.Exceptions, e)
82 | }
83 |
84 | // GetConfig implementes mocked AddEvent
85 | func (t *MockedEpsagonTracer) GetConfig() *Config {
86 | return t.Config
87 | }
88 |
89 | // AddExceptionTypeAndMessage implements AddExceptionTypeAndMessage
90 | func (t *MockedEpsagonTracer) AddExceptionTypeAndMessage(exceptionType, msg string) {
91 | t.AddException(&protocol.Exception{
92 | Type: exceptionType,
93 | Message: msg,
94 | Time: GetTimestamp(),
95 | })
96 | }
97 |
98 | // AddLabel implements AddLabel
99 | func (t *MockedEpsagonTracer) AddLabel(key string, value interface{}) {
100 | t.Labels[key] = value
101 | }
102 |
103 | // verifyLabel implements verifyLabel
104 | func (t *MockedEpsagonTracer) verifyLabel(label epsagonLabel) bool {
105 | return true
106 | }
107 |
108 | // AddError implements AddError
109 | func (t *MockedEpsagonTracer) AddError(errorType string, value interface{}) {
110 | t.RunnerException = &protocol.Exception{
111 | Type: errorType,
112 | Message: "test",
113 | }
114 | }
115 |
116 | // GetRunnerEvent implements AddError
117 | func (t *MockedEpsagonTracer) GetRunnerEvent() *protocol.Event {
118 | for _, event := range *t.Events {
119 | if event.Origin == "runner" {
120 | return event
121 | }
122 | }
123 | return nil
124 | }
125 |
--------------------------------------------------------------------------------
/tracer/tracer_test.go:
--------------------------------------------------------------------------------
1 | package tracer_test
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "log"
10 | "net/http"
11 | "net/http/httptest"
12 | "os"
13 | "strings"
14 |
15 | "testing"
16 | "time"
17 |
18 | "github.com/epsagon/epsagon-go/epsagon"
19 | "github.com/epsagon/epsagon-go/protocol"
20 | "github.com/epsagon/epsagon-go/tracer"
21 | . "github.com/onsi/ginkgo"
22 | . "github.com/onsi/gomega"
23 | )
24 |
25 | func TestEpsagonTracer(t *testing.T) {
26 | RegisterFailHandler(Fail)
27 | RunSpecs(t, "Epsagon Core Suite")
28 | }
29 |
30 | var _ = Describe("epsagonTracer suite", func() {
31 | Describe("Run/Stop", func() {
32 | })
33 | Describe("AddEvent", func() {
34 | })
35 | Describe("AddException", func() {
36 | })
37 | Describe("sendTraces", func() {
38 | })
39 | })
40 |
41 | func runWithTracer(endpoint string, operations func()) {
42 | tracer.GlobalTracer = nil
43 | tracer.CreateGlobalTracer(&tracer.Config{
44 | CollectorURL: endpoint,
45 | Token: "1",
46 | })
47 | tracer.GlobalTracer.Start()
48 | defer tracer.StopGlobalTracer()
49 | operations()
50 | }
51 |
52 | // testWithTracer runs a test with
53 | func testWithTracer(timeout *time.Duration, operations func()) *protocol.Trace {
54 | traceChannel := make(chan *protocol.Trace)
55 | fakeCollectorServer := httptest.NewServer(http.HandlerFunc(
56 | func(res http.ResponseWriter, req *http.Request) {
57 | buf, err := ioutil.ReadAll(req.Body)
58 | if err != nil {
59 | traceChannel <- nil
60 | return
61 | }
62 | var receivedTrace protocol.Trace
63 | err = json.Unmarshal(buf, &receivedTrace)
64 | if err != nil {
65 | traceChannel <- nil
66 | return
67 | }
68 | traceChannel <- &receivedTrace
69 | res.Write([]byte(""))
70 | },
71 | ))
72 | go runWithTracer(fakeCollectorServer.URL, operations)
73 | if timeout == nil {
74 | defaultTimeout := time.Second * 10
75 | timeout = &defaultTimeout
76 | }
77 | timer := time.NewTimer(*timeout)
78 | select {
79 | case <-timer.C:
80 | fakeCollectorServer.Close()
81 | return nil
82 | case trace := <-traceChannel:
83 | fakeCollectorServer.Close()
84 | return trace
85 | }
86 | }
87 |
88 | type stubHTTPClient struct {
89 | httpClient *http.Client
90 | PostError error
91 | }
92 |
93 | func (s stubHTTPClient) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
94 | if s.PostError != nil {
95 | return nil, s.PostError
96 | }
97 | return s.httpClient.Post(url, contentType, body)
98 | }
99 |
100 | func Test_handleSendTracesResponse(t *testing.T) {
101 | tests := []struct {
102 | name string
103 | apiResponse string
104 | apiStatusCode int
105 | httpClient stubHTTPClient
106 | expectedLog string
107 | }{
108 | {
109 | name: "No Log",
110 | apiResponse: `{"test":"valid"}`,
111 | apiStatusCode: http.StatusOK,
112 | httpClient: stubHTTPClient{
113 | httpClient: &http.Client{Timeout: time.Duration(time.Second)},
114 | },
115 | expectedLog: "",
116 | },
117 | {
118 | name: "Error With No Response",
119 | httpClient: stubHTTPClient{
120 | httpClient: &http.Client{Timeout: time.Duration(time.Second)},
121 | PostError: fmt.Errorf("Post http://not-valid-blackole.local.test: dial tcp: lookup not-valid-blackole.local.test: no such host"),
122 | },
123 | expectedLog: fmt.Sprintf("Error while sending traces \nPost http://not-valid-blackole.local.test: dial tcp: lookup not-valid-blackole.local.test: no such host"),
124 | },
125 | {
126 | name: "Error With 5XX Response",
127 | apiResponse: `{"error":"failed to send traces"}`,
128 | httpClient: stubHTTPClient{
129 | httpClient: &http.Client{Timeout: time.Duration(time.Second)},
130 | },
131 | apiStatusCode: http.StatusInternalServerError,
132 | expectedLog: fmt.Sprintf("Error while sending traces \n{\"error\":\"failed to send traces\"}"),
133 | },
134 | }
135 | for _, test := range tests {
136 | t.Run(test.name, func(t *testing.T) {
137 | //Read the logs to a buffer
138 | buf := bytes.Buffer{}
139 | log.SetOutput(&buf)
140 | defer func() {
141 | log.SetOutput(os.Stderr)
142 | }()
143 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
144 | w.WriteHeader(test.apiStatusCode)
145 | w.Write([]byte(test.apiResponse))
146 | }))
147 | defer server.Close()
148 | resp, err := test.httpClient.Post(server.URL, "application/json", nil)
149 | tracer.HandleSendTracesResponse(resp, err)
150 |
151 | if !strings.Contains(buf.String(), test.expectedLog) {
152 | t.Errorf("Unexpected log: expected %s got %s", test.expectedLog, buf.String())
153 | }
154 |
155 | })
156 | }
157 | }
158 |
159 | func Test_AddLabel_sanity(t *testing.T) {
160 | defaultTimeout := time.Second * 5
161 | timeout := &defaultTimeout
162 | trace := testWithTracer(timeout, func() { epsagon.Label("test_key", "test_value") })
163 | Expect(trace).ToNot(BeNil())
164 | }
165 |
166 | func Test_AddError_sanity(t *testing.T) {
167 | defaultTimeout := time.Second * 5
168 | timeout := &defaultTimeout
169 | trace := testWithTracer(timeout, func() { epsagon.Error("some error") })
170 | Expect(trace).ToNot(BeNil())
171 | }
172 |
173 | func Test_AddTypeError(t *testing.T) {
174 | defaultTimeout := time.Second * 5
175 | timeout := &defaultTimeout
176 | trace := testWithTracer(timeout, func() { epsagon.TypeError("some error", "test error type") })
177 | Expect(trace).ToNot(BeNil())
178 | }
179 |
180 | func Test_MaxTraceSize_sanity(t *testing.T) {
181 | defer os.Unsetenv(tracer.MaxTraceSizeEnvVar)
182 | os.Setenv(tracer.MaxTraceSizeEnvVar, "2048")
183 | defaultTimeout := time.Second * 5
184 | timeout := &defaultTimeout
185 | trace := testWithTracer(timeout, func() { epsagon.Label("1", "2") })
186 | Expect(trace).ToNot(BeNil())
187 | os.Setenv(tracer.MaxTraceSizeEnvVar, "64")
188 | trace = testWithTracer(timeout, func() { epsagon.Label("1", "2") })
189 | Expect(trace).To(BeNil())
190 | }
191 |
--------------------------------------------------------------------------------
/tracer/version.go:
--------------------------------------------------------------------------------
1 | package tracer
2 |
3 | // VERSION is Epsagon tracer version
4 | const VERSION = "1.39.0"
5 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/aws.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/aws/aws-sdk-go/aws/request"
8 | "github.com/aws/aws-sdk-go/aws/session"
9 | "github.com/epsagon/epsagon-go/epsagon"
10 | "github.com/epsagon/epsagon-go/protocol"
11 | "github.com/epsagon/epsagon-go/tracer"
12 | "log"
13 | "time"
14 | )
15 |
16 | // WrapSession wraps an aws session.Session with epsgaon traces
17 | func WrapSession(s *session.Session, args ...context.Context) *session.Session {
18 | if s == nil {
19 | return s
20 | }
21 | s.Handlers.Complete.PushFrontNamed(
22 | request.NamedHandler{
23 | Name: "github.com/epsagon/epsagon-go/wrappers/aws/aws-sdk-go/aws/aws.go",
24 | Fn: func(r *request.Request) {
25 | currentTracer := epsagon.ExtractTracer(args)
26 | completeEventData(r, currentTracer)
27 | },
28 | })
29 | return s
30 | }
31 |
32 | func getTimeStampFromRequest(r *request.Request) float64 {
33 | return float64(r.Time.UTC().UnixNano()) / float64(time.Millisecond) / float64(time.Nanosecond) / 1000.0
34 | }
35 |
36 | func completeEventData(r *request.Request, currentTracer tracer.Tracer) {
37 | defer epsagon.GeneralEpsagonRecover("aws-sdk-go wrapper", "", currentTracer)
38 | if currentTracer.GetConfig().Debug {
39 | log.Printf("EPSAGON DEBUG OnComplete current tracer: %+v\n", currentTracer)
40 | log.Printf("EPSAGON DEBUG OnComplete request response: %+v\n", r.HTTPResponse)
41 | log.Printf("EPSAGON DEBUG OnComplete request Operation: %+v\n", r.Operation)
42 | log.Printf("EPSAGON DEBUG OnComplete request ClientInfo: %+v\n", r.ClientInfo)
43 | log.Printf("EPSAGON DEBUG OnComplete request Params: %+v\n", r.Params)
44 | log.Printf("EPSAGON DEBUG OnComplete request Data: %+v\n", r.Data)
45 | }
46 |
47 | endTime := tracer.GetTimestamp()
48 | event := protocol.Event{
49 | Id: r.RequestID,
50 | StartTime: getTimeStampFromRequest(r),
51 | Origin: "aws-sdk",
52 | Resource: extractResourceInformation(r, currentTracer),
53 | }
54 | event.Duration = endTime - event.StartTime
55 | currentTracer.AddEvent(&event)
56 | }
57 |
58 | type factory func(*request.Request, *protocol.Resource, bool, tracer.Tracer)
59 |
60 | var awsResourceEventFactories = map[string]factory{
61 | "sqs": sqsEventDataFactory,
62 | "s3": s3EventDataFactory,
63 | "dynamodb": dynamodbEventDataFactory,
64 | "kinesis": kinesisEventDataFactory,
65 | "ses": sesEventDataFactory,
66 | "sns": snsEventDataFactory,
67 | "lambda": lambdaEventDataFactory,
68 | "sfn": sfnEventDataFactory,
69 | }
70 |
71 | func extractResourceInformation(
72 | r *request.Request, currentTracer tracer.Tracer) *protocol.Resource {
73 | res := protocol.Resource{
74 | Type: r.ClientInfo.ServiceName,
75 | Operation: r.Operation.Name,
76 | Metadata: make(map[string]string),
77 | }
78 | factory := awsResourceEventFactories[res.Type]
79 | if factory != nil {
80 | factory(r, &res, currentTracer.GetConfig().MetadataOnly, currentTracer)
81 | } else {
82 | defaultFactory(r, &res, currentTracer.GetConfig().MetadataOnly, currentTracer)
83 | }
84 | return &res
85 | }
86 |
87 | func defaultFactory(r *request.Request, res *protocol.Resource, metadataOnly bool, currentTracer tracer.Tracer) {
88 | if currentTracer.GetConfig().Debug {
89 | log.Println("EPSAGON DEBUG:: entering defaultFactory")
90 | }
91 | if !metadataOnly {
92 | extractInterfaceToMetadata(r.Data, res)
93 | extractInterfaceToMetadata(r.Params, res)
94 | }
95 | }
96 |
97 | func extractInterfaceToMetadata(input interface{}, res *protocol.Resource) {
98 | var data map[string]interface{}
99 | rawJSON, err := json.Marshal(input)
100 | if err != nil {
101 | log.Printf("EPSAGON DEBUG: Failed to marshal input: %+v\n", input)
102 | return
103 | }
104 | err = json.Unmarshal(rawJSON, &data)
105 | if err != nil {
106 | log.Printf("EPSAGON DEBUG: Failed to unmarshal input: %+v\n", rawJSON)
107 | return
108 | }
109 | for key, value := range data {
110 | res.Metadata[key] = fmt.Sprintf("%v", value)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/common_utils.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "reflect"
7 | "strconv"
8 |
9 | "github.com/aws/aws-sdk-go/aws/request"
10 | "github.com/epsagon/epsagon-go/protocol"
11 | "github.com/epsagon/epsagon-go/tracer"
12 | )
13 |
14 | type specificOperationHandler func(r *request.Request, res *protocol.Resource, metadataOnly bool, currenttracer tracer.Tracer)
15 |
16 | func handleSpecificOperation(
17 | r *request.Request,
18 | res *protocol.Resource,
19 | metadataOnly bool,
20 | handlers map[string]specificOperationHandler,
21 | defaultHandler specificOperationHandler,
22 | currentTracer tracer.Tracer,
23 | ) {
24 | handler := handlers[res.Operation]
25 | if handler == nil {
26 | handler = defaultHandler
27 | }
28 | if handler != nil {
29 | handler(r, res, metadataOnly, currentTracer)
30 | }
31 | }
32 |
33 | func isValueZero(value reflect.Value) bool {
34 | return !value.IsValid() || value.IsZero()
35 | }
36 |
37 | func getFieldStringPtr(value reflect.Value, fieldName string) (string, bool) {
38 | field := value.FieldByName(fieldName)
39 | if isValueZero(field) {
40 | return "", false
41 | }
42 | return field.Elem().String(), true
43 | }
44 |
45 | func updateMetadataFromBytes(
46 | value reflect.Value, fieldName string, targetKey string, metadata map[string]string) {
47 | field := value.FieldByName(fieldName)
48 | if isValueZero(field) {
49 | return
50 | }
51 | metadata[targetKey] = string(field.Bytes())
52 | }
53 |
54 | func updateMetadataFromValue(
55 | value reflect.Value, fieldName string, targetKey string, metadata map[string]string) {
56 | fieldValue, ok := getFieldStringPtr(value, fieldName)
57 | if ok {
58 | metadata[targetKey] = fieldValue
59 | }
60 | }
61 |
62 | func updateMetadataFromInt64(
63 | value reflect.Value, fieldName string, targetKey string, metadata map[string]string) {
64 | field := value.FieldByName(fieldName)
65 | if isValueZero(field) {
66 | return
67 | }
68 | metadata[targetKey] = strconv.FormatInt(field.Elem().Int(), 10)
69 | }
70 |
71 | func updateMetadataWithFieldToJSON(
72 | value reflect.Value,
73 | fieldName string,
74 | targetKey string,
75 | metadata map[string]string,
76 | currentTracer tracer.Tracer,
77 | ) {
78 | field := value.FieldByName(fieldName)
79 | if isValueZero(field) {
80 | return
81 | }
82 | stream, err := json.Marshal(field.Interface())
83 | if err != nil {
84 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go", fmt.Sprintf("%v", err))
85 | return
86 | }
87 | metadata[targetKey] = string(stream)
88 | }
89 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/kinesis_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/aws/request"
5 | "github.com/epsagon/epsagon-go/protocol"
6 | "github.com/epsagon/epsagon-go/tracer"
7 | "reflect"
8 | )
9 |
10 | func kinesisEventDataFactory(
11 | r *request.Request,
12 | res *protocol.Resource,
13 | metadataOnly bool,
14 | currentTracer tracer.Tracer,
15 | ) {
16 | inputValue := reflect.ValueOf(r.Params).Elem()
17 | streamName, ok := getFieldStringPtr(inputValue, "StreamName")
18 | if !ok {
19 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go",
20 | "kinesisEventDataFactory: couldn't find StreamName")
21 | }
22 | res.Name = streamName
23 | updateMetadataFromValue(inputValue, "PartitionKey", "partition_key", res.Metadata)
24 | if !metadataOnly {
25 | dataField := inputValue.FieldByName("Data")
26 | if dataField != (reflect.Value{}) {
27 | res.Metadata["data"] = string(dataField.Bytes())
28 | }
29 | }
30 | handleSpecificOperation(r, res, metadataOnly,
31 | map[string]specificOperationHandler{
32 | "PutRecord": handleKinesisPutRecord,
33 | }, nil, currentTracer,
34 | )
35 | }
36 |
37 | func handleKinesisPutRecord(
38 | r *request.Request,
39 | res *protocol.Resource,
40 | metadataOnly bool,
41 | currentTracer tracer.Tracer,
42 | ) {
43 | outputValue := reflect.ValueOf(r.Data).Elem()
44 | updateMetadataFromValue(outputValue, "ShardId", "shared_id", res.Metadata)
45 | updateMetadataFromValue(outputValue, "SequenceNumber", "sequence_number", res.Metadata)
46 | }
47 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/lambda_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "fmt"
5 | "github.com/aws/aws-sdk-go/aws/request"
6 | "github.com/epsagon/epsagon-go/protocol"
7 | "github.com/epsagon/epsagon-go/tracer"
8 | "io"
9 | "reflect"
10 | )
11 |
12 | func lambdaEventDataFactory(
13 | r *request.Request,
14 | res *protocol.Resource,
15 | metadataOnly bool,
16 | currentTracer tracer.Tracer,
17 | ) {
18 | inputValue := reflect.ValueOf(r.Params).Elem()
19 | functionName, ok := getFieldStringPtr(inputValue, "FunctionName")
20 | if ok {
21 | res.Name = functionName
22 | }
23 | if metadataOnly {
24 | return
25 | }
26 | updateMetadataFromBytes(inputValue, "Payload", "payload", res.Metadata)
27 | invokeArgsField := inputValue.FieldByName("InvokeArgs")
28 | if invokeArgsField == (reflect.Value{}) {
29 | return
30 | }
31 | invokeArgsReader := invokeArgsField.Interface().(io.ReadSeeker)
32 | invokeArgsBytes := make([]byte, 100)
33 |
34 | initialOffset, err := invokeArgsReader.Seek(int64(0), io.SeekStart)
35 | if err != nil {
36 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go",
37 | fmt.Sprintf("lambdaEventDataFactory: %v", err))
38 | return
39 | }
40 |
41 | _, err = invokeArgsReader.Read(invokeArgsBytes)
42 | if err != nil {
43 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go",
44 | fmt.Sprintf("lambdaEventDataFactory: %v", err))
45 | return
46 | }
47 | res.Metadata["invoke_args"] = string(invokeArgsBytes)
48 | _, err = invokeArgsReader.Seek(initialOffset, io.SeekStart)
49 | if err != nil {
50 | currentTracer.AddExceptionTypeAndMessage("aws-sdk-go",
51 | fmt.Sprintf("lambdaEventDataFactory: %v", err))
52 | return
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/s3_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "fmt"
5 | "github.com/aws/aws-sdk-go/aws/request"
6 | "github.com/epsagon/epsagon-go/protocol"
7 | "github.com/epsagon/epsagon-go/tracer"
8 | "reflect"
9 | "strings"
10 | "time"
11 | )
12 |
13 | func s3EventDataFactory(
14 | r *request.Request,
15 | res *protocol.Resource,
16 | metadataOnly bool,
17 | currentTracer tracer.Tracer,
18 | ) {
19 | inputValue := reflect.ValueOf(r.Params).Elem()
20 | bucketName, ok := getFieldStringPtr(inputValue, "Bucket")
21 | if ok {
22 | res.Name = bucketName
23 | }
24 | handleSpecificOperations := map[string]specificOperationHandler{
25 | "HeadObject": handleS3GetOrHeadObject,
26 | "GetObject": handleS3GetOrHeadObject,
27 | "PutObject": handleS3PutObject,
28 | "ListObjects": handleS3ListObject,
29 | }
30 | handler := handleSpecificOperations[res.Operation]
31 | if handler != nil {
32 | handler(r, res, metadataOnly, currentTracer)
33 | }
34 | }
35 |
36 | func commonS3OpertionHandler(r *request.Request, res *protocol.Resource, metadataOnly bool) {
37 | inputValue := reflect.ValueOf(r.Params).Elem()
38 | updateMetadataFromValue(inputValue, "Key", "key", res.Metadata)
39 | outputValue := reflect.ValueOf(r.Data).Elem()
40 | etag, ok := getFieldStringPtr(outputValue, "ETag")
41 | if ok {
42 | etag = strings.Trim(etag, "\"")
43 | res.Metadata["etag"] = etag
44 | }
45 | }
46 |
47 | func handleS3GetOrHeadObject(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
48 | commonS3OpertionHandler(r, res, metadataOnly)
49 | outputValue := reflect.ValueOf(r.Data).Elem()
50 | updateMetadataFromValue(outputValue, "ContentLength", "file_size", res.Metadata)
51 |
52 | lastModifiedField := outputValue.FieldByName("LastModified")
53 | if lastModifiedField == (reflect.Value{}) {
54 | return
55 | }
56 | lastModified := lastModifiedField.Elem().Interface().(time.Time)
57 | res.Metadata["last_modified"] = lastModified.String()
58 | }
59 |
60 | func handleS3PutObject(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
61 | commonS3OpertionHandler(r, res, metadataOnly)
62 | }
63 |
64 | type s3File struct {
65 | key string
66 | size int64
67 | etag string
68 | }
69 |
70 | func handleS3ListObject(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
71 | if metadataOnly {
72 | return
73 | }
74 |
75 | outputValue := reflect.ValueOf(r.Data).Elem()
76 | contentsField := outputValue.FieldByName("Contents")
77 | if contentsField == (reflect.Value{}) {
78 | return
79 | }
80 | length := contentsField.Len()
81 | files := make([]s3File, length)
82 | for i := 0; i < length; i++ {
83 | var key, etag string
84 | var size int64
85 | fileObject := contentsField.Index(i).Elem()
86 | etag = fileObject.FieldByName("ETag").Elem().String()
87 | key = fileObject.FieldByName("Key").Elem().String()
88 | size = fileObject.FieldByName("Size").Elem().Int()
89 |
90 | files = append(files, s3File{key, size, etag})
91 | }
92 | res.Metadata["files"] = fmt.Sprintf("%+v", files)
93 | }
94 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/ses_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/aws/request"
5 | "github.com/epsagon/epsagon-go/protocol"
6 | "github.com/epsagon/epsagon-go/tracer"
7 | "reflect"
8 | )
9 |
10 | func sesEventDataFactory(
11 | r *request.Request,
12 | res *protocol.Resource,
13 | metadataOnly bool,
14 | currentTracer tracer.Tracer,
15 | ) {
16 | handleSpecificOperation(r, res, metadataOnly,
17 | map[string]specificOperationHandler{
18 | "SendEmail": handleSESSendEmail,
19 | },
20 | nil, currentTracer,
21 | )
22 | }
23 |
24 | func handleSESSendEmail(
25 | r *request.Request,
26 | res *protocol.Resource,
27 | metadataOnly bool,
28 | currentTracer tracer.Tracer,
29 | ) {
30 | inputValue := reflect.ValueOf(r.Params).Elem()
31 | updateMetadataFromValue(inputValue, "Source", "source", res.Metadata)
32 | updateMetadataWithFieldToJSON(inputValue, "Destination", "destination", res.Metadata, currentTracer)
33 | messageField := inputValue.FieldByName("Message")
34 | if messageField != (reflect.Value{}) {
35 | updateMetadataWithFieldToJSON(messageField, "Subject", "subject", res.Metadata, currentTracer)
36 | if !metadataOnly {
37 | updateMetadataWithFieldToJSON(messageField, "Body", "body", res.Metadata, currentTracer)
38 | }
39 | }
40 | outputValue := reflect.ValueOf(r.Data).Elem()
41 | updateMetadataFromValue(outputValue, "MessageId", "message_id", res.Metadata)
42 | }
43 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/sfn_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/aws/request"
5 | "github.com/epsagon/epsagon-go/protocol"
6 | "github.com/epsagon/epsagon-go/tracer"
7 | "reflect"
8 | "strings"
9 | )
10 |
11 | func sfnEventDataFactory(
12 | r *request.Request,
13 | res *protocol.Resource,
14 | metadataOnly bool,
15 | currentTracer tracer.Tracer,
16 | ) {
17 | res.Type = "stepfunctions"
18 | handleSpecificOperation(r, res, metadataOnly,
19 | map[string]specificOperationHandler{
20 | "PutRecord": handleSFNStartExecution,
21 | }, nil, currentTracer,
22 | )
23 | }
24 |
25 | func handleSFNStartExecution(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
26 | inputValue := reflect.ValueOf(r.Params).Elem()
27 | arn, ok := getFieldStringPtr(inputValue, "StateMachineArn")
28 | if ok {
29 | arnParts := strings.Split(arn, ":")
30 | res.Name = arnParts[len(arnParts)-1]
31 | res.Metadata["State Machine ARN"] = arn
32 | }
33 | updateMetadataFromValue(inputValue, "Name", "Execution Name", res.Metadata)
34 | if !metadataOnly {
35 | updateMetadataFromValue(inputValue, "Input", "input", res.Metadata)
36 | }
37 | outputValue := reflect.ValueOf(r.Data).Elem()
38 | updateMetadataFromValue(outputValue, "ExecutionArn", "Execution ARN", res.Metadata)
39 | }
40 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/sns_factory.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/aws/request"
5 | "github.com/epsagon/epsagon-go/protocol"
6 | "github.com/epsagon/epsagon-go/tracer"
7 | "reflect"
8 | "strings"
9 | )
10 |
11 | const InvalidFieldValue = ""
12 |
13 | func snsEventDataFactory(
14 | r *request.Request,
15 | res *protocol.Resource,
16 | metadataOnly bool,
17 | currentTracer tracer.Tracer,
18 | ) {
19 | if !metadataOnly {
20 | inputValue := reflect.ValueOf(r.Params).Elem()
21 | updateMetadataFromValue(inputValue, "Message", "Notification Message", res.Metadata)
22 | }
23 | handleSpecificOperation(r, res, metadataOnly,
24 | map[string]specificOperationHandler{
25 | "CreateTopic": handleSNSCreateTopic,
26 | "Publish": handlerSNSPublish,
27 | },
28 | handleSNSdefault, currentTracer,
29 | )
30 | }
31 |
32 | // gets the target name
33 | func getSNStargetName(inputValue reflect.Value, targetKey string) (string, bool) {
34 | arnString, ok := getFieldStringPtr(inputValue, targetKey)
35 | if !ok {
36 | return "", false
37 | }
38 | arnSplit := strings.Split(arnString, ":")
39 | targetName := arnSplit[len(arnSplit)-1]
40 | return targetName, targetName != InvalidFieldValue
41 | }
42 |
43 | func handleSNSdefault(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
44 | inputValue := reflect.ValueOf(r.Params).Elem()
45 | targetName, ok := getSNStargetName(inputValue, "TopicArn")
46 | if ok {
47 | res.Name = targetName
48 | return
49 | }
50 | targetName, ok = getSNStargetName(inputValue, "TargetArn")
51 | if ok {
52 | res.Name = targetName
53 | }
54 | }
55 |
56 | func handleSNSCreateTopic(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
57 | inputValue := reflect.ValueOf(r.Params).Elem()
58 | name, ok := getFieldStringPtr(inputValue, "Name")
59 | if ok {
60 | res.Name = name
61 | }
62 | }
63 |
64 | func handlerSNSPublish(r *request.Request, res *protocol.Resource, metadataOnly bool, currentTracer tracer.Tracer) {
65 | handleSNSdefault(r, res, metadataOnly, currentTracer)
66 | outputValue := reflect.ValueOf(r.Data).Elem()
67 | updateMetadataFromValue(outputValue, "MessageId", "Message ID", res.Metadata)
68 | }
69 |
--------------------------------------------------------------------------------
/wrappers/aws/aws-sdk-go/aws/sqs_factories.go:
--------------------------------------------------------------------------------
1 | package epsagonawswrapper
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/aws/request"
5 | "github.com/epsagon/epsagon-go/protocol"
6 | "github.com/epsagon/epsagon-go/tracer"
7 | "reflect"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | func sqsEventDataFactory(
13 | r *request.Request,
14 | res *protocol.Resource,
15 | metadataOnly bool,
16 | currentTracer tracer.Tracer,
17 | ) {
18 | inputValue := reflect.ValueOf(r.Params).Elem()
19 | identifyQueueName(inputValue, res)
20 |
21 | if !metadataOnly {
22 | updateMessageBody(inputValue, res)
23 | }
24 |
25 | handleSpecificOperations := map[string]specificOperationHandler{
26 | "SendMessage": handleSQSSendMessage,
27 | "ReceiveMessage": handleSQSReceiveMessage,
28 | }
29 | handler := handleSpecificOperations[res.Operation]
30 | if handler != nil {
31 | handler(r, res, metadataOnly, currentTracer)
32 | }
33 | }
34 |
35 | func updateMessageBody(inputValue reflect.Value, res *protocol.Resource) {
36 | entry := inputValue.FieldByName("Entries")
37 | if entry == (reflect.Value{}) || entry.Len() == 0 {
38 | entry = inputValue
39 | } else {
40 | // TODO currently only records the first message
41 | entry = entry.Index(0).Elem()
42 | }
43 | updateMetadataFromValue(entry, "MessageBody", "MessageBody", res.Metadata)
44 | }
45 |
46 | func identifyQueueName(inputValue reflect.Value, res *protocol.Resource) {
47 | queueURLField := inputValue.FieldByName("QueueUrl")
48 | if queueURLField != (reflect.Value{}) {
49 | queueURL, ok := queueURLField.Elem().Interface().(string)
50 | if ok {
51 | urlParts := strings.Split(queueURL, "/")
52 | res.Name = urlParts[len(urlParts)-1]
53 | } // TODO else send exception?
54 | } else {
55 | queueNameField := inputValue.FieldByName("QueueName")
56 | if queueNameField != (reflect.Value{}) {
57 | res.Name = queueNameField.Elem().String()
58 | }
59 | }
60 | }
61 |
62 | func handleSQSSendMessage(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
63 | outputValue := reflect.ValueOf(r.Data).Elem()
64 | updateMetadataFromValue(outputValue, "MessageId", "Message ID", res.Metadata)
65 | updateMetadataFromValue(outputValue, "MD5OfMessageBodyMessageId",
66 | "MD5 Of Message Body", res.Metadata)
67 | }
68 |
69 | func handleSQSReceiveMessage(r *request.Request, res *protocol.Resource, metadataOnly bool, _ tracer.Tracer) {
70 | var numberOfMessages int
71 | outputValue := reflect.ValueOf(r.Data).Elem()
72 | messages := outputValue.FieldByName("Messages")
73 | if messages == (reflect.Value{}) {
74 | numberOfMessages = 0
75 | } else {
76 | numberOfMessages = messages.Len()
77 | if numberOfMessages > 0 {
78 | updateMetadataFromValue(messages.Index(0).Elem(), "MessageId", "Message ID", res.Metadata)
79 | updateMetadataFromValue(messages.Index(0).Elem(), "MD5OfMessageBodyMessageId",
80 | "MD5 Of Message Body", res.Metadata)
81 | }
82 | }
83 | res.Metadata["Number Of Messages"] = strconv.Itoa(numberOfMessages)
84 | }
85 |
--------------------------------------------------------------------------------
/wrappers/fiber/fiber.go:
--------------------------------------------------------------------------------
1 | package epsagonfiber
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "runtime/debug"
7 | "strconv"
8 |
9 | "github.com/epsagon/epsagon-go/epsagon"
10 | "github.com/epsagon/epsagon-go/protocol"
11 | "github.com/epsagon/epsagon-go/tracer"
12 | "github.com/gofiber/fiber/v2"
13 | "github.com/valyala/fasthttp"
14 | )
15 |
16 | // FiberEpsagonMiddleware is an epsagon instumentation middleware for fiber apps
17 | type FiberEpsagonMiddleware struct {
18 | Config *epsagon.Config
19 | }
20 |
21 | func parseQueryArgs(args *fasthttp.Args) map[string]string {
22 | result := make(map[string]string)
23 | args.VisitAll(func(key, val []byte) {
24 | result[string(key)] = string(val)
25 | })
26 | return result
27 | }
28 |
29 | // convert map values to string. On error, add exception to tracer with the
30 | // given error message
31 | func convertMapValuesToString(
32 | values map[string]string,
33 | wrapperTracer tracer.Tracer,
34 | errorMessage string) string {
35 | processed, err := json.Marshal(values)
36 | if err != nil {
37 | wrapperTracer.AddException(&protocol.Exception{
38 | Type: "trigger-creation",
39 | Message: errorMessage,
40 | Traceback: string(debug.Stack()),
41 | Time: tracer.GetTimestamp(),
42 | })
43 | return ""
44 | }
45 | return string(processed)
46 | }
47 |
48 | func processQueryFromURI(uriObj *fasthttp.URI, wrapperTracer tracer.Tracer) string {
49 | if uriObj == nil {
50 | return ""
51 | }
52 | args := parseQueryArgs(uriObj.QueryArgs())
53 | return convertMapValuesToString(
54 | args,
55 | wrapperTracer,
56 | fmt.Sprintf("Failed to serialize query params %s", uriObj.QueryString()))
57 |
58 | }
59 |
60 | func processRequestHeaders(requestHeaders *fasthttp.RequestHeader, wrapperTracer tracer.Tracer) string {
61 | if requestHeaders == nil {
62 | return ""
63 | }
64 | headers := make(map[string]string)
65 | requestHeaders.VisitAll(func(key, val []byte) {
66 | headers[string(key)] = string(val)
67 | })
68 | return convertMapValuesToString(
69 | headers,
70 | wrapperTracer,
71 | fmt.Sprintf("Failed to serialize request headers"))
72 | }
73 |
74 | func processResponseHeaders(responseHeaders *fasthttp.ResponseHeader, wrapperTracer tracer.Tracer) string {
75 | if responseHeaders == nil {
76 | return ""
77 | }
78 | headers := make(map[string]string)
79 | responseHeaders.VisitAll(func(key, val []byte) {
80 | headers[string(key)] = string(val)
81 | })
82 | return convertMapValuesToString(
83 | headers,
84 | wrapperTracer,
85 | fmt.Sprintf("Failed to serialize response headers"))
86 | }
87 |
88 | // Gets the request content type header, empty string if not found
89 | func getRequestContentType(fiberCtx *fiber.Ctx) string {
90 | return string(fiberCtx.Request().Header.ContentType())
91 | }
92 |
93 | // CreateHTTPTriggerEvent creates an HTTP trigger event
94 | func CreateHTTPTriggerEvent(wrapperTracer tracer.Tracer, fiberCtx *fiber.Ctx, resourceName string) *protocol.Event {
95 | request := fiberCtx.Request()
96 |
97 | name := resourceName
98 | if len(name) == 0 {
99 | name = fiberCtx.Hostname()
100 | }
101 |
102 | event := &protocol.Event{
103 | Id: "",
104 | Origin: "trigger",
105 | StartTime: tracer.GetTimestamp(),
106 | Resource: &protocol.Resource{
107 | Name: name,
108 | Type: "http",
109 | Operation: fiberCtx.Method(),
110 | Metadata: map[string]string{
111 | "path": fiberCtx.Path(),
112 | },
113 | },
114 | }
115 | if !wrapperTracer.GetConfig().MetadataOnly {
116 | event.Resource.Metadata["query_string_params"] = processQueryFromURI(request.URI(), wrapperTracer)
117 | event.Resource.Metadata["request_headers"] = processRequestHeaders(&request.Header, wrapperTracer)
118 | event.Resource.Metadata["request_body"] = string(fiberCtx.Body())
119 | }
120 | return event
121 | }
122 |
123 | func fiberHandler(c *fiber.Ctx) (err error) {
124 | err = c.Next()
125 | return err
126 | }
127 |
128 | func (middleware *FiberEpsagonMiddleware) HandlerFunc() fiber.Handler {
129 | config := middleware.Config
130 | if config == nil {
131 | config = &epsagon.Config{}
132 | }
133 | return func(c *fiber.Ctx) (err error) {
134 | if epsagon.ShouldIgnoreRequest(getRequestContentType(c), c.Path()) {
135 | return c.Next()
136 | }
137 |
138 | callingOriginalHandler := false
139 | called := false
140 | var triggerEvent *protocol.Event = nil
141 | defer func() {
142 | userError := recover()
143 | if userError == nil {
144 | return
145 | }
146 | if !callingOriginalHandler {
147 | err = c.Next()
148 | return
149 | }
150 | if !called { // panic only if error happened in original handler
151 | triggerEvent.Resource.Metadata["status_code"] = "500"
152 | panic(userError)
153 | }
154 | }()
155 |
156 | wrapperTracer := tracer.CreateTracer(&config.Config)
157 | wrapperTracer.Start()
158 | defer wrapperTracer.SendStopSignal()
159 | userContext := c.UserContext()
160 | c.SetUserContext(epsagon.ContextWithTracer(wrapperTracer, userContext))
161 | triggerEvent = CreateHTTPTriggerEvent(wrapperTracer, c, c.Hostname())
162 | wrapperTracer.AddEvent(triggerEvent)
163 | wrapper := epsagon.WrapGenericFunction(
164 | func(c *fiber.Ctx) error {
165 | err = c.Next()
166 | return err
167 | }, config, wrapperTracer, false, c.Path())
168 | defer postExecutionUpdates(wrapperTracer, triggerEvent, c, wrapper)
169 | callingOriginalHandler = true
170 | wrapper.Call(c)
171 | called = true
172 | return err
173 | }
174 | }
175 |
176 | func postExecutionUpdates(
177 | wrapperTracer tracer.Tracer, triggerEvent *protocol.Event,
178 | c *fiber.Ctx, handlerWrapper *epsagon.GenericWrapper) {
179 | runner := handlerWrapper.GetRunnerEvent()
180 | if runner != nil {
181 | runner.Resource.Type = "fiber"
182 | }
183 | response := c.Response()
184 | triggerEvent.Resource.Metadata["status_code"] = strconv.Itoa(response.StatusCode())
185 | if !wrapperTracer.GetConfig().MetadataOnly {
186 | triggerEvent.Resource.Metadata["response_headers"] = processResponseHeaders(&response.Header, wrapperTracer)
187 | triggerEvent.Resource.Metadata["response_body"] = string(response.Body())
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/wrappers/gin/gin.go:
--------------------------------------------------------------------------------
1 | package epsagongin
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/epsagon/epsagon-go/epsagon"
9 | "github.com/epsagon/epsagon-go/protocol"
10 | "github.com/epsagon/epsagon-go/tracer"
11 | epsagonhttp "github.com/epsagon/epsagon-go/wrappers/net/http"
12 | "github.com/gin-gonic/gin"
13 | )
14 |
15 | // TracerKey is the key of the epsagon tracer in the gin.Context Keys map passed to the handlers
16 | const TracerKey = "EpsagonTracer"
17 |
18 | // EpsagonContext creates a context.Background() with epsagon's associated tracer for nexted instrumentations
19 | func EpsagonContext(c *gin.Context) context.Context {
20 | return epsagon.ContextWithTracer(c.Keys[TracerKey].(tracer.Tracer))
21 | }
22 |
23 | // GinRouterWrapper is an epsagon instumentation wrapper for gin.RouterGroup
24 | type GinRouterWrapper struct {
25 | gin.IRouter
26 | Hostname string
27 | Config *epsagon.Config
28 | }
29 |
30 | type wrappedGinWriter struct {
31 | gin.ResponseWriter
32 | htrw http.ResponseWriter
33 | }
34 |
35 | func wrapGinWriter(c *gin.Context, triggerEvent *protocol.Event) {
36 | wrappedResponseWriter := &wrappedGinWriter{
37 | ResponseWriter: c.Writer,
38 | htrw: epsagonhttp.CreateWrappedResponseWriter(c.Writer, triggerEvent.Resource),
39 | }
40 | c.Writer = wrappedResponseWriter
41 | }
42 |
43 | func postExecutionUpdates(
44 | wrapperTracer tracer.Tracer, triggerEvent *protocol.Event,
45 | c *gin.Context, handlerWrapper *epsagon.GenericWrapper) {
46 | runner := handlerWrapper.GetRunnerEvent()
47 | if runner != nil {
48 | runner.Resource.Type = "gin"
49 | }
50 | wrappedResponseWriter, ok := c.Writer.(*wrappedGinWriter)
51 | if ok {
52 | wrappedResponseWriter.htrw.(*epsagonhttp.WrappedResponseWriter).UpdateResource()
53 | }
54 | userError := recover()
55 | if userError != nil {
56 | triggerEvent.Resource.Metadata["status_code"] = "500"
57 | panic(userError)
58 | }
59 | }
60 |
61 | func wrapGinHandler(handler gin.HandlerFunc, hostname string, relativePath string, config *epsagon.Config) gin.HandlerFunc {
62 | if config == nil {
63 | config = &epsagon.Config{}
64 | }
65 | return func(c *gin.Context) {
66 | wrapperTracer := tracer.CreateTracer(&config.Config)
67 | wrapperTracer.Start()
68 | defer wrapperTracer.SendStopSignal()
69 |
70 | c.Set(TracerKey, wrapperTracer)
71 | wrapper := epsagon.WrapGenericFunction(
72 | handler, config, wrapperTracer, false, relativePath,
73 | )
74 | triggerEvent := epsagonhttp.CreateHTTPTriggerEvent(
75 | wrapperTracer, c.Request, hostname)
76 | wrapperTracer.AddEvent(triggerEvent)
77 | if !config.MetadataOnly {
78 | wrapGinWriter(c, triggerEvent)
79 | }
80 | defer postExecutionUpdates(wrapperTracer, triggerEvent, c, wrapper)
81 | wrapper.Call(c)
82 | triggerEvent.Resource.Metadata["status_code"] = fmt.Sprint(c.Writer.Status())
83 | }
84 | }
85 |
86 | // Handle is a wrapper for gin.RouterGroup.Handle that adds epsagon instrumentaiton and event triggers
87 | // to all invocations of that handler
88 | func (router *GinRouterWrapper) Handle(httpMethod, relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
89 | if len(handlers) >= 1 {
90 | handlers[0] = wrapGinHandler(handlers[0], router.Hostname, relativePath, router.Config)
91 | }
92 | return router.IRouter.Handle(httpMethod, relativePath, handlers...)
93 | }
94 |
95 | // POST is a shortcut for router.Handle("POST", path, handle).
96 | func (router *GinRouterWrapper) POST(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
97 | return router.Handle(http.MethodPost, relativePath, handlers...)
98 | }
99 |
100 | // GET is a shortcut for router.Handle("GET", path, handle).
101 | func (router *GinRouterWrapper) GET(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
102 | return router.Handle(http.MethodGet, relativePath, handlers...)
103 | }
104 |
105 | // DELETE is a shortcut for router.Handle("DELETE", path, handle).
106 | func (router *GinRouterWrapper) DELETE(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
107 | return router.Handle(http.MethodDelete, relativePath, handlers...)
108 | }
109 |
110 | // PATCH is a shortcut for router.Handle("PATCH", path, handle).
111 | func (router *GinRouterWrapper) PATCH(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
112 | return router.Handle(http.MethodPatch, relativePath, handlers...)
113 | }
114 |
115 | // PUT is a shortcut for router.Handle("PUT", path, handle).
116 | func (router *GinRouterWrapper) PUT(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
117 | return router.Handle(http.MethodPut, relativePath, handlers...)
118 | }
119 |
120 | // OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
121 | func (router *GinRouterWrapper) OPTIONS(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
122 | return router.Handle(http.MethodOptions, relativePath, handlers...)
123 | }
124 |
125 | // HEAD is a shortcut for router.Handle("HEAD", path, handle).
126 | func (router *GinRouterWrapper) HEAD(relativePath string, handlers ...gin.HandlerFunc) gin.IRoutes {
127 | return router.Handle(http.MethodHead, relativePath, handlers...)
128 | }
129 |
130 | // Run is a shortcut for router.IRouter.(*gin.Engine).Run()
131 | func (router *GinRouterWrapper) Run(addr ...string) error {
132 | engine, ok := router.IRouter.(*gin.Engine)
133 |
134 | if !ok {
135 | return fmt.Errorf("Could not start Gin engine")
136 | }
137 |
138 | return engine.Run(addr...)
139 | }
140 |
141 | func (grw *wrappedGinWriter) Header() http.Header {
142 | return grw.htrw.Header()
143 | }
144 |
145 | func (grw *wrappedGinWriter) Write(data []byte) (int, error) {
146 | return grw.htrw.Write(data)
147 | }
148 |
149 | func (grw *wrappedGinWriter) WriteHeader(statusCode int) {
150 | grw.htrw.WriteHeader(statusCode)
151 | }
152 |
--------------------------------------------------------------------------------
/wrappers/mongo/common_utils.go:
--------------------------------------------------------------------------------
1 | package epsagonmongo
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "github.com/epsagon/epsagon-go/epsagon"
8 | "github.com/epsagon/epsagon-go/protocol"
9 | "github.com/epsagon/epsagon-go/tracer"
10 | "github.com/google/uuid"
11 | "go.mongodb.org/mongo-driver/mongo"
12 | "strconv"
13 |
14 | "reflect"
15 | "runtime"
16 | "strings"
17 | )
18 |
19 | // currentFuncName returns the name of the caller function as a string
20 | // for func Foo() { x := currentFuncName() }, x == "Foo"
21 | func currentFuncName() string {
22 | current := make([]uintptr, 1)
23 | if level := runtime.Callers(2, current); level == 0 {
24 | return ""
25 | }
26 | caller := runtime.FuncForPC(current[0] - 1)
27 | if caller == nil {
28 | return ""
29 | }
30 | sysFuncName := caller.Name()
31 | return partitionByDelimiterAtIndex(sysFuncName, ".", -1)
32 |
33 | }
34 |
35 | func startMongoEvent(opName string, coll *MongoCollectionWrapper) *protocol.Event {
36 | defer epsagon.GeneralEpsagonRecover("mongo-driver", currentFuncName(), coll.tracer)
37 | return &protocol.Event{
38 | Id: "mongodb-" + uuid.New().String(),
39 | Origin: "mongodb",
40 | ErrorCode: protocol.ErrorCode_OK,
41 | StartTime: tracer.GetTimestamp(),
42 | Resource: createMongoResource(opName, coll),
43 | }
44 | }
45 |
46 | func completeMongoEvent(currentTracer tracer.Tracer, event *protocol.Event) {
47 | defer epsagon.GeneralEpsagonRecover("mongo-driver", currentFuncName(), currentTracer)
48 | event.Duration = tracer.GetTimestamp() - event.StartTime
49 | currentTracer.AddEvent(event)
50 |
51 | }
52 |
53 | func createMongoResource(opName string, coll *MongoCollectionWrapper) *protocol.Resource {
54 | defer epsagon.GeneralEpsagonRecover("mongo-driver", currentFuncName(), coll.tracer)
55 | return &protocol.Resource{
56 | Name: coll.Database().Name() + "." + coll.collection.Name(),
57 | Type: "mongodb",
58 | Operation: opName,
59 | Metadata: make(map[string]string),
60 | }
61 | }
62 |
63 | // extractStructFields parses the fields from a struct and adds it to metadata under field name metaField
64 | // attempts to convert to int if possible, else keeps as a string
65 | func extractStructFields(
66 | metadata map[string]string,
67 | metaField string,
68 | s interface{},
69 | ) {
70 | val := reflect.ValueOf(s)
71 | if val.Kind() == reflect.Ptr {
72 | val = val.Elem()
73 | }
74 | valuesMap := make(map[string]string, val.NumField())
75 |
76 | for i := 0; i < val.NumField(); i++ {
77 | if val.Field(i).CanInterface() {
78 | fieldVal := val.Field(i)
79 | v := fmt.Sprintf("%q", fieldVal)
80 | if _, err := strconv.Atoi(v); err == nil {
81 | v = strconv.FormatInt(fieldVal.Int(), 10)
82 | }
83 | valuesMap[val.Type().Field(i).Name] = v
84 | }
85 | }
86 | doc, _ := json.Marshal(valuesMap)
87 | metadata[metaField] = string(doc)
88 | }
89 |
90 | // marshalToMetadata marshals any object to JSON and adds it as a string to metadata under metaFielf
91 | func marshalToMetadata(
92 | metadata map[string]string,
93 | metaField string,
94 | s interface{},
95 | config *tracer.Config,
96 | ) {
97 | docBytes, err := json.Marshal(s)
98 | if err != nil {
99 | epsagon.DebugLog(config.Debug, "Could not Marshal JSON", err)
100 | }
101 | docString := string(docBytes)
102 | if docString == "" {
103 | return
104 | }
105 | metadata[metaField] = docString
106 | }
107 |
108 | // readCursor accepts a cursor and returns a slice of maps
109 | // each map represents a mongo document
110 | func readCursor(cursor *mongo.Cursor) ([]map[string]string, error) {
111 | var documents []map[string]string
112 | err := cursor.All(context.Background(), &documents)
113 | return documents, err
114 | }
115 |
116 | func logOperationFailure(messages ...string) {
117 | for _, m := range messages {
118 | epsagon.DebugLog(true, "[MONGO]", m)
119 | }
120 | }
121 |
122 | // partition a string by delimiter and return the partitioned at the index
123 | func partitionByDelimiterAtIndex(original, delimiter string, index int) string {
124 | s := strings.Split(original, delimiter)
125 | i := moduloFloor(len(s), index)
126 | return s[i]
127 |
128 | }
129 |
130 | // flooring an index by size
131 | // useful for wrapping around negative indices to positive
132 | func moduloFloor(size, index int) int {
133 | return (index + size) % size
134 | }
135 |
--------------------------------------------------------------------------------
/wrappers/mongo/mongo_test.go:
--------------------------------------------------------------------------------
1 | package epsagonmongo
2 |
3 | import (
4 | "context"
5 | "testing"
6 | "time"
7 |
8 | "github.com/benweissmann/memongo"
9 | "github.com/epsagon/epsagon-go/epsagon"
10 | "github.com/epsagon/epsagon-go/protocol"
11 | "github.com/epsagon/epsagon-go/tracer"
12 | . "github.com/onsi/ginkgo"
13 | . "github.com/onsi/gomega"
14 | "go.mongodb.org/mongo-driver/bson"
15 | "go.mongodb.org/mongo-driver/mongo"
16 | "go.mongodb.org/mongo-driver/mongo/options"
17 | )
18 |
19 | func TestMongoWrapper(t *testing.T) {
20 | RegisterFailHandler(Fail)
21 | RunSpecs(t, "Mongo Driver Test Suite")
22 | }
23 |
24 | var _ = Describe("mongo_wrapper", func() {
25 | Describe("CollectionWrapper", func() {
26 | var (
27 | mongoServer *memongo.Server
28 | mongoOptions *memongo.Options
29 | started chan bool
30 | testConf *epsagon.Config
31 | events []*protocol.Event
32 | exceptions []*protocol.Exception
33 | wrapper *MongoCollectionWrapper
34 | testContext context.Context
35 | testDatabaseName string
36 | testCollectionName string
37 | cancel func()
38 | )
39 | BeforeEach(func() {
40 | started = make(chan bool)
41 | // start server goroutine, runs in background until block
42 | go func() {
43 | mongoOptions = &memongo.Options{
44 | MongoVersion: "4.2.0",
45 | StartupTimeout: 5 * time.Second,
46 | }
47 | mongoServer, _ = memongo.StartWithOptions(mongoOptions)
48 | started <- true
49 | }()
50 |
51 | testConf = &epsagon.Config{Config: tracer.Config{
52 | Disable: true,
53 | TestMode: true,
54 | }}
55 | events = make([]*protocol.Event, 0)
56 | exceptions = make([]*protocol.Exception, 0)
57 | tracer.GlobalTracer = &tracer.MockedEpsagonTracer{
58 | Events: &events,
59 | Exceptions: &exceptions,
60 | Labels: make(map[string]interface{}),
61 | Config: &testConf.Config,
62 | }
63 |
64 | testCollectionName = "collectionName"
65 | testDatabaseName = "databaseName"
66 | testContext, cancel = context.WithTimeout(context.Background(), 2*time.Second)
67 |
68 | // blocking await until server is started
69 | select {
70 | case <-started:
71 | break
72 | }
73 | client, _ := mongo.Connect(testContext, options.Client().ApplyURI(mongoServer.URI()))
74 | wrapper = &MongoCollectionWrapper{
75 | collection: client.Database(testDatabaseName).Collection(testCollectionName),
76 | tracer: tracer.GlobalTracer,
77 | }
78 | })
79 | AfterEach(func() {
80 | mongoServer.Stop()
81 | cancel()
82 | })
83 | Context("Writing DB", func() {
84 | It("calls InsertOne", func() {
85 | _, err := wrapper.InsertOne(context.Background(), struct {
86 | Name string
87 | }{"TestName"})
88 | Expect(err).To(BeNil())
89 | })
90 | It("calls InsertMany", func() {
91 | _, err := wrapper.InsertMany(context.Background(), []interface{}{
92 | bson.D{
93 | {Key: "name", Value: "hello"},
94 | {Key: "age", Value: "33"},
95 | },
96 | bson.D{
97 | {Key: "name", Value: "world"},
98 | {Key: "age", Value: "44"},
99 | },
100 | })
101 | Expect(err).To(BeNil())
102 | })
103 | })
104 | Context("Reading DB", func() {
105 | It("calls InsertOne and FindOne", func() {
106 | type doc struct {
107 | Name string
108 | }
109 | reqDoc := doc{Name: "TestName"}
110 | resDoc := doc{}
111 |
112 | _, err := wrapper.InsertOne(context.Background(), reqDoc)
113 | Expect(err).To(BeNil())
114 |
115 | response := wrapper.FindOne(
116 | context.Background(),
117 | bson.D{{Key: "name", Value: "TestName"}},
118 | )
119 |
120 | response.Decode(&resDoc)
121 | Expect(reqDoc).To(Equal(resDoc))
122 | Expect(response.Err()).To(BeNil())
123 |
124 | })
125 | It("calls InsertMany and Find", func() {
126 | type doc struct {
127 | Name string
128 | }
129 | docs := []interface{}{
130 | bson.D{
131 | {Key: "name", Value: "hello"},
132 | {Key: "age", Value: "33"},
133 | },
134 | bson.D{
135 | {Key: "name", Value: "world"},
136 | {Key: "age", Value: "44"},
137 | },
138 | }
139 |
140 | _, err := wrapper.InsertMany(context.Background(), docs)
141 | Expect(err).To(BeNil())
142 |
143 | cur, err := wrapper.Find(
144 | context.Background(),
145 | bson.M{},
146 | )
147 | Expect(err).To(BeNil())
148 |
149 |
150 | readCursor(cur)
151 | })
152 | It("calls CountDocuments", func() {
153 | docs := []interface{}{
154 | bson.D{
155 | {Key: "name", Value: "hello"},
156 | {Key: "age", Value: "33"},
157 | },
158 | bson.D{
159 | {Key: "name", Value: "world"},
160 | {Key: "age", Value: "44"},
161 | },
162 | }
163 |
164 | wrapper.InsertMany(context.Background(), docs)
165 | res, err := wrapper.CountDocuments(
166 | context.Background(),
167 | bson.D{{}},
168 | )
169 | Expect(err).To(BeNil())
170 | Expect(res).To(Equal(int64(2)))
171 | })
172 | })
173 | })
174 | })
175 |
--------------------------------------------------------------------------------
/wrappers/net/http/handler_wrapper.go:
--------------------------------------------------------------------------------
1 | package epsagonhttp
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "net/http"
7 | "net/url"
8 | "runtime/debug"
9 |
10 | "github.com/epsagon/epsagon-go/epsagon"
11 | "github.com/epsagon/epsagon-go/protocol"
12 | "github.com/epsagon/epsagon-go/tracer"
13 | )
14 |
15 | // HandlerFunction is a generic http handler function
16 | type HandlerFunction func(http.ResponseWriter, *http.Request)
17 |
18 | func processRawQuery(urlObj *url.URL, wrapperTracer tracer.Tracer) string {
19 | if urlObj == nil {
20 | return ""
21 | }
22 | processed, err := json.Marshal(urlObj.Query())
23 | if err != nil {
24 | wrapperTracer.AddException(&protocol.Exception{
25 | Type: "trigger-creation",
26 | Message: fmt.Sprintf("Failed to serialize query params %s", urlObj.RawQuery),
27 | Traceback: string(debug.Stack()),
28 | Time: tracer.GetTimestamp(),
29 | })
30 | return ""
31 | }
32 | return string(processed)
33 | }
34 |
35 | // CreateHTTPTriggerEvent creates an HTTP trigger event
36 | func CreateHTTPTriggerEvent(wrapperTracer tracer.Tracer, request *http.Request, resourceName string) *protocol.Event {
37 | name := resourceName
38 | if len(name) == 0 {
39 | name = request.Host
40 | }
41 | event := &protocol.Event{
42 | Id: "",
43 | Origin: "trigger",
44 | StartTime: tracer.GetTimestamp(),
45 | Resource: &protocol.Resource{
46 | Name: name,
47 | Type: "http",
48 | Operation: request.Method,
49 | Metadata: map[string]string{
50 | "path": request.URL.Path,
51 | },
52 | },
53 | }
54 | if !wrapperTracer.GetConfig().MetadataOnly {
55 | event.Resource.Metadata["query_string_parameters"] = processRawQuery(
56 | request.URL, wrapperTracer)
57 | headers, body := epsagon.ExtractRequestData(request)
58 | event.Resource.Metadata["request_headers"] = headers
59 | event.Resource.Metadata["request_body"] = body
60 | }
61 | return event
62 | }
63 |
64 | // WrapHandleFunc wraps a generic http.HandleFunc handler function with Epsagon
65 | // Last two optional paramerts are the name of the handler (will be the resource name of the events) and an optional hardcoded hostname
66 | func WrapHandleFunc(
67 | config *epsagon.Config, handler HandlerFunction, names ...string) HandlerFunction {
68 | var hostName, handlerName string
69 | if config == nil {
70 | config = &epsagon.Config{}
71 | }
72 | if len(names) >= 1 {
73 | handlerName = names[0]
74 | }
75 | if len(names) >= 2 {
76 | hostName = names[1]
77 | }
78 | return func(rw http.ResponseWriter, request *http.Request) {
79 | wrapperTracer := tracer.CreateTracer(&config.Config)
80 | wrapperTracer.Start()
81 | defer wrapperTracer.Stop()
82 |
83 | triggerEvent := CreateHTTPTriggerEvent(
84 | wrapperTracer, request, hostName)
85 | wrapperTracer.AddEvent(triggerEvent)
86 | triggerEvent.Resource.Metadata["status_code"] = "200"
87 | defer func() {
88 | if userError := recover(); userError != nil {
89 | triggerEvent.Resource.Metadata["status_code"] = "500"
90 | panic(userError)
91 | }
92 | }()
93 |
94 | newRequest := request.WithContext(
95 | epsagon.ContextWithTracer(wrapperTracer, request.Context()))
96 |
97 | if !config.MetadataOnly {
98 | rw = &WrappedResponseWriter{
99 | ResponseWriter: rw,
100 | resource: triggerEvent.Resource,
101 | }
102 | }
103 | defer func() {
104 | if wrappedResponseWriter, ok := rw.(*WrappedResponseWriter); ok {
105 | wrappedResponseWriter.UpdateResource()
106 | }
107 | }()
108 |
109 | wrapper := epsagon.WrapGenericFunction(
110 | handler, config, wrapperTracer, false, handlerName,
111 | )
112 | wrapper.Call(rw, newRequest)
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/wrappers/net/http/response_writer.go:
--------------------------------------------------------------------------------
1 | package epsagonhttp
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "net/http"
7 |
8 | "github.com/epsagon/epsagon-go/epsagon"
9 | "github.com/epsagon/epsagon-go/protocol"
10 | )
11 |
12 | // WrappedResponseWriter is wrapping Resposne writer with Epsagon
13 | // to enrich the trace with data from the response
14 | type WrappedResponseWriter struct {
15 | http.ResponseWriter
16 | resource *protocol.Resource
17 | buf bytes.Buffer
18 | }
19 |
20 | // CreateWrappedResponseWriter creates a newWrappedResponseWriter
21 | func CreateWrappedResponseWriter(rw http.ResponseWriter, resource *protocol.Resource) *WrappedResponseWriter {
22 | return &WrappedResponseWriter{
23 | ResponseWriter: rw,
24 | resource: resource,
25 | buf: bytes.Buffer{},
26 | }
27 | }
28 |
29 | // Header wrapper
30 | func (w *WrappedResponseWriter) Header() http.Header {
31 | return w.ResponseWriter.Header()
32 | }
33 |
34 | // WriteHeader wrapper, will set status_code immediately
35 | func (w *WrappedResponseWriter) WriteHeader(statusCode int) {
36 | w.resource.Metadata["status_code"] = fmt.Sprint(statusCode)
37 | w.ResponseWriter.WriteHeader(statusCode)
38 | }
39 |
40 | // Write wrapper
41 | func (w *WrappedResponseWriter) Write(data []byte) (int, error) {
42 | w.buf.Write(data)
43 | return w.ResponseWriter.Write(data)
44 | }
45 |
46 | // UpdateResource updates the connected resource with the response headers and body
47 | func (w *WrappedResponseWriter) UpdateResource() {
48 | w.resource.Metadata["response_headers"], _ = epsagon.FormatHeaders(w.ResponseWriter.Header())
49 | w.resource.Metadata["response_body"] = w.buf.String()
50 | }
51 |
--------------------------------------------------------------------------------
/wrappers/redis/redis.go:
--------------------------------------------------------------------------------
1 | package epsagonredis
2 |
3 | import (
4 | "context"
5 | "encoding/json"
6 | "fmt"
7 | "net"
8 | "runtime/debug"
9 |
10 | "github.com/epsagon/epsagon-go/epsagon"
11 | "github.com/epsagon/epsagon-go/protocol"
12 | "github.com/epsagon/epsagon-go/tracer"
13 | "github.com/go-redis/redis/v8"
14 | "github.com/google/uuid"
15 | )
16 |
17 | type epsagonHook struct {
18 | host string
19 | port string
20 | dbIndex string
21 | event *protocol.Event
22 | tracer tracer.Tracer
23 | }
24 |
25 | func NewClient(opt *redis.Options, epsagonCtx context.Context) *redis.Client {
26 | client := redis.NewClient(opt)
27 | return wrapClient(client, opt, epsagonCtx)
28 | }
29 |
30 | func wrapClient(client *redis.Client, opt *redis.Options, epsagonCtx context.Context) (wrappedClient *redis.Client) {
31 | wrappedClient = client
32 | defer func() { recover() }()
33 |
34 | currentTracer := epsagon.ExtractTracer([]context.Context{epsagonCtx})
35 | if currentTracer != nil {
36 | host, port := getClientHostPort(opt)
37 | client.AddHook(&epsagonHook{
38 | host: host,
39 | port: port,
40 | dbIndex: fmt.Sprint(opt.DB),
41 | tracer: currentTracer,
42 | })
43 | }
44 | return wrappedClient
45 | }
46 |
47 | func getClientHostPort(opt *redis.Options) (host, port string) {
48 | if opt.Network == "unix" {
49 | return "localhost", opt.Addr
50 | }
51 | host, port, err := net.SplitHostPort(opt.Addr)
52 | if err == nil {
53 | return host, port
54 | }
55 | return opt.Addr, ""
56 | }
57 |
58 | func (epsHook *epsagonHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (processCtx context.Context, err error) {
59 | processCtx, err = ctx, nil
60 | defer func() { recover() }()
61 |
62 | cmdArgs := safeJsonify(cmd.Args())
63 | epsHook.before(cmd.Name(), cmdArgs)
64 | return
65 | }
66 |
67 | func (epsHook *epsagonHook) AfterProcess(ctx context.Context, cmd redis.Cmder) (err error) {
68 | defer func() { recover() }()
69 |
70 | var errMsg string
71 | if err := cmd.Err(); err != nil {
72 | errMsg = err.Error()
73 | }
74 | epsHook.after(cmd.String(), errMsg)
75 | return
76 | }
77 |
78 | func (epsHook *epsagonHook) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (processCtx context.Context, err error) {
79 | processCtx, err = ctx, nil
80 | defer func() { recover() }()
81 |
82 | cmdArgs := safeJsonify(getPiplineCmdArgs(cmds))
83 | epsHook.before("Pipeline", cmdArgs)
84 | return
85 | }
86 |
87 | func (epsHook *epsagonHook) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) (err error) {
88 | defer func() { recover() }()
89 |
90 | var errMsg string
91 | if errors := getPipelineErrors(cmds); len(errors) > 0 {
92 | errMsg = safeJsonify(errors)
93 |
94 | }
95 | response := safeJsonify(getPipelineResponse(cmds))
96 | epsHook.after(response, errMsg)
97 | return
98 | }
99 |
100 | func (epsHook *epsagonHook) before(operation, cmdArgs string) {
101 | metadata := getResourceMetadata(epsHook, cmdArgs)
102 | epsHook.event = createEvent(epsHook, operation, metadata)
103 | }
104 |
105 | func (epsHook *epsagonHook) after(response, errMsg string) {
106 | event := epsHook.event
107 | if event == nil {
108 | return
109 | }
110 | if !epsHook.tracer.GetConfig().MetadataOnly {
111 | event.Resource.Metadata["redis.response"] = response
112 | }
113 |
114 | eventEndTime := tracer.GetTimestamp()
115 | event.Duration = eventEndTime - event.StartTime
116 |
117 | if errMsg != "" {
118 | event.ErrorCode = protocol.ErrorCode_EXCEPTION
119 | event.Exception = &protocol.Exception{
120 | Message: errMsg,
121 | Traceback: string(debug.Stack()),
122 | Time: eventEndTime,
123 | }
124 | }
125 |
126 | epsHook.tracer.AddEvent(event)
127 | }
128 |
129 | func createEvent(epsHook *epsagonHook, operation string, metadata map[string]string) *protocol.Event {
130 | return &protocol.Event{
131 | Id: "redis-" + uuid.New().String(),
132 | StartTime: tracer.GetTimestamp(),
133 | Resource: &protocol.Resource{
134 | Name: epsHook.host,
135 | Type: "redis",
136 | Operation: operation,
137 | Metadata: metadata,
138 | },
139 | }
140 | }
141 |
142 | func getResourceMetadata(epsHook *epsagonHook, cmdArgs string) map[string]string {
143 | metadata := getConnectionMetadata(epsHook)
144 | if !epsHook.tracer.GetConfig().MetadataOnly {
145 | metadata["Command Arguments"] = cmdArgs
146 | }
147 | return metadata
148 | }
149 |
150 | func getConnectionMetadata(epsHook *epsagonHook) map[string]string {
151 | return map[string]string{
152 | "Redis Host": epsHook.host,
153 | "Redis Port": epsHook.port,
154 | "Redis DB Index": epsHook.dbIndex,
155 | }
156 | }
157 |
158 | func getPiplineCmdArgs(cmds []redis.Cmder) []interface{} {
159 | var cmdArgs []interface{}
160 | for _, cmd := range cmds {
161 | cmdArgs = append(cmdArgs, cmd.Args())
162 | }
163 | return cmdArgs
164 | }
165 |
166 | func getPipelineResponse(cmds []redis.Cmder) []string {
167 | var responses []string
168 | for _, cmd := range cmds {
169 | responses = append(responses, cmd.String())
170 | }
171 | return responses
172 | }
173 |
174 | func getPipelineErrors(cmds []redis.Cmder) []string {
175 | var errors []string
176 | for _, cmd := range cmds {
177 | if err := cmd.Err(); err != nil {
178 | errors = append(errors, err.Error())
179 | }
180 | }
181 | return errors
182 | }
183 |
184 | func safeJsonify(v interface{}) string {
185 | encodedValue, err := json.Marshal(v)
186 | if err == nil {
187 | return string(encodedValue)
188 | }
189 | return ""
190 | }
191 |
--------------------------------------------------------------------------------