upgrade deps; rewrite smtp session
This commit is contained in:
13
vendor/github.com/getsentry/sentry-go/.codecov.yml
generated
vendored
Normal file
13
vendor/github.com/getsentry/sentry-go/.codecov.yml
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
codecov:
|
||||
# across
|
||||
notify:
|
||||
# Do not notify until at least this number of reports have been uploaded
|
||||
# from the CI pipeline. We normally have more than that number, but 6
|
||||
# should be enough to get a first notification.
|
||||
after_n_builds: 6
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# Do not fail the commit status if the coverage was reduced up to this value
|
||||
threshold: 0.5%
|
||||
7
vendor/github.com/getsentry/sentry-go/.craft.yml
generated
vendored
7
vendor/github.com/getsentry/sentry-go/.craft.yml
generated
vendored
@@ -1,12 +1,13 @@
|
||||
minVersion: 0.23.1
|
||||
preReleaseCommand: bash scripts/craft-pre-release.sh
|
||||
minVersion: 0.35.0
|
||||
changelogPolicy: simple
|
||||
artifactProvider:
|
||||
name: none
|
||||
targets:
|
||||
- name: github
|
||||
includeNames: /none/
|
||||
tagPrefix: v
|
||||
- name: github
|
||||
tagPrefix: otel/v
|
||||
tagOnly: true
|
||||
- name: registry
|
||||
sdks:
|
||||
github:getsentry/sentry-go:
|
||||
|
||||
10
vendor/github.com/getsentry/sentry-go/.gitignore
generated
vendored
10
vendor/github.com/getsentry/sentry-go/.gitignore
generated
vendored
@@ -1,6 +1,14 @@
|
||||
# Code coverage artifacts
|
||||
coverage.txt
|
||||
coverage.out
|
||||
coverage.html
|
||||
.coverage/
|
||||
|
||||
# Just my personal way of tracking stuff — Kamil
|
||||
FIXME.md
|
||||
TODO.md
|
||||
!NOTES.md
|
||||
!NOTES.md
|
||||
|
||||
# IDE system files
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
5
vendor/github.com/getsentry/sentry-go/.golangci.yml
generated
vendored
5
vendor/github.com/getsentry/sentry-go/.golangci.yml
generated
vendored
@@ -2,8 +2,6 @@ linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
- dogsled
|
||||
- dupl
|
||||
- errcheck
|
||||
@@ -24,17 +22,16 @@ linters:
|
||||
- prealloc
|
||||
- revive
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
issues:
|
||||
exclude-rules:
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- goconst
|
||||
- prealloc
|
||||
- path: _test\.go
|
||||
text: "G306:"
|
||||
|
||||
466
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
466
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
@@ -1,5 +1,469 @@
|
||||
# Changelog
|
||||
|
||||
## 0.27.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.27.0.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- `Exception.ThreadId` is now typed as `uint64`. It was wrongly typed as `string` before. ([#770](https://github.com/getsentry/sentry-go/pull/770))
|
||||
|
||||
### Misc
|
||||
|
||||
- Export `Event.Attachments` ([#771](https://github.com/getsentry/sentry-go/pull/771))
|
||||
|
||||
## 0.26.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.26.0.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
As previously announced, this release removes some methods from the SDK.
|
||||
|
||||
- `sentry.TransactionName()` use `sentry.WithTransactionName()` instead.
|
||||
- `sentry.OpName()` use `sentry.WithOpName()` instead.
|
||||
- `sentry.TransctionSource()` use `sentry.WithTransactionSource()` instead.
|
||||
- `sentry.SpanSampled()` use `sentry.WithSpanSampled()` instead.
|
||||
|
||||
### Features
|
||||
|
||||
- Add `WithDescription` span option ([#751](https://github.com/getsentry/sentry-go/pull/751))
|
||||
|
||||
```go
|
||||
span := sentry.StartSpan(ctx, "http.client", WithDescription("GET /api/users"))
|
||||
```
|
||||
- Add support for package name parsing in Go 1.20 and higher ([#730](https://github.com/getsentry/sentry-go/pull/730))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Apply `ClientOptions.SampleRate` only to errors & messages ([#754](https://github.com/getsentry/sentry-go/pull/754))
|
||||
- Check if git is available before executing any git commands ([#737](https://github.com/getsentry/sentry-go/pull/737))
|
||||
|
||||
## 0.25.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.25.0.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
As previously announced, this release removes two global constants from the SDK.
|
||||
|
||||
- `sentry.Version` was removed. Use `sentry.SDKVersion` instead ([#727](https://github.com/getsentry/sentry-go/pull/727))
|
||||
- `sentry.SDKIdentifier` was removed. Use `Client.GetSDKIdentifier()` instead ([#727](https://github.com/getsentry/sentry-go/pull/727))
|
||||
|
||||
### Features
|
||||
|
||||
- Add `ClientOptions.IgnoreTransactions`, which allows you to ignore specific transactions based on their name ([#717](https://github.com/getsentry/sentry-go/pull/717))
|
||||
- Add `ClientOptions.Tags`, which allows you to set global tags that are applied to all events. You can also define tags by setting `SENTRY_TAGS_` environment variables ([#718](https://github.com/getsentry/sentry-go/pull/718))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Fix an issue in the profiler that would cause an infinite loop if the duration of a transaction is longer than 30 seconds ([#724](https://github.com/getsentry/sentry-go/issues/724))
|
||||
|
||||
### Misc
|
||||
|
||||
- `dsn.RequestHeaders()` is not to be removed, though it is still considered deprecated and should only be used when using a custom transport that sends events to the `/store` endpoint ([#720](https://github.com/getsentry/sentry-go/pull/720))
|
||||
|
||||
## 0.24.1
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.24.1.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Prevent a panic in `sentryotel.flushSpanProcessor()` ([(#711)](https://github.com/getsentry/sentry-go/pull/711))
|
||||
- Prevent a panic when setting the SDK identifier ([#715](https://github.com/getsentry/sentry-go/pull/715))
|
||||
|
||||
## 0.24.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.24.0.
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `sentry.Version` to be removed in 0.25.0. Use `sentry.SDKVersion` instead.
|
||||
- `sentry.SDKIdentifier` to be removed in 0.25.0. Use `Client.GetSDKIdentifier()` instead.
|
||||
- `dsn.RequestHeaders()` to be removed after 0.25.0, but no earlier than December 1, 2023. Requests to the `/envelope` endpoint are authenticated using the DSN in the envelope header.
|
||||
|
||||
### Features
|
||||
|
||||
- Run a single instance of the profiler instead of multiple ones for each Go routine ([#655](https://github.com/getsentry/sentry-go/pull/655))
|
||||
- Use the route path as the transaction names when using the Gin integration ([#675](https://github.com/getsentry/sentry-go/pull/675))
|
||||
- Set the SDK name accordingly when a framework integration is used ([#694](https://github.com/getsentry/sentry-go/pull/694))
|
||||
- Read release information (VCS revision) from `debug.ReadBuildInfo` ([#704](https://github.com/getsentry/sentry-go/pull/704))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- [otel] Fix incorrect usage of `attributes.Value.AsString` ([#684](https://github.com/getsentry/sentry-go/pull/684))
|
||||
- Fix trace function name parsing in profiler on go1.21+ ([#695](https://github.com/getsentry/sentry-go/pull/695))
|
||||
|
||||
### Misc
|
||||
|
||||
- Test against Go 1.21 ([#695](https://github.com/getsentry/sentry-go/pull/695))
|
||||
- Make tests more robust ([#698](https://github.com/getsentry/sentry-go/pull/698), [#699](https://github.com/getsentry/sentry-go/pull/699), [#700](https://github.com/getsentry/sentry-go/pull/700), [#702](https://github.com/getsentry/sentry-go/pull/702))
|
||||
|
||||
## 0.23.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.23.0.
|
||||
|
||||
### Features
|
||||
|
||||
- Initial support for [Cron Monitoring](https://docs.sentry.io/product/crons/) ([#661](https://github.com/getsentry/sentry-go/pull/661))
|
||||
|
||||
This is how the basic usage of the feature looks like:
|
||||
|
||||
```go
|
||||
// 🟡 Notify Sentry your job is running:
|
||||
checkinId := sentry.CaptureCheckIn(
|
||||
&sentry.CheckIn{
|
||||
MonitorSlug: "<monitor-slug>",
|
||||
Status: sentry.CheckInStatusInProgress,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
// Execute your scheduled task here...
|
||||
|
||||
// 🟢 Notify Sentry your job has completed successfully:
|
||||
sentry.CaptureCheckIn(
|
||||
&sentry.CheckIn{
|
||||
ID: *checkinId,
|
||||
MonitorSlug: "<monitor-slug>",
|
||||
Status: sentry.CheckInStatusOK,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
```
|
||||
|
||||
A full example of using Crons Monitoring is available [here](https://github.com/getsentry/sentry-go/blob/dde4d360660838f3c2e0ced8205bc8f7a8d312d9/_examples/crons/main.go).
|
||||
|
||||
More documentation on configuring and using Crons [can be found here](https://docs.sentry.io/platforms/go/crons/).
|
||||
|
||||
- Add support for [Event Attachments](https://docs.sentry.io/platforms/go/enriching-events/attachments/) ([#670](https://github.com/getsentry/sentry-go/pull/670))
|
||||
|
||||
It's now possible to add file/binary payloads to Sentry events:
|
||||
|
||||
```go
|
||||
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
||||
scope.AddAttachment(&Attachment{
|
||||
Filename: "report.html",
|
||||
ContentType: "text/html",
|
||||
Payload: []byte("<h1>Look, HTML</h1>"),
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
The attachment will then be accessible on the Issue Details page.
|
||||
|
||||
- Add sampling decision to trace envelope header ([#666](https://github.com/getsentry/sentry-go/pull/666))
|
||||
- Expose SpanFromContext function ([#672](https://github.com/getsentry/sentry-go/pull/672))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Make `Span.Finish` a no-op when the span is already finished ([#660](https://github.com/getsentry/sentry-go/pull/660))
|
||||
|
||||
## 0.22.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.22.0.
|
||||
|
||||
This release contains initial [profiling](https://docs.sentry.io/product/profiling/) support, as well as a few bug fixes and improvements.
|
||||
|
||||
### Features
|
||||
|
||||
- Initial (alpha) support for [profiling](https://docs.sentry.io/product/profiling/) ([#626](https://github.com/getsentry/sentry-go/pull/626))
|
||||
|
||||
Profiling is disabled by default. To enable it, configure both `TracesSampleRate` and `ProfilesSampleRate` when initializing the SDK:
|
||||
|
||||
```go
|
||||
err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: "__DSN__",
|
||||
EnableTracing: true,
|
||||
TracesSampleRate: 1.0,
|
||||
// The sampling rate for profiling is relative to TracesSampleRate. In this case, we'll capture profiles for 100% of transactions.
|
||||
ProfilesSampleRate: 1.0,
|
||||
})
|
||||
```
|
||||
|
||||
More documentation on profiling and current limitations [can be found here](https://docs.sentry.io/platforms/go/profiling/).
|
||||
|
||||
- Add transactions/tracing support go the Gin integration ([#644](https://github.com/getsentry/sentry-go/pull/644))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Always set a valid source on transactions ([#637](https://github.com/getsentry/sentry-go/pull/637))
|
||||
- Clone scope.Context in more places to avoid panics on concurrent reads and writes ([#638](https://github.com/getsentry/sentry-go/pull/638))
|
||||
- Fixes [#570](https://github.com/getsentry/sentry-go/issues/570)
|
||||
- Fix frames recognized as not being in-app still showing as in-app ([#647](https://github.com/getsentry/sentry-go/pull/647))
|
||||
|
||||
## 0.21.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.21.0.
|
||||
|
||||
Note: this release includes one **breaking change** and some **deprecations**, which are listed below.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**This change does not apply if you use [https://sentry.io](https://sentry.io)**
|
||||
|
||||
- Remove support for the `/store` endpoint ([#631](https://github.com/getsentry/sentry-go/pull/631))
|
||||
- This change requires a self-hosted version of Sentry 20.6.0 or higher. If you are using a version of [self-hosted Sentry](https://develop.sentry.dev/self-hosted/) (aka *on-premise*) older than 20.6.0, then you will need to [upgrade](https://develop.sentry.dev/self-hosted/releases/) your instance.
|
||||
|
||||
### Features
|
||||
|
||||
- Rename four span option functions ([#611](https://github.com/getsentry/sentry-go/pull/611), [#624](https://github.com/getsentry/sentry-go/pull/624))
|
||||
- `TransctionSource` -> `WithTransactionSource`
|
||||
- `SpanSampled` -> `WithSpanSampled`
|
||||
- `OpName` -> `WithOpName`
|
||||
- `TransactionName` -> `WithTransactionName`
|
||||
- Old functions `TransctionSource`, `SpanSampled`, `OpName`, and `TransactionName` are still available but are now **deprecated** and will be removed in a future release.
|
||||
- Make `client.EventFromMessage` and `client.EventFromException` methods public ([#607](https://github.com/getsentry/sentry-go/pull/607))
|
||||
- Add `client.SetException` method ([#607](https://github.com/getsentry/sentry-go/pull/607))
|
||||
- This allows to set or add errors to an existing `Event`.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Protect from panics while doing concurrent reads/writes to Span data fields ([#609](https://github.com/getsentry/sentry-go/pull/609))
|
||||
- [otel] Improve detection of Sentry-related spans ([#632](https://github.com/getsentry/sentry-go/pull/632), [#636](https://github.com/getsentry/sentry-go/pull/636))
|
||||
- Fixes cases when HTTP spans containing requests to Sentry were captured by Sentry ([#627](https://github.com/getsentry/sentry-go/issues/627))
|
||||
|
||||
### Misc
|
||||
|
||||
- Drop testing in (legacy) GOPATH mode ([#618](https://github.com/getsentry/sentry-go/pull/618))
|
||||
- Remove outdated documentation from https://pkg.go.dev/github.com/getsentry/sentry-go ([#623](https://github.com/getsentry/sentry-go/pull/623))
|
||||
|
||||
## 0.20.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.20.0.
|
||||
|
||||
Note: this release has some **breaking changes**, which are listed below.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Remove the following methods: `Scope.SetTransaction()`, `Scope.Transaction()` ([#605](https://github.com/getsentry/sentry-go/pull/605))
|
||||
|
||||
Span.Name should be used instead to access the transaction's name.
|
||||
|
||||
For example, the following [`TracesSampler`](https://docs.sentry.io/platforms/go/configuration/sampling/#setting-a-sampling-function) function should be now written as follows:
|
||||
|
||||
**Before:**
|
||||
```go
|
||||
TracesSampler: func(ctx sentry.SamplingContext) float64 {
|
||||
hub := sentry.GetHubFromContext(ctx.Span.Context())
|
||||
if hub.Scope().Transaction() == "GET /health" {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
},
|
||||
```
|
||||
|
||||
**After:**
|
||||
```go
|
||||
TracesSampler: func(ctx sentry.SamplingContext) float64 {
|
||||
if ctx.Span.Name == "GET /health" {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
},
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- Add `Span.SetContext()` method ([#599](https://github.com/getsentry/sentry-go/pull/599/))
|
||||
- It is recommended to use it instead of `hub.Scope().SetContext` when setting or updating context on transactions.
|
||||
- Add `DebugMeta` interface to `Event` and extend `Frame` structure with more fields ([#606](https://github.com/getsentry/sentry-go/pull/606))
|
||||
- More about DebugMeta interface [here](https://develop.sentry.dev/sdk/event-payloads/debugmeta/).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- [otel] Fix missing OpenTelemetry context on some events ([#599](https://github.com/getsentry/sentry-go/pull/599), [#605](https://github.com/getsentry/sentry-go/pull/605))
|
||||
- Fixes ([#596](https://github.com/getsentry/sentry-go/issues/596)).
|
||||
- [otel] Better handling for HTTP span attributes ([#610](https://github.com/getsentry/sentry-go/pull/610))
|
||||
|
||||
### Misc
|
||||
|
||||
- Bump minimum versions: `github.com/kataras/iris/v12` to 12.2.0, `github.com/labstack/echo/v4` to v4.10.0 ([#595](https://github.com/getsentry/sentry-go/pull/595))
|
||||
- Resolves [GO-2022-1144 / CVE-2022-41717](https://deps.dev/advisory/osv/GO-2022-1144), [GO-2023-1495 / CVE-2022-41721](https://deps.dev/advisory/osv/GO-2023-1495), [GO-2022-1059 / CVE-2022-32149](https://deps.dev/advisory/osv/GO-2022-1059).
|
||||
- Bump `google.golang.org/protobuf` minimum required version to 1.29.1 ([#604](https://github.com/getsentry/sentry-go/pull/604))
|
||||
- This fixes a potential denial of service issue ([CVE-2023-24535](https://github.com/advisories/GHSA-hw7c-3rfg-p46j)).
|
||||
- Exclude the `otel` module when building in GOPATH mode ([#615](https://github.com/getsentry/sentry-go/pull/615))
|
||||
|
||||
## 0.19.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.19.0.
|
||||
|
||||
### Features
|
||||
|
||||
- Add support for exception mechanism metadata ([#564](https://github.com/getsentry/sentry-go/pull/564/))
|
||||
- More about exception mechanisms [here](https://develop.sentry.dev/sdk/event-payloads/exception/#exception-mechanism).
|
||||
|
||||
### Bug Fixes
|
||||
- [otel] Use the correct "trace" context when sending a Sentry error ([#580](https://github.com/getsentry/sentry-go/pull/580/))
|
||||
|
||||
|
||||
### Misc
|
||||
- Drop support for Go 1.17, add support for Go 1.20 ([#563](https://github.com/getsentry/sentry-go/pull/563/))
|
||||
- According to our policy, we're officially supporting the last three minor releases of Go.
|
||||
- Switch repository license to MIT ([#583](https://github.com/getsentry/sentry-go/pull/583/))
|
||||
- More about Sentry licensing [here](https://open.sentry.io/licensing/).
|
||||
- Bump `golang.org/x/text` minimum required version to 0.3.8 ([#586](https://github.com/getsentry/sentry-go/pull/586))
|
||||
- This fixes [CVE-2022-32149](https://github.com/advisories/GHSA-69ch-w2m2-3vjp) vulnerability.
|
||||
|
||||
## 0.18.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.18.0.
|
||||
This release contains initial support for [OpenTelemetry](https://opentelemetry.io/) and various other bug fixes and improvements.
|
||||
|
||||
**Note**: This is the last release supporting Go 1.17.
|
||||
|
||||
### Features
|
||||
|
||||
- Initial support for [OpenTelemetry](https://opentelemetry.io/).
|
||||
You can now send all your OpenTelemetry spans to Sentry.
|
||||
|
||||
Install the `otel` module
|
||||
|
||||
```bash
|
||||
go get github.com/getsentry/sentry-go \
|
||||
github.com/getsentry/sentry-go/otel
|
||||
```
|
||||
|
||||
Configure the Sentry and OpenTelemetry SDKs
|
||||
|
||||
```go
|
||||
import (
|
||||
"go.opentelemetry.io/otel"
|
||||
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/getsentry/sentry-go/otel"
|
||||
// ...
|
||||
)
|
||||
|
||||
// Initlaize the Sentry SDK
|
||||
sentry.Init(sentry.ClientOptions{
|
||||
Dsn: "__DSN__",
|
||||
EnableTracing: true,
|
||||
TracesSampleRate: 1.0,
|
||||
})
|
||||
|
||||
// Set up the Sentry span processor
|
||||
tp := sdktrace.NewTracerProvider(
|
||||
sdktrace.WithSpanProcessor(sentryotel.NewSentrySpanProcessor()),
|
||||
// ...
|
||||
)
|
||||
otel.SetTracerProvider(tp)
|
||||
|
||||
// Set up the Sentry propagator
|
||||
otel.SetTextMapPropagator(sentryotel.NewSentryPropagator())
|
||||
```
|
||||
|
||||
You can read more about using OpenTelemetry with Sentry in our [docs](https://docs.sentry.io/platforms/go/performance/instrumentation/opentelemetry/).
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Do not freeze the Dynamic Sampling Context when no Sentry values are present in the baggage header ([#532](https://github.com/getsentry/sentry-go/pull/532))
|
||||
- Create a frozen Dynamic Sampling Context when calling `span.ToBaggage()` ([#566](https://github.com/getsentry/sentry-go/pull/566))
|
||||
- Fix baggage parsing and encoding in vendored otel package ([#568](https://github.com/getsentry/sentry-go/pull/568))
|
||||
|
||||
### Misc
|
||||
|
||||
- Add `Span.SetDynamicSamplingContext()` ([#539](https://github.com/getsentry/sentry-go/pull/539/))
|
||||
- Add various getters for `Dsn` ([#540](https://github.com/getsentry/sentry-go/pull/540))
|
||||
- Add `SpanOption::SpanSampled` ([#546](https://github.com/getsentry/sentry-go/pull/546))
|
||||
- Add `Span.SetData()` ([#542](https://github.com/getsentry/sentry-go/pull/542))
|
||||
- Add `Span.IsTransaction()` ([#543](https://github.com/getsentry/sentry-go/pull/543))
|
||||
- Add `Span.GetTransaction()` method ([#558](https://github.com/getsentry/sentry-go/pull/558))
|
||||
|
||||
## 0.17.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.17.0.
|
||||
This release contains a new `BeforeSendTransaction` hook option and corrects two regressions introduced in `0.16.0`.
|
||||
|
||||
### Features
|
||||
|
||||
- Add `BeforeSendTransaction` hook to `ClientOptions` ([#517](https://github.com/getsentry/sentry-go/pull/517))
|
||||
- Here's [an example](https://github.com/getsentry/sentry-go/blob/master/_examples/http/main.go#L56-L66) of how BeforeSendTransaction can be used to modify or drop transaction events.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Do not crash in Span.Finish() when the Client is empty [#520](https://github.com/getsentry/sentry-go/pull/520)
|
||||
- Fixes [#518](https://github.com/getsentry/sentry-go/issues/518)
|
||||
- Attach non-PII/non-sensitive request headers to events when `ClientOptions.SendDefaultPii` is set to `false` ([#524](https://github.com/getsentry/sentry-go/pull/524))
|
||||
- Fixes [#523](https://github.com/getsentry/sentry-go/issues/523)
|
||||
|
||||
### Misc
|
||||
|
||||
- Clarify how to handle logrus.Fatalf events ([#501](https://github.com/getsentry/sentry-go/pull/501/))
|
||||
- Rename the `examples` directory to `_examples` ([#521](https://github.com/getsentry/sentry-go/pull/521))
|
||||
- This removes an indirect dependency to `github.com/golang-jwt/jwt`
|
||||
|
||||
## 0.16.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.16.0.
|
||||
Due to ongoing work towards a stable API for `v1.0.0`, we sadly had to include **two breaking changes** in this release.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Add `EnableTracing`, a boolean option flag to enable performance monitoring (`false` by default).
|
||||
- If you're using `TracesSampleRate` or `TracesSampler`, this option is **required** to enable performance monitoring.
|
||||
|
||||
```go
|
||||
sentry.Init(sentry.ClientOptions{
|
||||
EnableTracing: true,
|
||||
TracesSampleRate: 1.0,
|
||||
})
|
||||
```
|
||||
- Unify TracesSampler [#498](https://github.com/getsentry/sentry-go/pull/498)
|
||||
- `TracesSampler` was changed to a callback that must return a `float64` between `0.0` and `1.0`.
|
||||
|
||||
For example, you can apply a sample rate of `1.0` (100%) to all `/api` transactions, and a sample rate of `0.5` (50%) to all other transactions.
|
||||
You can read more about this in our [SDK docs](https://docs.sentry.io/platforms/go/configuration/filtering/#using-sampling-to-filter-transaction-events).
|
||||
|
||||
```go
|
||||
sentry.Init(sentry.ClientOptions{
|
||||
TracesSampler: sentry.TracesSampler(func(ctx sentry.SamplingContext) float64 {
|
||||
hub := sentry.GetHubFromContext(ctx.Span.Context())
|
||||
name := hub.Scope().Transaction()
|
||||
|
||||
if strings.HasPrefix(name, "GET /api") {
|
||||
return 1.0
|
||||
}
|
||||
|
||||
return 0.5
|
||||
}),
|
||||
}
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- Send errors logged with [Logrus](https://github.com/sirupsen/logrus) to Sentry.
|
||||
- Have a look at our [logrus examples](https://github.com/getsentry/sentry-go/blob/master/_examples/logrus/main.go) on how to use the integration.
|
||||
- Add support for Dynamic Sampling [#491](https://github.com/getsentry/sentry-go/pull/491)
|
||||
- You can read more about Dynamic Sampling in our [product docs](https://docs.sentry.io/product/data-management-settings/dynamic-sampling/).
|
||||
- Add detailed logging about the reason transactions are being dropped.
|
||||
- You can enable SDK logging via `sentry.ClientOptions.Debug: true`.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Do not clone the hub when calling `StartTransaction` [#505](https://github.com/getsentry/sentry-go/pull/505)
|
||||
- Fixes [#502](https://github.com/getsentry/sentry-go/issues/502)
|
||||
|
||||
## 0.15.0
|
||||
|
||||
- fix: Scope values should not override Event values (#446)
|
||||
- feat: Make maximum amount of spans configurable (#460)
|
||||
- feat: Add a method to start a transaction (#482)
|
||||
- feat: Extend User interface by adding Data, Name and Segment (#483)
|
||||
- feat: Add ClientOptions.SendDefaultPII (#485)
|
||||
|
||||
## 0.14.0
|
||||
|
||||
- feat: Add function to continue from trace string (#434)
|
||||
- feat: Add `max-depth` options (#428)
|
||||
- *[breaking]* ref: Use a `Context` type mapping to a `map[string]interface{}` for all event contexts (#444)
|
||||
- *[breaking]* ref: Replace deprecated `ioutil` pkg with `os` & `io` (#454)
|
||||
- ref: Optimize `stacktrace.go` from size and speed (#467)
|
||||
- ci: Test against `go1.19` and `go1.18`, drop `go1.16` and `go1.15` support (#432, #477)
|
||||
- deps: Dependency update to fix CVEs (#462, #464, #477)
|
||||
|
||||
_NOTE:_ This version drops support for Go 1.16 and Go 1.15. The currently supported Go versions are the last 3 stable releases: 1.19, 1.18 and 1.17.
|
||||
|
||||
## v0.13.0
|
||||
|
||||
- ref: Change DSN ProjectID to be a string (#420)
|
||||
@@ -57,7 +521,7 @@ There are no breaking changes and upgrading should be a smooth experience for al
|
||||
_NOTE:_
|
||||
This version introduces support for [Sentry's Performance Monitoring](https://docs.sentry.io/platforms/go/performance/).
|
||||
The new tracing capabilities are beta, and we plan to expand them on future versions. Feedback is welcome, please open new issues on GitHub.
|
||||
The `sentryhttp` package got better API docs, an [updated usage example](https://github.com/getsentry/sentry-go/tree/master/example/http) and support for creating automatic transactions as part of Performance Monitoring.
|
||||
The `sentryhttp` package got better API docs, an [updated usage example](https://github.com/getsentry/sentry-go/tree/master/_examples/http) and support for creating automatic transactions as part of Performance Monitoring.
|
||||
|
||||
## v0.8.0
|
||||
|
||||
|
||||
2
vendor/github.com/getsentry/sentry-go/CONTRIBUTING.md
generated
vendored
2
vendor/github.com/getsentry/sentry-go/CONTRIBUTING.md
generated
vendored
@@ -72,6 +72,8 @@ $ go test -race -coverprofile=coverage.txt -covermode=atomic && go tool cover -h
|
||||
|
||||
## Linting
|
||||
|
||||
Lint with [`golangci-lint`](https://github.com/golangci/golangci-lint):
|
||||
|
||||
```console
|
||||
$ golangci-lint run
|
||||
```
|
||||
|
||||
24
vendor/github.com/getsentry/sentry-go/LICENSE
generated
vendored
24
vendor/github.com/getsentry/sentry-go/LICENSE
generated
vendored
@@ -1,9 +1,21 @@
|
||||
Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors.
|
||||
All rights reserved.
|
||||
MIT License
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
Copyright (c) 2019 Functional Software, Inc. dba Sentry
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
83
vendor/github.com/getsentry/sentry-go/Makefile
generated
vendored
Normal file
83
vendor/github.com/getsentry/sentry-go/Makefile
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
MKFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
|
||||
MKFILE_DIR := $(dir $(MKFILE_PATH))
|
||||
ALL_GO_MOD_DIRS := $(shell find . -type f -name 'go.mod' -exec dirname {} \; | sort)
|
||||
GO = go
|
||||
TIMEOUT = 300
|
||||
|
||||
# Parse Makefile and display the help
|
||||
help: ## Show help
|
||||
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||
.PHONY: help
|
||||
|
||||
build: ## Build everything
|
||||
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||
cd "$${dir}"; \
|
||||
echo ">>> Running 'go build' for module: $${dir}"; \
|
||||
go build ./...; \
|
||||
done;
|
||||
.PHONY: build
|
||||
|
||||
### Tests (inspired by https://github.com/open-telemetry/opentelemetry-go/blob/main/Makefile)
|
||||
TEST_TARGETS := test-short test-verbose test-race
|
||||
test-race: ARGS=-race
|
||||
test-short: ARGS=-short
|
||||
test-verbose: ARGS=-v -race
|
||||
$(TEST_TARGETS): test
|
||||
test: $(ALL_GO_MOD_DIRS:%=test/%) ## Run tests
|
||||
test/%: DIR=$*
|
||||
test/%:
|
||||
@echo ">>> Running tests for module: $(DIR)"
|
||||
@# We use '-count=1' to disable test caching.
|
||||
(cd $(DIR) && $(GO) test -count=1 -timeout $(TIMEOUT)s $(ARGS) ./...)
|
||||
.PHONY: $(TEST_TARGETS) test
|
||||
|
||||
# Coverage
|
||||
COVERAGE_MODE = atomic
|
||||
COVERAGE_PROFILE = coverage.out
|
||||
COVERAGE_REPORT_DIR = .coverage
|
||||
COVERAGE_REPORT_DIR_ABS = "$(MKFILE_DIR)/$(COVERAGE_REPORT_DIR)"
|
||||
$(COVERAGE_REPORT_DIR):
|
||||
mkdir -p $(COVERAGE_REPORT_DIR)
|
||||
clean-report-dir: $(COVERAGE_REPORT_DIR)
|
||||
test $(COVERAGE_REPORT_DIR) && rm -f $(COVERAGE_REPORT_DIR)/*
|
||||
test-coverage: $(COVERAGE_REPORT_DIR) clean-report-dir ## Test with coverage enabled
|
||||
set -e ; \
|
||||
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||
echo ">>> Running tests with coverage for module: $${dir}"; \
|
||||
DIR_ABS=$$(python -c 'import os, sys; print(os.path.realpath(sys.argv[1]))' $${dir}) ; \
|
||||
REPORT_NAME=$$(basename $${DIR_ABS}); \
|
||||
(cd "$${dir}" && \
|
||||
$(GO) test -count=1 -timeout $(TIMEOUT)s -coverpkg=./... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" ./... && \
|
||||
cp $(COVERAGE_PROFILE) "$(COVERAGE_REPORT_DIR_ABS)/$${REPORT_NAME}_$(COVERAGE_PROFILE)" && \
|
||||
$(GO) tool cover -html=$(COVERAGE_PROFILE) -o coverage.html); \
|
||||
done;
|
||||
.PHONY: test-coverage clean-report-dir
|
||||
|
||||
mod-tidy: ## Check go.mod tidiness
|
||||
set -e ; \
|
||||
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||
cd "$${dir}"; \
|
||||
echo ">>> Running 'go mod tidy' for module: $${dir}"; \
|
||||
go mod tidy -go=1.18 -compat=1.18; \
|
||||
done; \
|
||||
git diff --exit-code;
|
||||
.PHONY: mod-tidy
|
||||
|
||||
vet: ## Run "go vet"
|
||||
set -e ; \
|
||||
for dir in $(ALL_GO_MOD_DIRS); do \
|
||||
cd "$${dir}"; \
|
||||
echo ">>> Running 'go vet' for module: $${dir}"; \
|
||||
go vet ./...; \
|
||||
done;
|
||||
.PHONY: vet
|
||||
|
||||
lint: ## Lint (using "golangci-lint")
|
||||
golangci-lint run
|
||||
.PHONY: lint
|
||||
|
||||
fmt: ## Format all Go files
|
||||
gofmt -l -w -s .
|
||||
.PHONY: fmt
|
||||
35
vendor/github.com/getsentry/sentry-go/README.md
generated
vendored
35
vendor/github.com/getsentry/sentry-go/README.md
generated
vendored
@@ -1,8 +1,11 @@
|
||||
<p align="center">
|
||||
<a href="https://sentry.io" target="_blank" align="center">
|
||||
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
|
||||
<a href="https://sentry.io/?utm_source=github&utm_medium=logo" target="_blank">
|
||||
<picture>
|
||||
<source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-white.png" media="(prefers-color-scheme: dark)" />
|
||||
<source srcset="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" />
|
||||
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" alt="Sentry" width="280">
|
||||
</picture>
|
||||
</a>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
# Official Sentry SDK for Go
|
||||
@@ -14,11 +17,11 @@
|
||||
[](https://pkg.go.dev/github.com/getsentry/sentry-go)
|
||||
|
||||
`sentry-go` provides a Sentry client implementation for the Go programming
|
||||
language. This is the next line of the Go SDK for [Sentry](https://sentry.io/),
|
||||
language. This is the next generation of the Go SDK for [Sentry](https://sentry.io/),
|
||||
intended to replace the `raven-go` package.
|
||||
|
||||
> Looking for the old `raven-go` SDK documentation? See the Legacy client section [here](https://docs.sentry.io/clients/go/).
|
||||
> If you want to start using sentry-go instead, check out the [migration guide](https://docs.sentry.io/platforms/go/migration/).
|
||||
> If you want to start using `sentry-go` instead, check out the [migration guide](https://docs.sentry.io/platforms/go/migration/).
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -26,7 +29,7 @@ The only requirement is a Go compiler.
|
||||
|
||||
We verify this package against the 3 most recent releases of Go. Those are the
|
||||
supported versions. The exact versions are defined in
|
||||
[`GitHub workflow`](.github/workflows/ci.yml).
|
||||
[`GitHub workflow`](.github/workflows/test.yml).
|
||||
|
||||
In addition, we run tests against the current master branch of the Go toolchain,
|
||||
though support for this configuration is best-effort.
|
||||
@@ -35,19 +38,11 @@ though support for this configuration is best-effort.
|
||||
|
||||
`sentry-go` can be installed like any other Go library through `go get`:
|
||||
|
||||
```console
|
||||
$ go get github.com/getsentry/sentry-go
|
||||
```
|
||||
|
||||
Or, if you are already using
|
||||
[Go Modules](https://github.com/golang/go/wiki/Modules), you may specify a
|
||||
version number as well:
|
||||
|
||||
```console
|
||||
$ go get github.com/getsentry/sentry-go@latest
|
||||
```
|
||||
|
||||
Check out the [list of released versions](https://pkg.go.dev/github.com/getsentry/sentry-go?tab=versions).
|
||||
Check out the [list of released versions](https://github.com/getsentry/sentry-go/releases).
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -67,9 +62,9 @@ More on this in the [Configuration section of the official Sentry Go SDK documen
|
||||
|
||||
The SDK supports reporting errors and tracking application performance.
|
||||
|
||||
To get started, have a look at one of our [examples](example/):
|
||||
- [Basic error instrumentation](example/basic/main.go)
|
||||
- [Error and tracing for HTTP servers](example/http/main.go)
|
||||
To get started, have a look at one of our [examples](_examples/):
|
||||
- [Basic error instrumentation](_examples/basic/main.go)
|
||||
- [Error and tracing for HTTP servers](_examples/http/main.go)
|
||||
|
||||
We also provide a [complete API reference](https://pkg.go.dev/github.com/getsentry/sentry-go).
|
||||
|
||||
@@ -93,7 +88,7 @@ checkout the official documentation:
|
||||
- [](https://godoc.org/github.com/getsentry/sentry-go)
|
||||
- [](https://pkg.go.dev/github.com/getsentry/sentry-go)
|
||||
- [](https://docs.sentry.io/platforms/go/)
|
||||
- [](https://forum.sentry.io/c/sdks)
|
||||
- [](https://github.com/getsentry/sentry-go/discussions)
|
||||
- [](https://discord.gg/Ww9hbqr)
|
||||
- [](http://stackoverflow.com/questions/tagged/sentry)
|
||||
- [](https://twitter.com/intent/follow?screen_name=getsentry)
|
||||
@@ -101,7 +96,7 @@ checkout the official documentation:
|
||||
## License
|
||||
|
||||
Licensed under
|
||||
[The 2-Clause BSD License](https://opensource.org/licenses/BSD-2-Clause), see
|
||||
[The MIT License](https://opensource.org/licenses/mit/), see
|
||||
[`LICENSE`](LICENSE).
|
||||
|
||||
## Community
|
||||
|
||||
117
vendor/github.com/getsentry/sentry-go/check_in.go
generated
vendored
Normal file
117
vendor/github.com/getsentry/sentry-go/check_in.go
generated
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
package sentry
|
||||
|
||||
import "time"
|
||||
|
||||
type CheckInStatus string
|
||||
|
||||
const (
|
||||
CheckInStatusInProgress CheckInStatus = "in_progress"
|
||||
CheckInStatusOK CheckInStatus = "ok"
|
||||
CheckInStatusError CheckInStatus = "error"
|
||||
)
|
||||
|
||||
type checkInScheduleType string
|
||||
|
||||
const (
|
||||
checkInScheduleTypeCrontab checkInScheduleType = "crontab"
|
||||
checkInScheduleTypeInterval checkInScheduleType = "interval"
|
||||
)
|
||||
|
||||
type MonitorSchedule interface {
|
||||
// scheduleType is a private method that must be implemented for monitor schedule
|
||||
// implementation. It should never be called. This method is made for having
|
||||
// specific private implementation of MonitorSchedule interface.
|
||||
scheduleType() checkInScheduleType
|
||||
}
|
||||
|
||||
type crontabSchedule struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (c crontabSchedule) scheduleType() checkInScheduleType {
|
||||
return checkInScheduleTypeCrontab
|
||||
}
|
||||
|
||||
// CrontabSchedule defines the MonitorSchedule with a cron format.
|
||||
// Example: "8 * * * *".
|
||||
func CrontabSchedule(scheduleString string) MonitorSchedule {
|
||||
return crontabSchedule{
|
||||
Type: string(checkInScheduleTypeCrontab),
|
||||
Value: scheduleString,
|
||||
}
|
||||
}
|
||||
|
||||
type intervalSchedule struct {
|
||||
Type string `json:"type"`
|
||||
Value int64 `json:"value"`
|
||||
Unit string `json:"unit"`
|
||||
}
|
||||
|
||||
func (i intervalSchedule) scheduleType() checkInScheduleType {
|
||||
return checkInScheduleTypeInterval
|
||||
}
|
||||
|
||||
type MonitorScheduleUnit string
|
||||
|
||||
const (
|
||||
MonitorScheduleUnitMinute MonitorScheduleUnit = "minute"
|
||||
MonitorScheduleUnitHour MonitorScheduleUnit = "hour"
|
||||
MonitorScheduleUnitDay MonitorScheduleUnit = "day"
|
||||
MonitorScheduleUnitWeek MonitorScheduleUnit = "week"
|
||||
MonitorScheduleUnitMonth MonitorScheduleUnit = "month"
|
||||
MonitorScheduleUnitYear MonitorScheduleUnit = "year"
|
||||
)
|
||||
|
||||
// IntervalSchedule defines the MonitorSchedule with an interval format.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// IntervalSchedule(1, sentry.MonitorScheduleUnitDay)
|
||||
func IntervalSchedule(value int64, unit MonitorScheduleUnit) MonitorSchedule {
|
||||
return intervalSchedule{
|
||||
Type: string(checkInScheduleTypeInterval),
|
||||
Value: value,
|
||||
Unit: string(unit),
|
||||
}
|
||||
}
|
||||
|
||||
type MonitorConfig struct { //nolint: maligned // prefer readability over optimal memory layout
|
||||
Schedule MonitorSchedule `json:"schedule,omitempty"`
|
||||
// The allowed margin of minutes after the expected check-in time that
|
||||
// the monitor will not be considered missed for.
|
||||
CheckInMargin int64 `json:"checkin_margin,omitempty"`
|
||||
// The allowed duration in minutes that the monitor may be `in_progress`
|
||||
// for before being considered failed due to timeout.
|
||||
MaxRuntime int64 `json:"max_runtime,omitempty"`
|
||||
// A tz database string representing the timezone which the monitor's execution schedule is in.
|
||||
// See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||
Timezone string `json:"timezone,omitempty"`
|
||||
}
|
||||
|
||||
type CheckIn struct { //nolint: maligned // prefer readability over optimal memory layout
|
||||
// Check-In ID (unique and client generated)
|
||||
ID EventID `json:"check_in_id"`
|
||||
// The distinct slug of the monitor.
|
||||
MonitorSlug string `json:"monitor_slug"`
|
||||
// The status of the check-in.
|
||||
Status CheckInStatus `json:"status"`
|
||||
// The duration of the check-in. Will only take effect if the status is ok or error.
|
||||
Duration time.Duration `json:"duration,omitempty"`
|
||||
}
|
||||
|
||||
// serializedCheckIn is used by checkInMarshalJSON method on Event struct.
|
||||
// See https://develop.sentry.dev/sdk/check-ins/
|
||||
type serializedCheckIn struct { //nolint: maligned
|
||||
// Check-In ID (unique and client generated).
|
||||
CheckInID string `json:"check_in_id"`
|
||||
// The distinct slug of the monitor.
|
||||
MonitorSlug string `json:"monitor_slug"`
|
||||
// The status of the check-in.
|
||||
Status CheckInStatus `json:"status"`
|
||||
// The duration of the check-in in seconds. Will only take effect if the status is ok or error.
|
||||
Duration float64 `json:"duration,omitempty"`
|
||||
Release string `json:"release,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
MonitorConfig *MonitorConfig `json:"monitor_config,omitempty"`
|
||||
}
|
||||
291
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
291
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
@@ -3,15 +3,12 @@ package sentry
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -20,6 +17,9 @@ import (
|
||||
"github.com/getsentry/sentry-go/internal/debug"
|
||||
)
|
||||
|
||||
// The identifier of the SDK.
|
||||
const sdkIdentifier = "sentry.go"
|
||||
|
||||
// maxErrorDepth is the maximum number of errors reported in a chain of errors.
|
||||
// This protects the SDK from an arbitrarily long chain of wrapped errors.
|
||||
//
|
||||
@@ -29,6 +29,11 @@ import (
|
||||
// stack trace is often the most useful information.
|
||||
const maxErrorDepth = 10
|
||||
|
||||
// defaultMaxSpans limits the default number of recorded spans per transaction. The limit is
|
||||
// meant to bound memory usage and prevent too large transaction events that
|
||||
// would be rejected by Sentry.
|
||||
const defaultMaxSpans = 1000
|
||||
|
||||
// hostname is the host name reported by the kernel. It is precomputed once to
|
||||
// avoid syscalls when capturing events.
|
||||
//
|
||||
@@ -60,7 +65,7 @@ func (r *lockedRand) Float64() float64 {
|
||||
// other hand, the source returned from rand.NewSource is not safe for
|
||||
// concurrent use, so we need to couple its use with a sync.Mutex.
|
||||
var rng = &lockedRand{
|
||||
//#nosec G404 -- We are fine using transparent, non-secure value here.
|
||||
// #nosec G404 -- We are fine using transparent, non-secure value here.
|
||||
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
|
||||
@@ -74,7 +79,7 @@ type usageError struct {
|
||||
|
||||
// Logger is an instance of log.Logger that is use to provide debug information about running Sentry Client
|
||||
// can be enabled by either using Logger.SetOutput directly or with Debug client option.
|
||||
var Logger = log.New(ioutil.Discard, "[Sentry] ", log.LstdFlags)
|
||||
var Logger = log.New(io.Discard, "[Sentry] ", log.LstdFlags)
|
||||
|
||||
// EventProcessor is a function that processes an event.
|
||||
// Event processors are used to change an event before it is sent to Sentry.
|
||||
@@ -122,18 +127,31 @@ type ClientOptions struct {
|
||||
// 0.0 is treated as if it was 1.0. To drop all events, set the DSN to the
|
||||
// empty string.
|
||||
SampleRate float64
|
||||
// Enable performance tracing.
|
||||
EnableTracing bool
|
||||
// The sample rate for sampling traces in the range [0.0, 1.0].
|
||||
TracesSampleRate float64
|
||||
// Used to customize the sampling of traces, overrides TracesSampleRate.
|
||||
TracesSampler TracesSampler
|
||||
// The sample rate for profiling traces in the range [0.0, 1.0].
|
||||
// This is relative to TracesSampleRate - it is a ratio of profiled traces out of all sampled traces.
|
||||
ProfilesSampleRate float64
|
||||
// List of regexp strings that will be used to match against event's message
|
||||
// and if applicable, caught errors type and value.
|
||||
// If the match is found, then a whole event will be dropped.
|
||||
IgnoreErrors []string
|
||||
// List of regexp strings that will be used to match against a transaction's
|
||||
// name. If a match is found, then the transaction will be dropped.
|
||||
IgnoreTransactions []string
|
||||
// If this flag is enabled, certain personally identifiable information (PII) is added by active integrations.
|
||||
// By default, no such data is sent.
|
||||
SendDefaultPII bool
|
||||
// BeforeSend is called before error events are sent to Sentry.
|
||||
// Use it to mutate the event or return nil to discard the event.
|
||||
// See EventProcessor if you need to mutate transactions.
|
||||
BeforeSend func(event *Event, hint *EventHint) *Event
|
||||
// BeforeSendTransaction is called before transaction events are sent to Sentry.
|
||||
// Use it to mutate the transaction or return nil to discard the transaction.
|
||||
BeforeSendTransaction func(event *Event, hint *EventHint) *Event
|
||||
// Before breadcrumb add callback.
|
||||
BeforeBreadcrumb func(breadcrumb *Breadcrumb, hint *BreadcrumbHint) *Breadcrumb
|
||||
// Integrations to be installed on the current Client, receives default
|
||||
@@ -173,8 +191,14 @@ type ClientOptions struct {
|
||||
Dist string
|
||||
// The environment to be sent with events.
|
||||
Environment string
|
||||
// Maximum number of breadcrumbs.
|
||||
// Maximum number of breadcrumbs
|
||||
// when MaxBreadcrumbs is negative then ignore breadcrumbs.
|
||||
MaxBreadcrumbs int
|
||||
// Maximum number of spans.
|
||||
//
|
||||
// See https://develop.sentry.dev/sdk/envelopes/#size-limits for size limits
|
||||
// applied during event ingestion. Events that exceed these limits might get dropped.
|
||||
MaxSpans int
|
||||
// An optional pointer to http.Client that will be used with a default
|
||||
// HTTPTransport. Using your own client will make HTTPTransport, HTTPProxy,
|
||||
// HTTPSProxy and CaCerts options ignored.
|
||||
@@ -192,15 +216,28 @@ type ClientOptions struct {
|
||||
HTTPSProxy string
|
||||
// An optional set of SSL certificates to use.
|
||||
CaCerts *x509.CertPool
|
||||
// MaxErrorDepth is the maximum number of errors reported in a chain of errors.
|
||||
// This protects the SDK from an arbitrarily long chain of wrapped errors.
|
||||
//
|
||||
// An additional consideration is that arguably reporting a long chain of errors
|
||||
// is of little use when debugging production errors with Sentry. The Sentry UI
|
||||
// is not optimized for long chains either. The top-level error together with a
|
||||
// stack trace is often the most useful information.
|
||||
MaxErrorDepth int
|
||||
// Default event tags. These are overridden by tags set on a scope.
|
||||
Tags map[string]string
|
||||
}
|
||||
|
||||
// Client is the underlying processor that is used by the main API and Hub
|
||||
// instances. It must be created with NewClient.
|
||||
type Client struct {
|
||||
mu sync.RWMutex
|
||||
options ClientOptions
|
||||
dsn *Dsn
|
||||
eventProcessors []EventProcessor
|
||||
integrations []Integration
|
||||
sdkIdentifier string
|
||||
sdkVersion string
|
||||
// Transport is read-only. Replacing the transport of an existing client is
|
||||
// not supported, create a new client instead.
|
||||
Transport Transport
|
||||
@@ -214,8 +251,26 @@ type Client struct {
|
||||
// single goroutine) or hub methods (for concurrent programs, for example web
|
||||
// servers).
|
||||
func NewClient(options ClientOptions) (*Client, error) {
|
||||
if options.TracesSampleRate != 0.0 && options.TracesSampler != nil {
|
||||
return nil, errors.New("TracesSampleRate and TracesSampler are mutually exclusive")
|
||||
// The default error event sample rate for all SDKs is 1.0 (send all).
|
||||
//
|
||||
// In Go, the zero value (default) for float64 is 0.0, which means that
|
||||
// constructing a client with NewClient(ClientOptions{}), or, equivalently,
|
||||
// initializing the SDK with Init(ClientOptions{}) without an explicit
|
||||
// SampleRate would drop all events.
|
||||
//
|
||||
// To retain the desired default behavior, we exceptionally flip SampleRate
|
||||
// from 0.0 to 1.0 here. Setting the sample rate to 0.0 is not very useful
|
||||
// anyway, and the same end result can be achieved in many other ways like
|
||||
// not initializing the SDK, setting the DSN to the empty string or using an
|
||||
// event processor that always returns nil.
|
||||
//
|
||||
// An alternative API could be such that default options don't need to be
|
||||
// the same as Go's zero values, for example using the Functional Options
|
||||
// pattern. That would either require a breaking change if we want to reuse
|
||||
// the obvious NewClient name, or a new function as an alternative
|
||||
// constructor.
|
||||
if options.SampleRate == 0.0 {
|
||||
options.SampleRate = 1.0
|
||||
}
|
||||
|
||||
if options.Debug {
|
||||
@@ -238,6 +293,14 @@ func NewClient(options ClientOptions) (*Client, error) {
|
||||
options.Environment = os.Getenv("SENTRY_ENVIRONMENT")
|
||||
}
|
||||
|
||||
if options.MaxErrorDepth == 0 {
|
||||
options.MaxErrorDepth = maxErrorDepth
|
||||
}
|
||||
|
||||
if options.MaxSpans == 0 {
|
||||
options.MaxSpans = defaultMaxSpans
|
||||
}
|
||||
|
||||
// SENTRYGODEBUG is a comma-separated list of key=value pairs (similar
|
||||
// to GODEBUG). It is not a supported feature: recognized debug options
|
||||
// may change any time.
|
||||
@@ -271,8 +334,10 @@ func NewClient(options ClientOptions) (*Client, error) {
|
||||
}
|
||||
|
||||
client := Client{
|
||||
options: options,
|
||||
dsn: dsn,
|
||||
options: options,
|
||||
dsn: dsn,
|
||||
sdkIdentifier: sdkIdentifier,
|
||||
sdkVersion: SDKVersion,
|
||||
}
|
||||
|
||||
client.setupTransport()
|
||||
@@ -294,7 +359,7 @@ func (client *Client) setupTransport() {
|
||||
// accommodate more concurrent events.
|
||||
// TODO(tracing): consider using separate buffers per
|
||||
// event type.
|
||||
if opts.TracesSampleRate != 0 || opts.TracesSampler != nil {
|
||||
if opts.EnableTracing {
|
||||
httpTransport.BufferSize = 1000
|
||||
}
|
||||
transport = httpTransport
|
||||
@@ -311,6 +376,8 @@ func (client *Client) setupIntegrations() {
|
||||
new(environmentIntegration),
|
||||
new(modulesIntegration),
|
||||
new(ignoreErrorsIntegration),
|
||||
new(ignoreTransactionsIntegration),
|
||||
new(globalTagsIntegration),
|
||||
}
|
||||
|
||||
if client.options.Integrations != nil {
|
||||
@@ -326,6 +393,10 @@ func (client *Client) setupIntegrations() {
|
||||
integration.SetupOnce(client)
|
||||
Logger.Printf("Integration installed: %s\n", integration.Name())
|
||||
}
|
||||
|
||||
sort.Slice(client.integrations, func(i, j int) bool {
|
||||
return client.integrations[i].Name() < client.integrations[j].Name()
|
||||
})
|
||||
}
|
||||
|
||||
// AddEventProcessor adds an event processor to the client. It must not be
|
||||
@@ -340,22 +411,33 @@ func (client *Client) AddEventProcessor(processor EventProcessor) {
|
||||
}
|
||||
|
||||
// Options return ClientOptions for the current Client.
|
||||
func (client Client) Options() ClientOptions {
|
||||
func (client *Client) Options() ClientOptions {
|
||||
// Note: internally, consider using `client.options` instead of `client.Options()` to avoid copying the object each time.
|
||||
return client.options
|
||||
}
|
||||
|
||||
// CaptureMessage captures an arbitrary message.
|
||||
func (client *Client) CaptureMessage(message string, hint *EventHint, scope EventModifier) *EventID {
|
||||
event := client.eventFromMessage(message, LevelInfo)
|
||||
event := client.EventFromMessage(message, LevelInfo)
|
||||
return client.CaptureEvent(event, hint, scope)
|
||||
}
|
||||
|
||||
// CaptureException captures an error.
|
||||
func (client *Client) CaptureException(exception error, hint *EventHint, scope EventModifier) *EventID {
|
||||
event := client.eventFromException(exception, LevelError)
|
||||
event := client.EventFromException(exception, LevelError)
|
||||
return client.CaptureEvent(event, hint, scope)
|
||||
}
|
||||
|
||||
// CaptureCheckIn captures a check in.
|
||||
func (client *Client) CaptureCheckIn(checkIn *CheckIn, monitorConfig *MonitorConfig, scope EventModifier) *EventID {
|
||||
event := client.EventFromCheckIn(checkIn, monitorConfig)
|
||||
if event != nil && event.CheckIn != nil {
|
||||
client.CaptureEvent(event, nil, scope)
|
||||
return &event.CheckIn.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CaptureEvent captures an event on the currently active client if any.
|
||||
//
|
||||
// The event must already be assembled. Typically code would instead use
|
||||
@@ -376,7 +458,7 @@ func (client *Client) Recover(err interface{}, hint *EventHint, scope EventModif
|
||||
// use the Context for communicating deadline nor cancelation. All it does
|
||||
// is store the Context in the EventHint and there nil means the Context is
|
||||
// not available.
|
||||
//nolint: staticcheck
|
||||
// nolint: staticcheck
|
||||
return client.RecoverWithContext(nil, err, hint, scope)
|
||||
}
|
||||
|
||||
@@ -407,11 +489,11 @@ func (client *Client) RecoverWithContext(
|
||||
var event *Event
|
||||
switch err := err.(type) {
|
||||
case error:
|
||||
event = client.eventFromException(err, LevelFatal)
|
||||
event = client.EventFromException(err, LevelFatal)
|
||||
case string:
|
||||
event = client.eventFromMessage(err, LevelFatal)
|
||||
event = client.EventFromMessage(err, LevelFatal)
|
||||
default:
|
||||
event = client.eventFromMessage(fmt.Sprintf("%#v", err), LevelFatal)
|
||||
event = client.EventFromMessage(fmt.Sprintf("%#v", err), LevelFatal)
|
||||
}
|
||||
return client.CaptureEvent(event, hint, scope)
|
||||
}
|
||||
@@ -431,16 +513,17 @@ func (client *Client) Flush(timeout time.Duration) bool {
|
||||
return client.Transport.Flush(timeout)
|
||||
}
|
||||
|
||||
func (client *Client) eventFromMessage(message string, level Level) *Event {
|
||||
// EventFromMessage creates an event from the given message string.
|
||||
func (client *Client) EventFromMessage(message string, level Level) *Event {
|
||||
if message == "" {
|
||||
err := usageError{fmt.Errorf("%s called with empty message", callerFunctionName())}
|
||||
return client.eventFromException(err, level)
|
||||
return client.EventFromException(err, level)
|
||||
}
|
||||
event := NewEvent()
|
||||
event.Level = level
|
||||
event.Message = message
|
||||
|
||||
if client.Options().AttachStacktrace {
|
||||
if client.options.AttachStacktrace {
|
||||
event.Threads = []Thread{{
|
||||
Stacktrace: NewStacktrace(),
|
||||
Crashed: false,
|
||||
@@ -451,45 +534,62 @@ func (client *Client) eventFromMessage(message string, level Level) *Event {
|
||||
return event
|
||||
}
|
||||
|
||||
func (client *Client) eventFromException(exception error, level Level) *Event {
|
||||
// EventFromException creates a new Sentry event from the given `error` instance.
|
||||
func (client *Client) EventFromException(exception error, level Level) *Event {
|
||||
event := NewEvent()
|
||||
event.Level = level
|
||||
|
||||
err := exception
|
||||
if err == nil {
|
||||
err = usageError{fmt.Errorf("%s called with nil error", callerFunctionName())}
|
||||
}
|
||||
|
||||
event := NewEvent()
|
||||
event.Level = level
|
||||
|
||||
for i := 0; i < maxErrorDepth && err != nil; i++ {
|
||||
event.Exception = append(event.Exception, Exception{
|
||||
Value: err.Error(),
|
||||
Type: reflect.TypeOf(err).String(),
|
||||
Stacktrace: ExtractStacktrace(err),
|
||||
})
|
||||
switch previous := err.(type) {
|
||||
case interface{ Unwrap() error }:
|
||||
err = previous.Unwrap()
|
||||
case interface{ Cause() error }:
|
||||
err = previous.Cause()
|
||||
default:
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add a trace of the current stack to the most recent error in a chain if
|
||||
// it doesn't have a stack trace yet.
|
||||
// We only add to the most recent error to avoid duplication and because the
|
||||
// current stack is most likely unrelated to errors deeper in the chain.
|
||||
if event.Exception[0].Stacktrace == nil {
|
||||
event.Exception[0].Stacktrace = NewStacktrace()
|
||||
}
|
||||
|
||||
// event.Exception should be sorted such that the most recent error is last.
|
||||
reverse(event.Exception)
|
||||
event.SetException(err, client.options.MaxErrorDepth)
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
// EventFromCheckIn creates a new Sentry event from the given `check_in` instance.
|
||||
func (client *Client) EventFromCheckIn(checkIn *CheckIn, monitorConfig *MonitorConfig) *Event {
|
||||
if checkIn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
event := NewEvent()
|
||||
event.Type = checkInType
|
||||
|
||||
var checkInID EventID
|
||||
if checkIn.ID == "" {
|
||||
checkInID = EventID(uuid())
|
||||
} else {
|
||||
checkInID = checkIn.ID
|
||||
}
|
||||
|
||||
event.CheckIn = &CheckIn{
|
||||
ID: checkInID,
|
||||
MonitorSlug: checkIn.MonitorSlug,
|
||||
Status: checkIn.Status,
|
||||
Duration: checkIn.Duration,
|
||||
}
|
||||
event.MonitorConfig = monitorConfig
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
func (client *Client) SetSDKIdentifier(identifier string) {
|
||||
client.mu.Lock()
|
||||
defer client.mu.Unlock()
|
||||
|
||||
client.sdkIdentifier = identifier
|
||||
}
|
||||
|
||||
func (client *Client) GetSDKIdentifier() string {
|
||||
client.mu.RLock()
|
||||
defer client.mu.RUnlock()
|
||||
|
||||
return client.sdkIdentifier
|
||||
}
|
||||
|
||||
// reverse reverses the slice a in place.
|
||||
func reverse(a []Exception) {
|
||||
for i := len(a)/2 - 1; i >= 0; i-- {
|
||||
@@ -504,34 +604,10 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
|
||||
return client.CaptureException(err, hint, scope)
|
||||
}
|
||||
|
||||
options := client.Options()
|
||||
|
||||
// The default error event sample rate for all SDKs is 1.0 (send all).
|
||||
//
|
||||
// In Go, the zero value (default) for float64 is 0.0, which means that
|
||||
// constructing a client with NewClient(ClientOptions{}), or, equivalently,
|
||||
// initializing the SDK with Init(ClientOptions{}) without an explicit
|
||||
// SampleRate would drop all events.
|
||||
//
|
||||
// To retain the desired default behavior, we exceptionally flip SampleRate
|
||||
// from 0.0 to 1.0 here. Setting the sample rate to 0.0 is not very useful
|
||||
// anyway, and the same end result can be achieved in many other ways like
|
||||
// not initializing the SDK, setting the DSN to the empty string or using an
|
||||
// event processor that always returns nil.
|
||||
//
|
||||
// An alternative API could be such that default options don't need to be
|
||||
// the same as Go's zero values, for example using the Functional Options
|
||||
// pattern. That would either require a breaking change if we want to reuse
|
||||
// the obvious NewClient name, or a new function as an alternative
|
||||
// constructor.
|
||||
if options.SampleRate == 0.0 {
|
||||
options.SampleRate = 1.0
|
||||
}
|
||||
|
||||
// Transactions are sampled by options.TracesSampleRate or
|
||||
// options.TracesSampler when they are started. All other events
|
||||
// (errors, messages) are sampled here.
|
||||
if event.Type != transactionType && !sample(options.SampleRate) {
|
||||
// options.TracesSampler when they are started. Other events
|
||||
// (errors, messages) are sampled here. Does not apply to check-ins.
|
||||
if event.Type != transactionType && event.Type != checkInType && !sample(client.options.SampleRate) {
|
||||
Logger.Println("Event dropped due to SampleRate hit.")
|
||||
return nil
|
||||
}
|
||||
@@ -540,12 +616,19 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
|
||||
return nil
|
||||
}
|
||||
|
||||
// As per spec, transactions do not go through BeforeSend.
|
||||
if event.Type != transactionType && options.BeforeSend != nil {
|
||||
if hint == nil {
|
||||
hint = &EventHint{}
|
||||
// Apply beforeSend* processors
|
||||
if hint == nil {
|
||||
hint = &EventHint{}
|
||||
}
|
||||
if event.Type == transactionType && client.options.BeforeSendTransaction != nil {
|
||||
// Transaction events
|
||||
if event = client.options.BeforeSendTransaction(event, hint); event == nil {
|
||||
Logger.Println("Transaction dropped due to BeforeSendTransaction callback.")
|
||||
return nil
|
||||
}
|
||||
if event = options.BeforeSend(event, hint); event == nil {
|
||||
} else if event.Type != transactionType && event.Type != checkInType && client.options.BeforeSend != nil {
|
||||
// All other events
|
||||
if event = client.options.BeforeSend(event, hint); event == nil {
|
||||
Logger.Println("Event dropped due to BeforeSend callback.")
|
||||
return nil
|
||||
}
|
||||
@@ -558,6 +641,7 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
|
||||
|
||||
func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventModifier) *Event {
|
||||
if event.EventID == "" {
|
||||
// TODO set EventID when the event is created, same as in other SDKs. It's necessary for profileTransaction.ID.
|
||||
event.EventID = EventID(uuid())
|
||||
}
|
||||
|
||||
@@ -570,33 +654,33 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
|
||||
}
|
||||
|
||||
if event.ServerName == "" {
|
||||
if client.Options().ServerName != "" {
|
||||
event.ServerName = client.Options().ServerName
|
||||
} else {
|
||||
event.ServerName = client.options.ServerName
|
||||
|
||||
if event.ServerName == "" {
|
||||
event.ServerName = hostname
|
||||
}
|
||||
}
|
||||
|
||||
if event.Release == "" && client.Options().Release != "" {
|
||||
event.Release = client.Options().Release
|
||||
if event.Release == "" {
|
||||
event.Release = client.options.Release
|
||||
}
|
||||
|
||||
if event.Dist == "" && client.Options().Dist != "" {
|
||||
event.Dist = client.Options().Dist
|
||||
if event.Dist == "" {
|
||||
event.Dist = client.options.Dist
|
||||
}
|
||||
|
||||
if event.Environment == "" && client.Options().Environment != "" {
|
||||
event.Environment = client.Options().Environment
|
||||
if event.Environment == "" {
|
||||
event.Environment = client.options.Environment
|
||||
}
|
||||
|
||||
event.Platform = "go"
|
||||
event.Sdk = SdkInfo{
|
||||
Name: "sentry.go",
|
||||
Version: Version,
|
||||
Name: client.GetSDKIdentifier(),
|
||||
Version: SDKVersion,
|
||||
Integrations: client.listIntegrations(),
|
||||
Packages: []SdkPackage{{
|
||||
Name: "sentry-go",
|
||||
Version: Version,
|
||||
Version: SDKVersion,
|
||||
}},
|
||||
}
|
||||
|
||||
@@ -625,19 +709,22 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
|
||||
}
|
||||
}
|
||||
|
||||
if event.sdkMetaData.transactionProfile != nil {
|
||||
event.sdkMetaData.transactionProfile.UpdateFromEvent(event)
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
func (client Client) listIntegrations() []string {
|
||||
integrations := make([]string, 0, len(client.integrations))
|
||||
for _, integration := range client.integrations {
|
||||
integrations = append(integrations, integration.Name())
|
||||
func (client *Client) listIntegrations() []string {
|
||||
integrations := make([]string, len(client.integrations))
|
||||
for i, integration := range client.integrations {
|
||||
integrations[i] = integration.Name()
|
||||
}
|
||||
sort.Strings(integrations)
|
||||
return integrations
|
||||
}
|
||||
|
||||
func (client Client) integrationAlreadyInstalled(name string) bool {
|
||||
func (client *Client) integrationAlreadyInstalled(name string) bool {
|
||||
for _, integration := range client.integrations {
|
||||
if integration.Name() == name {
|
||||
return true
|
||||
|
||||
61
vendor/github.com/getsentry/sentry-go/doc.go
generated
vendored
61
vendor/github.com/getsentry/sentry-go/doc.go
generated
vendored
@@ -1,63 +1,6 @@
|
||||
/*
|
||||
Package sentry is the official Sentry SDK for Go.
|
||||
Package repository: https://github.com/getsentry/sentry-go/
|
||||
|
||||
Use it to report errors and track application performance through distributed
|
||||
tracing.
|
||||
|
||||
For more information about Sentry and SDK features please have a look at the
|
||||
documentation site https://docs.sentry.io/platforms/go/.
|
||||
|
||||
Basic Usage
|
||||
|
||||
The first step is to initialize the SDK, providing at a minimum the DSN of your
|
||||
Sentry project. This step is accomplished through a call to sentry.Init.
|
||||
|
||||
func main() {
|
||||
err := sentry.Init(...)
|
||||
...
|
||||
}
|
||||
|
||||
A more detailed yet simple example is available at
|
||||
https://github.com/getsentry/sentry-go/blob/master/example/basic/main.go.
|
||||
|
||||
Error Reporting
|
||||
|
||||
The Capture* functions report messages and errors to Sentry.
|
||||
|
||||
sentry.CaptureMessage(...)
|
||||
sentry.CaptureException(...)
|
||||
sentry.CaptureEvent(...)
|
||||
|
||||
Use similarly named functions in the Hub for concurrent programs like web
|
||||
servers.
|
||||
|
||||
Performance Monitoring
|
||||
|
||||
You can use Sentry to monitor your application's performance. More information
|
||||
on the product page https://docs.sentry.io/product/performance/.
|
||||
|
||||
The StartSpan function creates new spans.
|
||||
|
||||
span := sentry.StartSpan(ctx, "operation")
|
||||
...
|
||||
span.Finish()
|
||||
|
||||
Integrations
|
||||
|
||||
The SDK has support for several Go frameworks, available as subpackages.
|
||||
|
||||
Getting Support
|
||||
|
||||
For paid Sentry.io accounts, head out to https://sentry.io/support.
|
||||
|
||||
For all users, support channels include:
|
||||
Forum: https://forum.sentry.io
|
||||
Discord: https://discord.gg/Ww9hbqr (#go channel)
|
||||
|
||||
If you found an issue with the SDK, please report through
|
||||
https://github.com/getsentry/sentry-go/issues/new/choose.
|
||||
|
||||
For responsibly disclosing a security issue, please follow the steps in
|
||||
https://sentry.io/security/#vulnerability-disclosure.
|
||||
For more information about Sentry and SDK features, please have a look at the official documentation site: https://docs.sentry.io/platforms/go/
|
||||
*/
|
||||
package sentry
|
||||
|
||||
56
vendor/github.com/getsentry/sentry-go/dsn.go
generated
vendored
56
vendor/github.com/getsentry/sentry-go/dsn.go
generated
vendored
@@ -145,19 +145,44 @@ func (dsn Dsn) String() string {
|
||||
return url
|
||||
}
|
||||
|
||||
// StoreAPIURL returns the URL of the store endpoint of the project associated
|
||||
// with the DSN.
|
||||
func (dsn Dsn) StoreAPIURL() *url.URL {
|
||||
return dsn.getAPIURL("store")
|
||||
// Get the scheme of the DSN.
|
||||
func (dsn Dsn) GetScheme() string {
|
||||
return string(dsn.scheme)
|
||||
}
|
||||
|
||||
// EnvelopeAPIURL returns the URL of the envelope endpoint of the project
|
||||
// Get the public key of the DSN.
|
||||
func (dsn Dsn) GetPublicKey() string {
|
||||
return dsn.publicKey
|
||||
}
|
||||
|
||||
// Get the secret key of the DSN.
|
||||
func (dsn Dsn) GetSecretKey() string {
|
||||
return dsn.secretKey
|
||||
}
|
||||
|
||||
// Get the host of the DSN.
|
||||
func (dsn Dsn) GetHost() string {
|
||||
return dsn.host
|
||||
}
|
||||
|
||||
// Get the port of the DSN.
|
||||
func (dsn Dsn) GetPort() int {
|
||||
return dsn.port
|
||||
}
|
||||
|
||||
// Get the path of the DSN.
|
||||
func (dsn Dsn) GetPath() string {
|
||||
return dsn.path
|
||||
}
|
||||
|
||||
// Get the project ID of the DSN.
|
||||
func (dsn Dsn) GetProjectID() string {
|
||||
return dsn.projectID
|
||||
}
|
||||
|
||||
// GetAPIURL returns the URL of the envelope endpoint of the project
|
||||
// associated with the DSN.
|
||||
func (dsn Dsn) EnvelopeAPIURL() *url.URL {
|
||||
return dsn.getAPIURL("envelope")
|
||||
}
|
||||
|
||||
func (dsn Dsn) getAPIURL(s string) *url.URL {
|
||||
func (dsn Dsn) GetAPIURL() *url.URL {
|
||||
var rawURL string
|
||||
rawURL += fmt.Sprintf("%s://%s", dsn.scheme, dsn.host)
|
||||
if dsn.port != dsn.scheme.defaultPort() {
|
||||
@@ -166,15 +191,20 @@ func (dsn Dsn) getAPIURL(s string) *url.URL {
|
||||
if dsn.path != "" {
|
||||
rawURL += dsn.path
|
||||
}
|
||||
rawURL += fmt.Sprintf("/api/%s/%s/", dsn.projectID, s)
|
||||
rawURL += fmt.Sprintf("/api/%s/%s/", dsn.projectID, "envelope")
|
||||
parsedURL, _ := url.Parse(rawURL)
|
||||
return parsedURL
|
||||
}
|
||||
|
||||
// RequestHeaders returns all the necessary headers that have to be used in the transport.
|
||||
// RequestHeaders returns all the necessary headers that have to be used in the transport when seinding events
|
||||
// to the /store endpoint.
|
||||
//
|
||||
// Deprecated: This method shall only be used if you want to implement your own transport that sends events to
|
||||
// the /store endpoint. If you're using the transport provided by the SDK, all necessary headers to authenticate
|
||||
// against the /envelope endpoint are added automatically.
|
||||
func (dsn Dsn) RequestHeaders() map[string]string {
|
||||
auth := fmt.Sprintf("Sentry sentry_version=%s, sentry_timestamp=%d, "+
|
||||
"sentry_client=sentry.go/%s, sentry_key=%s", apiVersion, time.Now().Unix(), Version, dsn.publicKey)
|
||||
"sentry_client=sentry.go/%s, sentry_key=%s", apiVersion, time.Now().Unix(), SDKVersion, dsn.publicKey)
|
||||
|
||||
if dsn.secretKey != "" {
|
||||
auth = fmt.Sprintf("%s, sentry_secret=%s", auth, dsn.secretKey)
|
||||
|
||||
123
vendor/github.com/getsentry/sentry-go/dynamic_sampling_context.go
generated
vendored
Normal file
123
vendor/github.com/getsentry/sentry-go/dynamic_sampling_context.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/getsentry/sentry-go/internal/otel/baggage"
|
||||
)
|
||||
|
||||
const (
|
||||
sentryPrefix = "sentry-"
|
||||
)
|
||||
|
||||
// DynamicSamplingContext holds information about the current event that can be used to make dynamic sampling decisions.
|
||||
type DynamicSamplingContext struct {
|
||||
Entries map[string]string
|
||||
Frozen bool
|
||||
}
|
||||
|
||||
func DynamicSamplingContextFromHeader(header []byte) (DynamicSamplingContext, error) {
|
||||
bag, err := baggage.Parse(string(header))
|
||||
if err != nil {
|
||||
return DynamicSamplingContext{}, err
|
||||
}
|
||||
|
||||
entries := map[string]string{}
|
||||
for _, member := range bag.Members() {
|
||||
// We only store baggage members if their key starts with "sentry-".
|
||||
if k, v := member.Key(), member.Value(); strings.HasPrefix(k, sentryPrefix) {
|
||||
entries[strings.TrimPrefix(k, sentryPrefix)] = v
|
||||
}
|
||||
}
|
||||
|
||||
return DynamicSamplingContext{
|
||||
Entries: entries,
|
||||
// If there's at least one Sentry value, we consider the DSC frozen
|
||||
Frozen: len(entries) > 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext {
|
||||
entries := map[string]string{}
|
||||
|
||||
hub := hubFromContext(span.Context())
|
||||
scope := hub.Scope()
|
||||
client := hub.Client()
|
||||
|
||||
if client == nil || scope == nil {
|
||||
return DynamicSamplingContext{
|
||||
Entries: map[string]string{},
|
||||
Frozen: false,
|
||||
}
|
||||
}
|
||||
|
||||
if traceID := span.TraceID.String(); traceID != "" {
|
||||
entries["trace_id"] = traceID
|
||||
}
|
||||
if sampleRate := span.sampleRate; sampleRate != 0 {
|
||||
entries["sample_rate"] = strconv.FormatFloat(sampleRate, 'f', -1, 64)
|
||||
}
|
||||
|
||||
if dsn := client.dsn; dsn != nil {
|
||||
if publicKey := dsn.publicKey; publicKey != "" {
|
||||
entries["public_key"] = publicKey
|
||||
}
|
||||
}
|
||||
if release := client.options.Release; release != "" {
|
||||
entries["release"] = release
|
||||
}
|
||||
if environment := client.options.Environment; environment != "" {
|
||||
entries["environment"] = environment
|
||||
}
|
||||
|
||||
// Only include the transaction name if it's of good quality (not empty and not SourceURL)
|
||||
if span.Source != "" && span.Source != SourceURL {
|
||||
if span.IsTransaction() {
|
||||
entries["transaction"] = span.Name
|
||||
}
|
||||
}
|
||||
|
||||
if userSegment := scope.user.Segment; userSegment != "" {
|
||||
entries["user_segment"] = userSegment
|
||||
}
|
||||
|
||||
if span.Sampled.Bool() {
|
||||
entries["sampled"] = "true"
|
||||
} else {
|
||||
entries["sampled"] = "false"
|
||||
}
|
||||
|
||||
return DynamicSamplingContext{
|
||||
Entries: entries,
|
||||
Frozen: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (d DynamicSamplingContext) HasEntries() bool {
|
||||
return len(d.Entries) > 0
|
||||
}
|
||||
|
||||
func (d DynamicSamplingContext) IsFrozen() bool {
|
||||
return d.Frozen
|
||||
}
|
||||
|
||||
func (d DynamicSamplingContext) String() string {
|
||||
members := []baggage.Member{}
|
||||
for k, entry := range d.Entries {
|
||||
member, err := baggage.NewMember(sentryPrefix+k, entry)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
members = append(members, member)
|
||||
}
|
||||
if len(members) > 0 {
|
||||
baggage, err := baggage.New(members...)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return baggage.String()
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
34
vendor/github.com/getsentry/sentry-go/hub.go
generated
vendored
34
vendor/github.com/getsentry/sentry-go/hub.go
generated
vendored
@@ -267,6 +267,18 @@ func (hub *Hub) CaptureException(exception error) *EventID {
|
||||
return eventID
|
||||
}
|
||||
|
||||
// CaptureCheckIn calls the method of the same name on currently bound Client instance
|
||||
// passing it a top-level Scope.
|
||||
// Returns CheckInID if the check-in was captured successfully, or nil otherwise.
|
||||
func (hub *Hub) CaptureCheckIn(checkIn *CheckIn, monitorConfig *MonitorConfig) *EventID {
|
||||
client, scope := hub.Client(), hub.Scope()
|
||||
if client == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return client.CaptureCheckIn(checkIn, monitorConfig, scope)
|
||||
}
|
||||
|
||||
// AddBreadcrumb records a new breadcrumb.
|
||||
//
|
||||
// The total number of breadcrumbs that can be recorded are limited by the
|
||||
@@ -280,31 +292,27 @@ func (hub *Hub) AddBreadcrumb(breadcrumb *Breadcrumb, hint *BreadcrumbHint) {
|
||||
return
|
||||
}
|
||||
|
||||
options := client.Options()
|
||||
max := defaultMaxBreadcrumbs
|
||||
|
||||
if options.MaxBreadcrumbs != 0 {
|
||||
max = options.MaxBreadcrumbs
|
||||
}
|
||||
|
||||
max := client.options.MaxBreadcrumbs
|
||||
if max < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if options.BeforeBreadcrumb != nil {
|
||||
h := &BreadcrumbHint{}
|
||||
if hint != nil {
|
||||
h = hint
|
||||
if client.options.BeforeBreadcrumb != nil {
|
||||
if hint == nil {
|
||||
hint = &BreadcrumbHint{}
|
||||
}
|
||||
if breadcrumb = options.BeforeBreadcrumb(breadcrumb, h); breadcrumb == nil {
|
||||
if breadcrumb = client.options.BeforeBreadcrumb(breadcrumb, hint); breadcrumb == nil {
|
||||
Logger.Println("breadcrumb dropped due to BeforeBreadcrumb callback.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if max > maxBreadcrumbs {
|
||||
if max == 0 {
|
||||
max = defaultMaxBreadcrumbs
|
||||
} else if max > maxBreadcrumbs {
|
||||
max = maxBreadcrumbs
|
||||
}
|
||||
|
||||
hub.Scope().AddBreadcrumb(breadcrumb, max)
|
||||
}
|
||||
|
||||
|
||||
121
vendor/github.com/getsentry/sentry-go/integrations.go
generated
vendored
121
vendor/github.com/getsentry/sentry-go/integrations.go
generated
vendored
@@ -2,6 +2,7 @@ package sentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
@@ -26,7 +27,7 @@ func (mi *modulesIntegration) SetupOnce(client *Client) {
|
||||
client.AddEventProcessor(mi.processor)
|
||||
}
|
||||
|
||||
func (mi *modulesIntegration) processor(event *Event, hint *EventHint) *Event {
|
||||
func (mi *modulesIntegration) processor(event *Event, _ *EventHint) *Event {
|
||||
if len(event.Modules) == 0 {
|
||||
mi.once.Do(func() {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
@@ -69,21 +70,22 @@ func (ei *environmentIntegration) SetupOnce(client *Client) {
|
||||
client.AddEventProcessor(ei.processor)
|
||||
}
|
||||
|
||||
func (ei *environmentIntegration) processor(event *Event, hint *EventHint) *Event {
|
||||
func (ei *environmentIntegration) processor(event *Event, _ *EventHint) *Event {
|
||||
// Initialize maps as necessary.
|
||||
contextNames := []string{"device", "os", "runtime"}
|
||||
if event.Contexts == nil {
|
||||
event.Contexts = make(map[string]interface{})
|
||||
event.Contexts = make(map[string]Context, len(contextNames))
|
||||
}
|
||||
for _, name := range []string{"device", "os", "runtime"} {
|
||||
for _, name := range contextNames {
|
||||
if event.Contexts[name] == nil {
|
||||
event.Contexts[name] = make(map[string]interface{})
|
||||
event.Contexts[name] = make(Context)
|
||||
}
|
||||
}
|
||||
|
||||
// Set contextual information preserving existing data. For each context, if
|
||||
// the existing value is not of type map[string]interface{}, then no
|
||||
// additional information is added.
|
||||
if deviceContext, ok := event.Contexts["device"].(map[string]interface{}); ok {
|
||||
if deviceContext, ok := event.Contexts["device"]; ok {
|
||||
if _, ok := deviceContext["arch"]; !ok {
|
||||
deviceContext["arch"] = runtime.GOARCH
|
||||
}
|
||||
@@ -91,12 +93,12 @@ func (ei *environmentIntegration) processor(event *Event, hint *EventHint) *Even
|
||||
deviceContext["num_cpu"] = runtime.NumCPU()
|
||||
}
|
||||
}
|
||||
if osContext, ok := event.Contexts["os"].(map[string]interface{}); ok {
|
||||
if osContext, ok := event.Contexts["os"]; ok {
|
||||
if _, ok := osContext["name"]; !ok {
|
||||
osContext["name"] = runtime.GOOS
|
||||
}
|
||||
}
|
||||
if runtimeContext, ok := event.Contexts["runtime"].(map[string]interface{}); ok {
|
||||
if runtimeContext, ok := event.Contexts["runtime"]; ok {
|
||||
if _, ok := runtimeContext["name"]; !ok {
|
||||
runtimeContext["name"] = "go"
|
||||
}
|
||||
@@ -129,11 +131,11 @@ func (iei *ignoreErrorsIntegration) Name() string {
|
||||
}
|
||||
|
||||
func (iei *ignoreErrorsIntegration) SetupOnce(client *Client) {
|
||||
iei.ignoreErrors = transformStringsIntoRegexps(client.Options().IgnoreErrors)
|
||||
iei.ignoreErrors = transformStringsIntoRegexps(client.options.IgnoreErrors)
|
||||
client.AddEventProcessor(iei.processor)
|
||||
}
|
||||
|
||||
func (iei *ignoreErrorsIntegration) processor(event *Event, hint *EventHint) *Event {
|
||||
func (iei *ignoreErrorsIntegration) processor(event *Event, _ *EventHint) *Event {
|
||||
suspects := getIgnoreErrorsSuspects(event)
|
||||
|
||||
for _, suspect := range suspects {
|
||||
@@ -176,6 +178,40 @@ func getIgnoreErrorsSuspects(event *Event) []string {
|
||||
return suspects
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Ignore Transactions Integration
|
||||
// ================================
|
||||
|
||||
type ignoreTransactionsIntegration struct {
|
||||
ignoreTransactions []*regexp.Regexp
|
||||
}
|
||||
|
||||
func (iei *ignoreTransactionsIntegration) Name() string {
|
||||
return "IgnoreTransactions"
|
||||
}
|
||||
|
||||
func (iei *ignoreTransactionsIntegration) SetupOnce(client *Client) {
|
||||
iei.ignoreTransactions = transformStringsIntoRegexps(client.options.IgnoreTransactions)
|
||||
client.AddEventProcessor(iei.processor)
|
||||
}
|
||||
|
||||
func (iei *ignoreTransactionsIntegration) processor(event *Event, _ *EventHint) *Event {
|
||||
suspect := event.Transaction
|
||||
if suspect == "" {
|
||||
return event
|
||||
}
|
||||
|
||||
for _, pattern := range iei.ignoreTransactions {
|
||||
if pattern.Match([]byte(suspect)) {
|
||||
Logger.Printf("Transaction dropped due to being matched by `IgnoreTransactions` option."+
|
||||
"| Value matched: %s | Filter used: %s", suspect, pattern)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Contextify Frames Integration
|
||||
// ================================
|
||||
@@ -197,7 +233,7 @@ func (cfi *contextifyFramesIntegration) SetupOnce(client *Client) {
|
||||
client.AddEventProcessor(cfi.processor)
|
||||
}
|
||||
|
||||
func (cfi *contextifyFramesIntegration) processor(event *Event, hint *EventHint) *Event {
|
||||
func (cfi *contextifyFramesIntegration) processor(event *Event, _ *EventHint) *Event {
|
||||
// Range over all exceptions
|
||||
for _, ex := range event.Exception {
|
||||
// If it has no stacktrace, just bail out
|
||||
@@ -290,3 +326,66 @@ func (cfi *contextifyFramesIntegration) addContextLinesToFrame(frame Frame, line
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
// ================================
|
||||
// Global Tags Integration
|
||||
// ================================
|
||||
|
||||
const envTagsPrefix = "SENTRY_TAGS_"
|
||||
|
||||
type globalTagsIntegration struct {
|
||||
tags map[string]string
|
||||
envTags map[string]string
|
||||
}
|
||||
|
||||
func (ti *globalTagsIntegration) Name() string {
|
||||
return "GlobalTags"
|
||||
}
|
||||
|
||||
func (ti *globalTagsIntegration) SetupOnce(client *Client) {
|
||||
ti.tags = make(map[string]string, len(client.options.Tags))
|
||||
for k, v := range client.options.Tags {
|
||||
ti.tags[k] = v
|
||||
}
|
||||
|
||||
ti.envTags = loadEnvTags()
|
||||
|
||||
client.AddEventProcessor(ti.processor)
|
||||
}
|
||||
|
||||
func (ti *globalTagsIntegration) processor(event *Event, _ *EventHint) *Event {
|
||||
if len(ti.tags) == 0 && len(ti.envTags) == 0 {
|
||||
return event
|
||||
}
|
||||
|
||||
if event.Tags == nil {
|
||||
event.Tags = make(map[string]string, len(ti.tags)+len(ti.envTags))
|
||||
}
|
||||
|
||||
for k, v := range ti.tags {
|
||||
if _, ok := event.Tags[k]; !ok {
|
||||
event.Tags[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range ti.envTags {
|
||||
if _, ok := event.Tags[k]; !ok {
|
||||
event.Tags[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
func loadEnvTags() map[string]string {
|
||||
tags := map[string]string{}
|
||||
for _, pair := range os.Environ() {
|
||||
parts := strings.Split(pair, "=")
|
||||
if !strings.HasPrefix(parts[0], envTagsPrefix) {
|
||||
continue
|
||||
}
|
||||
tag := strings.TrimPrefix(parts[0], envTagsPrefix)
|
||||
tags[tag] = parts[1]
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
265
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
265
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
@@ -6,16 +6,24 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Protocol Docs (kinda)
|
||||
// https://github.com/getsentry/rust-sentry-types/blob/master/src/protocol/v7.rs
|
||||
// eventType is the type of an error event.
|
||||
const eventType = "event"
|
||||
|
||||
// transactionType is the type of a transaction event.
|
||||
const transactionType = "transaction"
|
||||
|
||||
// profileType is the type of a profile event.
|
||||
// currently, profiles are always sent as part of a transaction event.
|
||||
const profileType = "profile"
|
||||
|
||||
// checkInType is the type of a check in event.
|
||||
const checkInType = "check_in"
|
||||
|
||||
// Level marks the severity of the event.
|
||||
type Level string
|
||||
|
||||
@@ -28,6 +36,15 @@ const (
|
||||
LevelFatal Level = "fatal"
|
||||
)
|
||||
|
||||
func getSensitiveHeaders() map[string]bool {
|
||||
return map[string]bool{
|
||||
"Authorization": true,
|
||||
"Cookie": true,
|
||||
"X-Forwarded-For": true,
|
||||
"X-Real-Ip": true,
|
||||
}
|
||||
}
|
||||
|
||||
// SdkInfo contains all metadata about about the SDK being used.
|
||||
type SdkInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
@@ -90,13 +107,56 @@ func (b *Breadcrumb) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal((*breadcrumb)(b))
|
||||
}
|
||||
|
||||
// Attachment allows associating files with your events to aid in investigation.
|
||||
// An event may contain one or more attachments.
|
||||
type Attachment struct {
|
||||
Filename string
|
||||
ContentType string
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
// User describes the user associated with an Event. If this is used, at least
|
||||
// an ID or an IP address should be provided.
|
||||
type User struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
IPAddress string `json:"ip_address,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
IPAddress string `json:"ip_address,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Segment string `json:"segment,omitempty"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func (u User) IsEmpty() bool {
|
||||
if len(u.ID) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(u.Email) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(u.IPAddress) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(u.Username) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(u.Name) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(u.Segment) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(u.Data) > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Request contains information on a HTTP request related to the event.
|
||||
@@ -121,23 +181,35 @@ func NewRequest(r *http.Request) *Request {
|
||||
}
|
||||
url := fmt.Sprintf("%s://%s%s", protocol, r.Host, r.URL.Path)
|
||||
|
||||
// We read only the first Cookie header because of the specification:
|
||||
// https://tools.ietf.org/html/rfc6265#section-5.4
|
||||
// When the user agent generates an HTTP request, the user agent MUST NOT
|
||||
// attach more than one Cookie header field.
|
||||
cookies := r.Header.Get("Cookie")
|
||||
|
||||
headers := make(map[string]string, len(r.Header))
|
||||
for k, v := range r.Header {
|
||||
headers[k] = strings.Join(v, ",")
|
||||
}
|
||||
headers["Host"] = r.Host
|
||||
|
||||
var cookies string
|
||||
var env map[string]string
|
||||
if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
|
||||
headers := map[string]string{}
|
||||
|
||||
if client := CurrentHub().Client(); client != nil && client.options.SendDefaultPII {
|
||||
// We read only the first Cookie header because of the specification:
|
||||
// https://tools.ietf.org/html/rfc6265#section-5.4
|
||||
// When the user agent generates an HTTP request, the user agent MUST NOT
|
||||
// attach more than one Cookie header field.
|
||||
cookies = r.Header.Get("Cookie")
|
||||
|
||||
for k, v := range r.Header {
|
||||
headers[k] = strings.Join(v, ",")
|
||||
}
|
||||
|
||||
if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||
env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
|
||||
}
|
||||
} else {
|
||||
sensitiveHeaders := getSensitiveHeaders()
|
||||
for k, v := range r.Header {
|
||||
if _, ok := sensitiveHeaders[k]; !ok {
|
||||
headers[k] = strings.Join(v, ",")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
headers["Host"] = r.Host
|
||||
|
||||
return &Request{
|
||||
URL: url,
|
||||
Method: r.Method,
|
||||
@@ -148,23 +220,82 @@ func NewRequest(r *http.Request) *Request {
|
||||
}
|
||||
}
|
||||
|
||||
// Mechanism is the mechanism by which an exception was generated and handled.
|
||||
type Mechanism struct {
|
||||
Type string `json:"type,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
HelpLink string `json:"help_link,omitempty"`
|
||||
Handled *bool `json:"handled,omitempty"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// SetUnhandled indicates that the exception is an unhandled exception, i.e.
|
||||
// from a panic.
|
||||
func (m *Mechanism) SetUnhandled() {
|
||||
h := false
|
||||
m.Handled = &h
|
||||
}
|
||||
|
||||
// Exception specifies an error that occurred.
|
||||
type Exception struct {
|
||||
Type string `json:"type,omitempty"` // used as the main issue title
|
||||
Value string `json:"value,omitempty"` // used as the main issue subtitle
|
||||
Module string `json:"module,omitempty"`
|
||||
ThreadID string `json:"thread_id,omitempty"`
|
||||
ThreadID uint64 `json:"thread_id,omitempty"`
|
||||
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
|
||||
Mechanism *Mechanism `json:"mechanism,omitempty"`
|
||||
}
|
||||
|
||||
// SDKMetaData is a struct to stash data which is needed at some point in the SDK's event processing pipeline
|
||||
// but which shouldn't get send to Sentry.
|
||||
type SDKMetaData struct {
|
||||
dsc DynamicSamplingContext
|
||||
transactionProfile *profileInfo
|
||||
}
|
||||
|
||||
// Contains information about how the name of the transaction was determined.
|
||||
type TransactionInfo struct {
|
||||
Source TransactionSource `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
// The DebugMeta interface is not used in Golang apps, but may be populated
|
||||
// when proxying Events from other platforms, like iOS, Android, and the
|
||||
// Web. (See: https://develop.sentry.dev/sdk/event-payloads/debugmeta/ ).
|
||||
type DebugMeta struct {
|
||||
SdkInfo *DebugMetaSdkInfo `json:"sdk_info,omitempty"`
|
||||
Images []DebugMetaImage `json:"images,omitempty"`
|
||||
}
|
||||
|
||||
type DebugMetaSdkInfo struct {
|
||||
SdkName string `json:"sdk_name,omitempty"`
|
||||
VersionMajor int `json:"version_major,omitempty"`
|
||||
VersionMinor int `json:"version_minor,omitempty"`
|
||||
VersionPatchlevel int `json:"version_patchlevel,omitempty"`
|
||||
}
|
||||
|
||||
type DebugMetaImage struct {
|
||||
Type string `json:"type,omitempty"` // all
|
||||
ImageAddr string `json:"image_addr,omitempty"` // macho,elf,pe
|
||||
ImageSize int `json:"image_size,omitempty"` // macho,elf,pe
|
||||
DebugID string `json:"debug_id,omitempty"` // macho,elf,pe,wasm,sourcemap
|
||||
DebugFile string `json:"debug_file,omitempty"` // macho,elf,pe,wasm
|
||||
CodeID string `json:"code_id,omitempty"` // macho,elf,pe,wasm
|
||||
CodeFile string `json:"code_file,omitempty"` // macho,elf,pe,wasm,sourcemap
|
||||
ImageVmaddr string `json:"image_vmaddr,omitempty"` // macho,elf,pe
|
||||
Arch string `json:"arch,omitempty"` // macho,elf,pe
|
||||
UUID string `json:"uuid,omitempty"` // proguard
|
||||
}
|
||||
|
||||
// EventID is a hexadecimal string representing a unique uuid4 for an Event.
|
||||
// An EventID must be 32 characters long, lowercase and not have any dashes.
|
||||
type EventID string
|
||||
|
||||
type Context = map[string]interface{}
|
||||
|
||||
// Event is the fundamental data structure that is sent to Sentry.
|
||||
type Event struct {
|
||||
Breadcrumbs []*Breadcrumb `json:"breadcrumbs,omitempty"`
|
||||
Contexts map[string]interface{} `json:"contexts,omitempty"`
|
||||
Contexts map[string]Context `json:"contexts,omitempty"`
|
||||
Dist string `json:"dist,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
EventID EventID `json:"event_id,omitempty"`
|
||||
@@ -185,12 +316,62 @@ type Event struct {
|
||||
Modules map[string]string `json:"modules,omitempty"`
|
||||
Request *Request `json:"request,omitempty"`
|
||||
Exception []Exception `json:"exception,omitempty"`
|
||||
DebugMeta *DebugMeta `json:"debug_meta,omitempty"`
|
||||
Attachments []*Attachment `json:"-"`
|
||||
|
||||
// The fields below are only relevant for transactions.
|
||||
|
||||
Type string `json:"type,omitempty"`
|
||||
StartTime time.Time `json:"start_timestamp"`
|
||||
Spans []*Span `json:"spans,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
StartTime time.Time `json:"start_timestamp"`
|
||||
Spans []*Span `json:"spans,omitempty"`
|
||||
TransactionInfo *TransactionInfo `json:"transaction_info,omitempty"`
|
||||
|
||||
// The fields below are only relevant for crons/check ins
|
||||
|
||||
CheckIn *CheckIn `json:"check_in,omitempty"`
|
||||
MonitorConfig *MonitorConfig `json:"monitor_config,omitempty"`
|
||||
|
||||
// The fields below are not part of the final JSON payload.
|
||||
|
||||
sdkMetaData SDKMetaData
|
||||
}
|
||||
|
||||
// SetException appends the unwrapped errors to the event's exception list.
|
||||
//
|
||||
// maxErrorDepth is the maximum depth of the error chain we will look
|
||||
// into while unwrapping the errors.
|
||||
func (e *Event) SetException(exception error, maxErrorDepth int) {
|
||||
err := exception
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < maxErrorDepth && err != nil; i++ {
|
||||
e.Exception = append(e.Exception, Exception{
|
||||
Value: err.Error(),
|
||||
Type: reflect.TypeOf(err).String(),
|
||||
Stacktrace: ExtractStacktrace(err),
|
||||
})
|
||||
switch previous := err.(type) {
|
||||
case interface{ Unwrap() error }:
|
||||
err = previous.Unwrap()
|
||||
case interface{ Cause() error }:
|
||||
err = previous.Cause()
|
||||
default:
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add a trace of the current stack to the most recent error in a chain if
|
||||
// it doesn't have a stack trace yet.
|
||||
// We only add to the most recent error to avoid duplication and because the
|
||||
// current stack is most likely unrelated to errors deeper in the chain.
|
||||
if e.Exception[0].Stacktrace == nil {
|
||||
e.Exception[0].Stacktrace = NewStacktrace()
|
||||
}
|
||||
|
||||
// event.Exception should be sorted such that the most recent error is last.
|
||||
reverse(e.Exception)
|
||||
}
|
||||
|
||||
// TODO: Event.Contexts map[string]interface{} => map[string]EventContext,
|
||||
@@ -210,6 +391,8 @@ func (e *Event) MarshalJSON() ([]byte, error) {
|
||||
// and a few type tricks.
|
||||
if e.Type == transactionType {
|
||||
return e.transactionMarshalJSON()
|
||||
} else if e.Type == checkInType {
|
||||
return e.checkInMarshalJSON()
|
||||
}
|
||||
return e.defaultMarshalJSON()
|
||||
}
|
||||
@@ -232,9 +415,10 @@ func (e *Event) defaultMarshalJSON() ([]byte, error) {
|
||||
// be sent for transactions. They shadow the respective fields in Event
|
||||
// and are meant to remain nil, triggering the omitempty behavior.
|
||||
|
||||
Type json.RawMessage `json:"type,omitempty"`
|
||||
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
|
||||
Spans json.RawMessage `json:"spans,omitempty"`
|
||||
Type json.RawMessage `json:"type,omitempty"`
|
||||
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
|
||||
Spans json.RawMessage `json:"spans,omitempty"`
|
||||
TransactionInfo json.RawMessage `json:"transaction_info,omitempty"`
|
||||
}
|
||||
|
||||
x := errorEvent{event: (*event)(e)}
|
||||
@@ -283,10 +467,33 @@ func (e *Event) transactionMarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(x)
|
||||
}
|
||||
|
||||
func (e *Event) checkInMarshalJSON() ([]byte, error) {
|
||||
checkIn := serializedCheckIn{
|
||||
CheckInID: string(e.CheckIn.ID),
|
||||
MonitorSlug: e.CheckIn.MonitorSlug,
|
||||
Status: e.CheckIn.Status,
|
||||
Duration: e.CheckIn.Duration.Seconds(),
|
||||
Release: e.Release,
|
||||
Environment: e.Environment,
|
||||
MonitorConfig: nil,
|
||||
}
|
||||
|
||||
if e.MonitorConfig != nil {
|
||||
checkIn.MonitorConfig = &MonitorConfig{
|
||||
Schedule: e.MonitorConfig.Schedule,
|
||||
CheckInMargin: e.MonitorConfig.CheckInMargin,
|
||||
MaxRuntime: e.MonitorConfig.MaxRuntime,
|
||||
Timezone: e.MonitorConfig.Timezone,
|
||||
}
|
||||
}
|
||||
|
||||
return json.Marshal(checkIn)
|
||||
}
|
||||
|
||||
// NewEvent creates a new Event.
|
||||
func NewEvent() *Event {
|
||||
event := Event{
|
||||
Contexts: make(map[string]interface{}),
|
||||
Contexts: make(map[string]Context),
|
||||
Extra: make(map[string]interface{}),
|
||||
Tags: make(map[string]string),
|
||||
Modules: make(map[string]string),
|
||||
|
||||
23
vendor/github.com/getsentry/sentry-go/internal/crypto/randutil/randutil.go
generated
vendored
23
vendor/github.com/getsentry/sentry-go/internal/crypto/randutil/randutil.go
generated
vendored
@@ -1,23 +0,0 @@
|
||||
package randutil
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const (
|
||||
floatMax = 1 << 53
|
||||
floatMask = floatMax - 1
|
||||
)
|
||||
|
||||
// Float64 returns a cryptographically secure random number in [0.0, 1.0).
|
||||
func Float64() float64 {
|
||||
// The implementation is, in essence:
|
||||
// return float64(rand.Int63n(1<<53)) / (1<<53)
|
||||
b := make([]byte, 8)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return float64(binary.LittleEndian.Uint64(b)&floatMask) / floatMax
|
||||
}
|
||||
12
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/README.md
generated
vendored
Normal file
12
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/README.md
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
## Why do we have this "otel/baggage" folder?
|
||||
|
||||
The root sentry-go SDK (namely, the Dynamic Sampling functionality) needs an implementation of the [baggage spec](https://www.w3.org/TR/baggage/).
|
||||
For that reason, we've taken the existing baggage implementation from the [opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go/) repository, and fixed a few things that in our opinion were violating the specification.
|
||||
|
||||
These issues are:
|
||||
1. Baggage string value `one%20two` should be properly parsed as "one two"
|
||||
1. Baggage string value `one+two` should be parsed as "one+two"
|
||||
1. Go string value "one two" should be encoded as `one%20two` (percent encoding), and NOT as `one+two` (URL query encoding).
|
||||
1. Go string value "1=1" might be encoded as `1=1`, because the spec says: "Note, value MAY contain any number of the equal sign (=) characters. Parsers MUST NOT assume that the equal sign is only used to separate key and value.". `1%3D1` is also valid, but to simplify the implementation we're not doing it.
|
||||
|
||||
Changes were made in this PR: https://github.com/getsentry/sentry-go/pull/568
|
||||
604
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/baggage.go
generated
vendored
Normal file
604
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/baggage.go
generated
vendored
Normal file
@@ -0,0 +1,604 @@
|
||||
// Adapted from https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/baggage/baggage.go
|
||||
//
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package baggage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage"
|
||||
)
|
||||
|
||||
const (
|
||||
maxMembers = 180
|
||||
maxBytesPerMembers = 4096
|
||||
maxBytesPerBaggageString = 8192
|
||||
|
||||
listDelimiter = ","
|
||||
keyValueDelimiter = "="
|
||||
propertyDelimiter = ";"
|
||||
|
||||
keyDef = `([\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5a\x5e-\x7a\x7c\x7e]+)`
|
||||
valueDef = `([\x21\x23-\x2b\x2d-\x3a\x3c-\x5B\x5D-\x7e]*)`
|
||||
keyValueDef = `\s*` + keyDef + `\s*` + keyValueDelimiter + `\s*` + valueDef + `\s*`
|
||||
)
|
||||
|
||||
var (
|
||||
keyRe = regexp.MustCompile(`^` + keyDef + `$`)
|
||||
valueRe = regexp.MustCompile(`^` + valueDef + `$`)
|
||||
propertyRe = regexp.MustCompile(`^(?:\s*` + keyDef + `\s*|` + keyValueDef + `)$`)
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidKey = errors.New("invalid key")
|
||||
errInvalidValue = errors.New("invalid value")
|
||||
errInvalidProperty = errors.New("invalid baggage list-member property")
|
||||
errInvalidMember = errors.New("invalid baggage list-member")
|
||||
errMemberNumber = errors.New("too many list-members in baggage-string")
|
||||
errMemberBytes = errors.New("list-member too large")
|
||||
errBaggageBytes = errors.New("baggage-string too large")
|
||||
)
|
||||
|
||||
// Property is an additional metadata entry for a baggage list-member.
|
||||
type Property struct {
|
||||
key, value string
|
||||
|
||||
// hasValue indicates if a zero-value value means the property does not
|
||||
// have a value or if it was the zero-value.
|
||||
hasValue bool
|
||||
|
||||
// hasData indicates whether the created property contains data or not.
|
||||
// Properties that do not contain data are invalid with no other check
|
||||
// required.
|
||||
hasData bool
|
||||
}
|
||||
|
||||
// NewKeyProperty returns a new Property for key.
|
||||
//
|
||||
// If key is invalid, an error will be returned.
|
||||
func NewKeyProperty(key string) (Property, error) {
|
||||
if !keyRe.MatchString(key) {
|
||||
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
||||
}
|
||||
|
||||
p := Property{key: key, hasData: true}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// NewKeyValueProperty returns a new Property for key with value.
|
||||
//
|
||||
// If key or value are invalid, an error will be returned.
|
||||
func NewKeyValueProperty(key, value string) (Property, error) {
|
||||
if !keyRe.MatchString(key) {
|
||||
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
||||
}
|
||||
if !valueRe.MatchString(value) {
|
||||
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
||||
}
|
||||
|
||||
p := Property{
|
||||
key: key,
|
||||
value: value,
|
||||
hasValue: true,
|
||||
hasData: true,
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func newInvalidProperty() Property {
|
||||
return Property{}
|
||||
}
|
||||
|
||||
// parseProperty attempts to decode a Property from the passed string. It
|
||||
// returns an error if the input is invalid according to the W3C Baggage
|
||||
// specification.
|
||||
func parseProperty(property string) (Property, error) {
|
||||
if property == "" {
|
||||
return newInvalidProperty(), nil
|
||||
}
|
||||
|
||||
match := propertyRe.FindStringSubmatch(property)
|
||||
if len(match) != 4 {
|
||||
return newInvalidProperty(), fmt.Errorf("%w: %q", errInvalidProperty, property)
|
||||
}
|
||||
|
||||
p := Property{hasData: true}
|
||||
if match[1] != "" {
|
||||
p.key = match[1]
|
||||
} else {
|
||||
p.key = match[2]
|
||||
p.value = match[3]
|
||||
p.hasValue = true
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// validate ensures p conforms to the W3C Baggage specification, returning an
|
||||
// error otherwise.
|
||||
func (p Property) validate() error {
|
||||
errFunc := func(err error) error {
|
||||
return fmt.Errorf("invalid property: %w", err)
|
||||
}
|
||||
|
||||
if !p.hasData {
|
||||
return errFunc(fmt.Errorf("%w: %q", errInvalidProperty, p))
|
||||
}
|
||||
|
||||
if !keyRe.MatchString(p.key) {
|
||||
return errFunc(fmt.Errorf("%w: %q", errInvalidKey, p.key))
|
||||
}
|
||||
if p.hasValue && !valueRe.MatchString(p.value) {
|
||||
return errFunc(fmt.Errorf("%w: %q", errInvalidValue, p.value))
|
||||
}
|
||||
if !p.hasValue && p.value != "" {
|
||||
return errFunc(errors.New("inconsistent value"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Key returns the Property key.
|
||||
func (p Property) Key() string {
|
||||
return p.key
|
||||
}
|
||||
|
||||
// Value returns the Property value. Additionally, a boolean value is returned
|
||||
// indicating if the returned value is the empty if the Property has a value
|
||||
// that is empty or if the value is not set.
|
||||
func (p Property) Value() (string, bool) {
|
||||
return p.value, p.hasValue
|
||||
}
|
||||
|
||||
// String encodes Property into a string compliant with the W3C Baggage
|
||||
// specification.
|
||||
func (p Property) String() string {
|
||||
if p.hasValue {
|
||||
return fmt.Sprintf("%s%s%v", p.key, keyValueDelimiter, p.value)
|
||||
}
|
||||
return p.key
|
||||
}
|
||||
|
||||
type properties []Property
|
||||
|
||||
func fromInternalProperties(iProps []baggage.Property) properties {
|
||||
if len(iProps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
props := make(properties, len(iProps))
|
||||
for i, p := range iProps {
|
||||
props[i] = Property{
|
||||
key: p.Key,
|
||||
value: p.Value,
|
||||
hasValue: p.HasValue,
|
||||
}
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
func (p properties) asInternal() []baggage.Property {
|
||||
if len(p) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
iProps := make([]baggage.Property, len(p))
|
||||
for i, prop := range p {
|
||||
iProps[i] = baggage.Property{
|
||||
Key: prop.key,
|
||||
Value: prop.value,
|
||||
HasValue: prop.hasValue,
|
||||
}
|
||||
}
|
||||
return iProps
|
||||
}
|
||||
|
||||
func (p properties) Copy() properties {
|
||||
if len(p) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
props := make(properties, len(p))
|
||||
copy(props, p)
|
||||
return props
|
||||
}
|
||||
|
||||
// validate ensures each Property in p conforms to the W3C Baggage
|
||||
// specification, returning an error otherwise.
|
||||
func (p properties) validate() error {
|
||||
for _, prop := range p {
|
||||
if err := prop.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// String encodes properties into a string compliant with the W3C Baggage
|
||||
// specification.
|
||||
func (p properties) String() string {
|
||||
props := make([]string, len(p))
|
||||
for i, prop := range p {
|
||||
props[i] = prop.String()
|
||||
}
|
||||
return strings.Join(props, propertyDelimiter)
|
||||
}
|
||||
|
||||
// Member is a list-member of a baggage-string as defined by the W3C Baggage
|
||||
// specification.
|
||||
type Member struct {
|
||||
key, value string
|
||||
properties properties
|
||||
|
||||
// hasData indicates whether the created property contains data or not.
|
||||
// Properties that do not contain data are invalid with no other check
|
||||
// required.
|
||||
hasData bool
|
||||
}
|
||||
|
||||
// NewMember returns a new Member from the passed arguments. The key will be
|
||||
// used directly while the value will be url decoded after validation. An error
|
||||
// is returned if the created Member would be invalid according to the W3C
|
||||
// Baggage specification.
|
||||
func NewMember(key, value string, props ...Property) (Member, error) {
|
||||
m := Member{
|
||||
key: key,
|
||||
value: value,
|
||||
properties: properties(props).Copy(),
|
||||
hasData: true,
|
||||
}
|
||||
if err := m.validate(); err != nil {
|
||||
return newInvalidMember(), err
|
||||
}
|
||||
//// NOTE(anton): I don't think we need to unescape here
|
||||
// decodedValue, err := url.PathUnescape(value)
|
||||
// if err != nil {
|
||||
// return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
||||
// }
|
||||
// m.value = decodedValue
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func newInvalidMember() Member {
|
||||
return Member{}
|
||||
}
|
||||
|
||||
// parseMember attempts to decode a Member from the passed string. It returns
|
||||
// an error if the input is invalid according to the W3C Baggage
|
||||
// specification.
|
||||
func parseMember(member string) (Member, error) {
|
||||
if n := len(member); n > maxBytesPerMembers {
|
||||
return newInvalidMember(), fmt.Errorf("%w: %d", errMemberBytes, n)
|
||||
}
|
||||
|
||||
var (
|
||||
key, value string
|
||||
props properties
|
||||
)
|
||||
|
||||
parts := strings.SplitN(member, propertyDelimiter, 2)
|
||||
switch len(parts) {
|
||||
case 2:
|
||||
// Parse the member properties.
|
||||
for _, pStr := range strings.Split(parts[1], propertyDelimiter) {
|
||||
p, err := parseProperty(pStr)
|
||||
if err != nil {
|
||||
return newInvalidMember(), err
|
||||
}
|
||||
props = append(props, p)
|
||||
}
|
||||
fallthrough
|
||||
case 1:
|
||||
// Parse the member key/value pair.
|
||||
|
||||
// Take into account a value can contain equal signs (=).
|
||||
kv := strings.SplitN(parts[0], keyValueDelimiter, 2)
|
||||
if len(kv) != 2 {
|
||||
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidMember, member)
|
||||
}
|
||||
// "Leading and trailing whitespaces are allowed but MUST be trimmed
|
||||
// when converting the header into a data structure."
|
||||
key = strings.TrimSpace(kv[0])
|
||||
value = strings.TrimSpace(kv[1])
|
||||
var err error
|
||||
if !keyRe.MatchString(key) {
|
||||
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidKey, key)
|
||||
}
|
||||
if !valueRe.MatchString(value) {
|
||||
return newInvalidMember(), fmt.Errorf("%w: %q", errInvalidValue, value)
|
||||
}
|
||||
decodedValue, err := url.PathUnescape(value)
|
||||
if err != nil {
|
||||
return newInvalidMember(), fmt.Errorf("%w: %q", err, value)
|
||||
}
|
||||
value = decodedValue
|
||||
default:
|
||||
// This should never happen unless a developer has changed the string
|
||||
// splitting somehow. Panic instead of failing silently and allowing
|
||||
// the bug to slip past the CI checks.
|
||||
panic("failed to parse baggage member")
|
||||
}
|
||||
|
||||
return Member{key: key, value: value, properties: props, hasData: true}, nil
|
||||
}
|
||||
|
||||
// validate ensures m conforms to the W3C Baggage specification.
|
||||
// A key is just an ASCII string, but a value must be URL encoded UTF-8,
|
||||
// returning an error otherwise.
|
||||
func (m Member) validate() error {
|
||||
if !m.hasData {
|
||||
return fmt.Errorf("%w: %q", errInvalidMember, m)
|
||||
}
|
||||
|
||||
if !keyRe.MatchString(m.key) {
|
||||
return fmt.Errorf("%w: %q", errInvalidKey, m.key)
|
||||
}
|
||||
//// NOTE(anton): IMO it's too early to validate the value here.
|
||||
// if !valueRe.MatchString(m.value) {
|
||||
// return fmt.Errorf("%w: %q", errInvalidValue, m.value)
|
||||
// }
|
||||
return m.properties.validate()
|
||||
}
|
||||
|
||||
// Key returns the Member key.
|
||||
func (m Member) Key() string { return m.key }
|
||||
|
||||
// Value returns the Member value.
|
||||
func (m Member) Value() string { return m.value }
|
||||
|
||||
// Properties returns a copy of the Member properties.
|
||||
func (m Member) Properties() []Property { return m.properties.Copy() }
|
||||
|
||||
// String encodes Member into a string compliant with the W3C Baggage
|
||||
// specification.
|
||||
func (m Member) String() string {
|
||||
// A key is just an ASCII string, but a value is URL encoded UTF-8.
|
||||
s := fmt.Sprintf("%s%s%s", m.key, keyValueDelimiter, percentEncodeValue(m.value))
|
||||
if len(m.properties) > 0 {
|
||||
s = fmt.Sprintf("%s%s%s", s, propertyDelimiter, m.properties.String())
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// percentEncodeValue encodes the baggage value, using percent-encoding for
|
||||
// disallowed octets.
|
||||
func percentEncodeValue(s string) string {
|
||||
const upperhex = "0123456789ABCDEF"
|
||||
var sb strings.Builder
|
||||
|
||||
for byteIndex, width := 0, 0; byteIndex < len(s); byteIndex += width {
|
||||
runeValue, w := utf8.DecodeRuneInString(s[byteIndex:])
|
||||
width = w
|
||||
char := string(runeValue)
|
||||
if valueRe.MatchString(char) && char != "%" {
|
||||
// The character is returned as is, no need to percent-encode
|
||||
sb.WriteString(char)
|
||||
} else {
|
||||
// We need to percent-encode each byte of the multi-octet character
|
||||
for j := 0; j < width; j++ {
|
||||
b := s[byteIndex+j]
|
||||
sb.WriteByte('%')
|
||||
// Bitwise operations are inspired by "net/url"
|
||||
sb.WriteByte(upperhex[b>>4])
|
||||
sb.WriteByte(upperhex[b&15])
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Baggage is a list of baggage members representing the baggage-string as
|
||||
// defined by the W3C Baggage specification.
|
||||
type Baggage struct { //nolint:golint
|
||||
list baggage.List
|
||||
}
|
||||
|
||||
// New returns a new valid Baggage. It returns an error if it results in a
|
||||
// Baggage exceeding limits set in that specification.
|
||||
//
|
||||
// It expects all the provided members to have already been validated.
|
||||
func New(members ...Member) (Baggage, error) {
|
||||
if len(members) == 0 {
|
||||
return Baggage{}, nil
|
||||
}
|
||||
|
||||
b := make(baggage.List)
|
||||
for _, m := range members {
|
||||
if !m.hasData {
|
||||
return Baggage{}, errInvalidMember
|
||||
}
|
||||
|
||||
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||
b[m.key] = baggage.Item{
|
||||
Value: m.value,
|
||||
Properties: m.properties.asInternal(),
|
||||
}
|
||||
}
|
||||
|
||||
// Check member numbers after deduplication.
|
||||
if len(b) > maxMembers {
|
||||
return Baggage{}, errMemberNumber
|
||||
}
|
||||
|
||||
bag := Baggage{b}
|
||||
if n := len(bag.String()); n > maxBytesPerBaggageString {
|
||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||
}
|
||||
|
||||
return bag, nil
|
||||
}
|
||||
|
||||
// Parse attempts to decode a baggage-string from the passed string. It
|
||||
// returns an error if the input is invalid according to the W3C Baggage
|
||||
// specification.
|
||||
//
|
||||
// If there are duplicate list-members contained in baggage, the last one
|
||||
// defined (reading left-to-right) will be the only one kept. This diverges
|
||||
// from the W3C Baggage specification which allows duplicate list-members, but
|
||||
// conforms to the OpenTelemetry Baggage specification.
|
||||
func Parse(bStr string) (Baggage, error) {
|
||||
if bStr == "" {
|
||||
return Baggage{}, nil
|
||||
}
|
||||
|
||||
if n := len(bStr); n > maxBytesPerBaggageString {
|
||||
return Baggage{}, fmt.Errorf("%w: %d", errBaggageBytes, n)
|
||||
}
|
||||
|
||||
b := make(baggage.List)
|
||||
for _, memberStr := range strings.Split(bStr, listDelimiter) {
|
||||
m, err := parseMember(memberStr)
|
||||
if err != nil {
|
||||
return Baggage{}, err
|
||||
}
|
||||
// OpenTelemetry resolves duplicates by last-one-wins.
|
||||
b[m.key] = baggage.Item{
|
||||
Value: m.value,
|
||||
Properties: m.properties.asInternal(),
|
||||
}
|
||||
}
|
||||
|
||||
// OpenTelemetry does not allow for duplicate list-members, but the W3C
|
||||
// specification does. Now that we have deduplicated, ensure the baggage
|
||||
// does not exceed list-member limits.
|
||||
if len(b) > maxMembers {
|
||||
return Baggage{}, errMemberNumber
|
||||
}
|
||||
|
||||
return Baggage{b}, nil
|
||||
}
|
||||
|
||||
// Member returns the baggage list-member identified by key.
|
||||
//
|
||||
// If there is no list-member matching the passed key the returned Member will
|
||||
// be a zero-value Member.
|
||||
// The returned member is not validated, as we assume the validation happened
|
||||
// when it was added to the Baggage.
|
||||
func (b Baggage) Member(key string) Member {
|
||||
v, ok := b.list[key]
|
||||
if !ok {
|
||||
// We do not need to worry about distinguishing between the situation
|
||||
// where a zero-valued Member is included in the Baggage because a
|
||||
// zero-valued Member is invalid according to the W3C Baggage
|
||||
// specification (it has an empty key).
|
||||
return newInvalidMember()
|
||||
}
|
||||
|
||||
return Member{
|
||||
key: key,
|
||||
value: v.Value,
|
||||
properties: fromInternalProperties(v.Properties),
|
||||
hasData: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Members returns all the baggage list-members.
|
||||
// The order of the returned list-members does not have significance.
|
||||
//
|
||||
// The returned members are not validated, as we assume the validation happened
|
||||
// when they were added to the Baggage.
|
||||
func (b Baggage) Members() []Member {
|
||||
if len(b.list) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
members := make([]Member, 0, len(b.list))
|
||||
for k, v := range b.list {
|
||||
members = append(members, Member{
|
||||
key: k,
|
||||
value: v.Value,
|
||||
properties: fromInternalProperties(v.Properties),
|
||||
hasData: true,
|
||||
})
|
||||
}
|
||||
return members
|
||||
}
|
||||
|
||||
// SetMember returns a copy the Baggage with the member included. If the
|
||||
// baggage contains a Member with the same key the existing Member is
|
||||
// replaced.
|
||||
//
|
||||
// If member is invalid according to the W3C Baggage specification, an error
|
||||
// is returned with the original Baggage.
|
||||
func (b Baggage) SetMember(member Member) (Baggage, error) {
|
||||
if !member.hasData {
|
||||
return b, errInvalidMember
|
||||
}
|
||||
|
||||
n := len(b.list)
|
||||
if _, ok := b.list[member.key]; !ok {
|
||||
n++
|
||||
}
|
||||
list := make(baggage.List, n)
|
||||
|
||||
for k, v := range b.list {
|
||||
// Do not copy if we are just going to overwrite.
|
||||
if k == member.key {
|
||||
continue
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
|
||||
list[member.key] = baggage.Item{
|
||||
Value: member.value,
|
||||
Properties: member.properties.asInternal(),
|
||||
}
|
||||
|
||||
return Baggage{list: list}, nil
|
||||
}
|
||||
|
||||
// DeleteMember returns a copy of the Baggage with the list-member identified
|
||||
// by key removed.
|
||||
func (b Baggage) DeleteMember(key string) Baggage {
|
||||
n := len(b.list)
|
||||
if _, ok := b.list[key]; ok {
|
||||
n--
|
||||
}
|
||||
list := make(baggage.List, n)
|
||||
|
||||
for k, v := range b.list {
|
||||
if k == key {
|
||||
continue
|
||||
}
|
||||
list[k] = v
|
||||
}
|
||||
|
||||
return Baggage{list: list}
|
||||
}
|
||||
|
||||
// Len returns the number of list-members in the Baggage.
|
||||
func (b Baggage) Len() int {
|
||||
return len(b.list)
|
||||
}
|
||||
|
||||
// String encodes Baggage into a string compliant with the W3C Baggage
|
||||
// specification. The returned string will be invalid if the Baggage contains
|
||||
// any invalid list-members.
|
||||
func (b Baggage) String() string {
|
||||
members := make([]string, 0, len(b.list))
|
||||
for k, v := range b.list {
|
||||
members = append(members, Member{
|
||||
key: k,
|
||||
value: v.Value,
|
||||
properties: fromInternalProperties(v.Properties),
|
||||
}.String())
|
||||
}
|
||||
return strings.Join(members, listDelimiter)
|
||||
}
|
||||
45
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage/baggage.go
generated
vendored
Normal file
45
vendor/github.com/getsentry/sentry-go/internal/otel/baggage/internal/baggage/baggage.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// Adapted from https://github.com/open-telemetry/opentelemetry-go/blob/c21b6b6bb31a2f74edd06e262f1690f3f6ea3d5c/internal/baggage/baggage.go
|
||||
//
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package baggage provides base types and functionality to store and retrieve
|
||||
baggage in Go context. This package exists because the OpenTracing bridge to
|
||||
OpenTelemetry needs to synchronize state whenever baggage for a context is
|
||||
modified and that context contains an OpenTracing span. If it were not for
|
||||
this need this package would not need to exist and the
|
||||
`go.opentelemetry.io/otel/baggage` package would be the singular place where
|
||||
W3C baggage is handled.
|
||||
*/
|
||||
package baggage
|
||||
|
||||
// List is the collection of baggage members. The W3C allows for duplicates,
|
||||
// but OpenTelemetry does not, therefore, this is represented as a map.
|
||||
type List map[string]Item
|
||||
|
||||
// Item is the value and metadata properties part of a list-member.
|
||||
type Item struct {
|
||||
Value string
|
||||
Properties []Property
|
||||
}
|
||||
|
||||
// Property is a metadata entry for a list-member.
|
||||
type Property struct {
|
||||
Key, Value string
|
||||
|
||||
// HasValue indicates if a zero-value value means the property does not
|
||||
// have a value or if it was the zero-value.
|
||||
HasValue bool
|
||||
}
|
||||
15
vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go
generated
vendored
15
vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go
generated
vendored
@@ -1,6 +1,11 @@
|
||||
package ratelimit
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// Reference:
|
||||
// https://github.com/getsentry/relay/blob/0424a2e017d193a93918053c90cdae9472d164bf/relay-common/src/constants.rs#L116-L127
|
||||
@@ -31,11 +36,11 @@ func (c Category) String() string {
|
||||
case "":
|
||||
return "CategoryAll"
|
||||
default:
|
||||
var b strings.Builder
|
||||
b.WriteString("Category")
|
||||
caser := cases.Title(language.English)
|
||||
rv := "Category"
|
||||
for _, w := range strings.Fields(string(c)) {
|
||||
b.WriteString(strings.Title(w))
|
||||
rv += caser.String(w)
|
||||
}
|
||||
return b.String()
|
||||
return rv
|
||||
}
|
||||
}
|
||||
|
||||
2
vendor/github.com/getsentry/sentry-go/internal/ratelimit/rate_limits.go
generated
vendored
2
vendor/github.com/getsentry/sentry-go/internal/ratelimit/rate_limits.go
generated
vendored
@@ -15,7 +15,7 @@ var errInvalidXSRLRetryAfter = errors.New("invalid retry-after value")
|
||||
//
|
||||
// Example
|
||||
//
|
||||
// X-Sentry-Rate-Limits: 60:transaction, 2700:default;error;security
|
||||
// X-Sentry-Rate-Limits: 60:transaction, 2700:default;error;security
|
||||
//
|
||||
// This will rate limit transactions for the next 60 seconds and errors for the
|
||||
// next 2700 seconds.
|
||||
|
||||
15
vendor/github.com/getsentry/sentry-go/internal/traceparser/README.md
generated
vendored
Normal file
15
vendor/github.com/getsentry/sentry-go/internal/traceparser/README.md
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
## Benchmark results
|
||||
|
||||
```
|
||||
goos: windows
|
||||
goarch: amd64
|
||||
pkg: github.com/getsentry/sentry-go/internal/trace
|
||||
cpu: 12th Gen Intel(R) Core(TM) i7-12700K
|
||||
BenchmarkEqualBytes-20 44323621 26.08 ns/op
|
||||
BenchmarkStringEqual-20 60980257 18.27 ns/op
|
||||
BenchmarkEqualPrefix-20 41369181 31.12 ns/op
|
||||
BenchmarkFullParse-20 702012 1507 ns/op 1353.42 MB/s 1024 B/op 6 allocs/op
|
||||
BenchmarkFramesIterator-20 1229971 969.3 ns/op 896 B/op 5 allocs/op
|
||||
BenchmarkFramesReversedIterator-20 1271061 944.5 ns/op 896 B/op 5 allocs/op
|
||||
BenchmarkSplitOnly-20 2250800 534.0 ns/op 3818.23 MB/s 128 B/op 1 allocs/op
|
||||
```
|
||||
217
vendor/github.com/getsentry/sentry-go/internal/traceparser/parser.go
generated
vendored
Normal file
217
vendor/github.com/getsentry/sentry-go/internal/traceparser/parser.go
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
package traceparser
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var blockSeparator = []byte("\n\n")
|
||||
var lineSeparator = []byte("\n")
|
||||
|
||||
// Parses multi-stacktrace text dump produced by runtime.Stack([]byte, all=true).
|
||||
// The parser prioritizes performance but requires the input to be well-formed in order to return correct data.
|
||||
// See https://github.com/golang/go/blob/go1.20.4/src/runtime/mprof.go#L1191
|
||||
func Parse(data []byte) TraceCollection {
|
||||
var it = TraceCollection{}
|
||||
if len(data) > 0 {
|
||||
it.blocks = bytes.Split(data, blockSeparator)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
type TraceCollection struct {
|
||||
blocks [][]byte
|
||||
}
|
||||
|
||||
func (it TraceCollection) Length() int {
|
||||
return len(it.blocks)
|
||||
}
|
||||
|
||||
// Returns the stacktrace item at the given index.
|
||||
func (it *TraceCollection) Item(i int) Trace {
|
||||
// The first item may have a leading data separator and the last one may have a trailing one.
|
||||
// Note: Trim() doesn't make a copy for single-character cutset under 0x80. It will just slice the original.
|
||||
var data []byte
|
||||
switch {
|
||||
case i == 0:
|
||||
data = bytes.TrimLeft(it.blocks[i], "\n")
|
||||
case i == len(it.blocks)-1:
|
||||
data = bytes.TrimRight(it.blocks[i], "\n")
|
||||
default:
|
||||
data = it.blocks[i]
|
||||
}
|
||||
|
||||
var splitAt = bytes.IndexByte(data, '\n')
|
||||
if splitAt < 0 {
|
||||
return Trace{header: data}
|
||||
}
|
||||
|
||||
return Trace{
|
||||
header: data[:splitAt],
|
||||
data: data[splitAt+1:],
|
||||
}
|
||||
}
|
||||
|
||||
// Trace represents a single stacktrace block, identified by a Goroutine ID and a sequence of Frames.
|
||||
type Trace struct {
|
||||
header []byte
|
||||
data []byte
|
||||
}
|
||||
|
||||
var goroutinePrefix = []byte("goroutine ")
|
||||
|
||||
// GoID parses the Goroutine ID from the header.
|
||||
func (t *Trace) GoID() (id uint64) {
|
||||
if bytes.HasPrefix(t.header, goroutinePrefix) {
|
||||
var line = t.header[len(goroutinePrefix):]
|
||||
var splitAt = bytes.IndexByte(line, ' ')
|
||||
if splitAt >= 0 {
|
||||
id, _ = strconv.ParseUint(string(line[:splitAt]), 10, 64)
|
||||
}
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// UniqueIdentifier can be used as a map key to identify the trace.
|
||||
func (t *Trace) UniqueIdentifier() []byte {
|
||||
return t.data
|
||||
}
|
||||
|
||||
func (t *Trace) Frames() FrameIterator {
|
||||
var lines = bytes.Split(t.data, lineSeparator)
|
||||
return FrameIterator{lines: lines, i: 0, len: len(lines)}
|
||||
}
|
||||
|
||||
func (t *Trace) FramesReversed() ReverseFrameIterator {
|
||||
var lines = bytes.Split(t.data, lineSeparator)
|
||||
return ReverseFrameIterator{lines: lines, i: len(lines)}
|
||||
}
|
||||
|
||||
const framesElided = "...additional frames elided..."
|
||||
|
||||
// FrameIterator iterates over stack frames.
|
||||
type FrameIterator struct {
|
||||
lines [][]byte
|
||||
i int
|
||||
len int
|
||||
}
|
||||
|
||||
// Next returns the next frame, or nil if there are none.
|
||||
func (it *FrameIterator) Next() Frame {
|
||||
return Frame{it.popLine(), it.popLine()}
|
||||
}
|
||||
|
||||
func (it *FrameIterator) popLine() []byte {
|
||||
switch {
|
||||
case it.i >= it.len:
|
||||
return nil
|
||||
case string(it.lines[it.i]) == framesElided:
|
||||
it.i++
|
||||
return it.popLine()
|
||||
default:
|
||||
it.i++
|
||||
return it.lines[it.i-1]
|
||||
}
|
||||
}
|
||||
|
||||
// HasNext return true if there are values to be read.
|
||||
func (it *FrameIterator) HasNext() bool {
|
||||
return it.i < it.len
|
||||
}
|
||||
|
||||
// LengthUpperBound returns the maximum number of elements this stacks may contain.
|
||||
// The actual number may be lower because of elided frames. As such, the returned value
|
||||
// cannot be used to iterate over the frames but may be used to reserve capacity.
|
||||
func (it *FrameIterator) LengthUpperBound() int {
|
||||
return it.len / 2
|
||||
}
|
||||
|
||||
// ReverseFrameIterator iterates over stack frames in reverse order.
|
||||
type ReverseFrameIterator struct {
|
||||
lines [][]byte
|
||||
i int
|
||||
}
|
||||
|
||||
// Next returns the next frame, or nil if there are none.
|
||||
func (it *ReverseFrameIterator) Next() Frame {
|
||||
var line2 = it.popLine()
|
||||
return Frame{it.popLine(), line2}
|
||||
}
|
||||
|
||||
func (it *ReverseFrameIterator) popLine() []byte {
|
||||
it.i--
|
||||
switch {
|
||||
case it.i < 0:
|
||||
return nil
|
||||
case string(it.lines[it.i]) == framesElided:
|
||||
return it.popLine()
|
||||
default:
|
||||
return it.lines[it.i]
|
||||
}
|
||||
}
|
||||
|
||||
// HasNext return true if there are values to be read.
|
||||
func (it *ReverseFrameIterator) HasNext() bool {
|
||||
return it.i > 1
|
||||
}
|
||||
|
||||
// LengthUpperBound returns the maximum number of elements this stacks may contain.
|
||||
// The actual number may be lower because of elided frames. As such, the returned value
|
||||
// cannot be used to iterate over the frames but may be used to reserve capacity.
|
||||
func (it *ReverseFrameIterator) LengthUpperBound() int {
|
||||
return len(it.lines) / 2
|
||||
}
|
||||
|
||||
type Frame struct {
|
||||
line1 []byte
|
||||
line2 []byte
|
||||
}
|
||||
|
||||
// UniqueIdentifier can be used as a map key to identify the frame.
|
||||
func (f *Frame) UniqueIdentifier() []byte {
|
||||
// line2 contains file path, line number and program-counter offset from the beginning of a function
|
||||
// e.g. C:/Users/name/scoop/apps/go/current/src/testing/testing.go:1906 +0x63a
|
||||
return f.line2
|
||||
}
|
||||
|
||||
var createdByPrefix = []byte("created by ")
|
||||
|
||||
func (f *Frame) Func() []byte {
|
||||
if bytes.HasPrefix(f.line1, createdByPrefix) {
|
||||
// Since go1.21, the line ends with " in goroutine X", saying which goroutine created this one.
|
||||
// We currently don't have use for that so just remove it.
|
||||
var line = f.line1[len(createdByPrefix):]
|
||||
var spaceAt = bytes.IndexByte(line, ' ')
|
||||
if spaceAt < 0 {
|
||||
return line
|
||||
}
|
||||
return line[:spaceAt]
|
||||
}
|
||||
|
||||
var end = bytes.LastIndexByte(f.line1, '(')
|
||||
if end >= 0 {
|
||||
return f.line1[:end]
|
||||
}
|
||||
|
||||
return f.line1
|
||||
}
|
||||
|
||||
func (f *Frame) File() (path []byte, lineNumber int) {
|
||||
var line = f.line2
|
||||
if len(line) > 0 && line[0] == '\t' {
|
||||
line = line[1:]
|
||||
}
|
||||
|
||||
var splitAt = bytes.IndexByte(line, ' ')
|
||||
if splitAt >= 0 {
|
||||
line = line[:splitAt]
|
||||
}
|
||||
|
||||
splitAt = bytes.LastIndexByte(line, ':')
|
||||
if splitAt < 0 {
|
||||
return line, 0
|
||||
}
|
||||
|
||||
lineNumber, _ = strconv.Atoi(string(line[splitAt+1:]))
|
||||
return line[:splitAt], lineNumber
|
||||
}
|
||||
73
vendor/github.com/getsentry/sentry-go/profile_sample.go
generated
vendored
Normal file
73
vendor/github.com/getsentry/sentry-go/profile_sample.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
package sentry
|
||||
|
||||
// Based on https://github.com/getsentry/vroom/blob/d11c26063e802d66b9a592c4010261746ca3dfa4/internal/sample/sample.go
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
profileDevice struct {
|
||||
Architecture string `json:"architecture"`
|
||||
Classification string `json:"classification"`
|
||||
Locale string `json:"locale"`
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
Model string `json:"model"`
|
||||
}
|
||||
|
||||
profileOS struct {
|
||||
BuildNumber string `json:"build_number"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
profileRuntime struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
profileSample struct {
|
||||
ElapsedSinceStartNS uint64 `json:"elapsed_since_start_ns"`
|
||||
StackID int `json:"stack_id"`
|
||||
ThreadID uint64 `json:"thread_id"`
|
||||
}
|
||||
|
||||
profileThreadMetadata struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Priority int `json:"priority,omitempty"`
|
||||
}
|
||||
|
||||
profileStack []int
|
||||
|
||||
profileTrace struct {
|
||||
Frames []*Frame `json:"frames"`
|
||||
Samples []profileSample `json:"samples"`
|
||||
Stacks []profileStack `json:"stacks"`
|
||||
ThreadMetadata map[uint64]*profileThreadMetadata `json:"thread_metadata"`
|
||||
}
|
||||
|
||||
profileInfo struct {
|
||||
DebugMeta *DebugMeta `json:"debug_meta,omitempty"`
|
||||
Device profileDevice `json:"device"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
EventID string `json:"event_id"`
|
||||
OS profileOS `json:"os"`
|
||||
Platform string `json:"platform"`
|
||||
Release string `json:"release"`
|
||||
Dist string `json:"dist"`
|
||||
Runtime profileRuntime `json:"runtime"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Trace *profileTrace `json:"profile"`
|
||||
Transaction profileTransaction `json:"transaction"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// see https://github.com/getsentry/vroom/blob/a91e39416723ec44fc54010257020eeaf9a77cbd/internal/transaction/transaction.go
|
||||
profileTransaction struct {
|
||||
ActiveThreadID uint64 `json:"active_thread_id"`
|
||||
DurationNS uint64 `json:"duration_ns,omitempty"`
|
||||
ID EventID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
TraceID string `json:"trace_id"`
|
||||
}
|
||||
)
|
||||
451
vendor/github.com/getsentry/sentry-go/profiler.go
generated
vendored
Normal file
451
vendor/github.com/getsentry/sentry-go/profiler.go
generated
vendored
Normal file
@@ -0,0 +1,451 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"strconv"
|
||||
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go/internal/traceparser"
|
||||
)
|
||||
|
||||
// Start a profiler that collects samples continuously, with a buffer of up to 30 seconds.
|
||||
// Later, you can collect a slice from this buffer, producing a Trace.
|
||||
func startProfiling(startTime time.Time) profiler {
|
||||
onProfilerStart()
|
||||
|
||||
p := newProfiler(startTime)
|
||||
|
||||
// Wait for the profiler to finish setting up before returning to the caller.
|
||||
started := make(chan struct{})
|
||||
go p.run(started)
|
||||
|
||||
if _, ok := <-started; ok {
|
||||
return p
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type profiler interface {
|
||||
// GetSlice returns a slice of the profiled data between the given times.
|
||||
GetSlice(startTime, endTime time.Time) *profilerResult
|
||||
Stop(wait bool)
|
||||
}
|
||||
|
||||
type profilerResult struct {
|
||||
callerGoID uint64
|
||||
trace *profileTrace
|
||||
}
|
||||
|
||||
func getCurrentGoID() uint64 {
|
||||
// We shouldn't panic but let's be super safe.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Logger.Printf("Profiler panic in getCurrentGoID(): %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Buffer to read the stack trace into. We should be good with a small buffer because we only need the first line.
|
||||
var stacksBuffer = make([]byte, 100)
|
||||
var n = runtime.Stack(stacksBuffer, false)
|
||||
if n > 0 {
|
||||
var traces = traceparser.Parse(stacksBuffer[0:n])
|
||||
if traces.Length() > 0 {
|
||||
var trace = traces.Item(0)
|
||||
return trace.GoID()
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
const profilerSamplingRateHz = 101 // 101 Hz; not 100 Hz because of the lockstep sampling (https://stackoverflow.com/a/45471031/1181370)
|
||||
const profilerSamplingRate = time.Second / profilerSamplingRateHz
|
||||
const stackBufferMaxGrowth = 512 * 1024
|
||||
const stackBufferLimit = 10 * 1024 * 1024
|
||||
const profilerRuntimeLimit = 30 // seconds
|
||||
|
||||
type profileRecorder struct {
|
||||
startTime time.Time
|
||||
stopSignal chan struct{}
|
||||
stopped int64
|
||||
mutex sync.RWMutex
|
||||
testProfilerPanic int64
|
||||
|
||||
// Map from runtime.StackRecord.Stack0 to an index in stacks.
|
||||
stackIndexes map[string]int
|
||||
stacks []profileStack
|
||||
newStacks []profileStack // New stacks created in the current interation.
|
||||
stackKeyBuffer []byte
|
||||
|
||||
// Map from runtime.Frame.PC to an index in frames.
|
||||
frameIndexes map[string]int
|
||||
frames []*Frame
|
||||
newFrames []*Frame // New frames created in the current interation.
|
||||
|
||||
// We keep a ring buffer of 30 seconds worth of samples, so that we can later slice it.
|
||||
// Each bucket is a slice of samples all taken at the same time.
|
||||
samplesBucketsHead *ring.Ring
|
||||
|
||||
// Buffer to read current stacks - will grow automatically up to stackBufferLimit.
|
||||
stacksBuffer []byte
|
||||
}
|
||||
|
||||
func newProfiler(startTime time.Time) *profileRecorder {
|
||||
// Pre-allocate the profile trace for the currently active number of routines & 100 ms worth of samples.
|
||||
// Other coefficients are just guesses of what might be a good starting point to avoid allocs on short runs.
|
||||
return &profileRecorder{
|
||||
startTime: startTime,
|
||||
stopSignal: make(chan struct{}, 1),
|
||||
|
||||
stackIndexes: make(map[string]int, 32),
|
||||
stacks: make([]profileStack, 0, 32),
|
||||
newStacks: make([]profileStack, 0, 32),
|
||||
|
||||
frameIndexes: make(map[string]int, 128),
|
||||
frames: make([]*Frame, 0, 128),
|
||||
newFrames: make([]*Frame, 0, 128),
|
||||
|
||||
samplesBucketsHead: ring.New(profilerRuntimeLimit * profilerSamplingRateHz),
|
||||
|
||||
// A buffer of 2 KiB per goroutine stack looks like a good starting point (empirically determined).
|
||||
stacksBuffer: make([]byte, runtime.NumGoroutine()*2048),
|
||||
}
|
||||
}
|
||||
|
||||
// This allows us to test whether panic during profiling are handled correctly and don't block execution.
|
||||
// If the number is lower than 0, profilerGoroutine() will panic immedately.
|
||||
// If the number is higher than 0, profiler.onTick() will panic when the given samples-set index is being collected.
|
||||
var testProfilerPanic int64
|
||||
var profilerRunning int64
|
||||
|
||||
func (p *profileRecorder) run(started chan struct{}) {
|
||||
// Code backup for manual test debugging:
|
||||
// if !atomic.CompareAndSwapInt64(&profilerRunning, 0, 1) {
|
||||
// panic("Only one profiler can be running at a time")
|
||||
// }
|
||||
|
||||
// We shouldn't panic but let's be super safe.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
Logger.Printf("Profiler panic in run(): %v\n", err)
|
||||
}
|
||||
atomic.StoreInt64(&testProfilerPanic, 0)
|
||||
close(started)
|
||||
p.stopSignal <- struct{}{}
|
||||
atomic.StoreInt64(&p.stopped, 1)
|
||||
atomic.StoreInt64(&profilerRunning, 0)
|
||||
}()
|
||||
|
||||
p.testProfilerPanic = atomic.LoadInt64(&testProfilerPanic)
|
||||
if p.testProfilerPanic < 0 {
|
||||
Logger.Printf("Profiler panicking during startup because testProfilerPanic == %v\n", p.testProfilerPanic)
|
||||
panic("This is an expected panic in profilerGoroutine() during tests")
|
||||
}
|
||||
|
||||
// Collect the first sample immediately.
|
||||
p.onTick()
|
||||
|
||||
// Periodically collect stacks, starting after profilerSamplingRate has passed.
|
||||
collectTicker := profilerTickerFactory(profilerSamplingRate)
|
||||
defer collectTicker.Stop()
|
||||
var tickerChannel = collectTicker.TickSource()
|
||||
|
||||
started <- struct{}{}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tickerChannel:
|
||||
p.onTick()
|
||||
collectTicker.Ticked()
|
||||
case <-p.stopSignal:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *profileRecorder) Stop(wait bool) {
|
||||
if atomic.LoadInt64(&p.stopped) == 1 {
|
||||
return
|
||||
}
|
||||
p.stopSignal <- struct{}{}
|
||||
if wait {
|
||||
<-p.stopSignal
|
||||
}
|
||||
}
|
||||
|
||||
func (p *profileRecorder) GetSlice(startTime, endTime time.Time) *profilerResult {
|
||||
// Unlikely edge cases - profiler wasn't running at all or the given times are invalid in relation to each other.
|
||||
if p.startTime.After(endTime) || startTime.After(endTime) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var relativeStartNS = uint64(0)
|
||||
if p.startTime.Before(startTime) {
|
||||
relativeStartNS = uint64(startTime.Sub(p.startTime).Nanoseconds())
|
||||
}
|
||||
var relativeEndNS = uint64(endTime.Sub(p.startTime).Nanoseconds())
|
||||
|
||||
samplesCount, bucketsReversed, trace := p.getBuckets(relativeStartNS, relativeEndNS)
|
||||
if samplesCount == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result = &profilerResult{
|
||||
callerGoID: getCurrentGoID(),
|
||||
trace: trace,
|
||||
}
|
||||
|
||||
trace.Samples = make([]profileSample, samplesCount)
|
||||
trace.ThreadMetadata = make(map[uint64]*profileThreadMetadata, len(bucketsReversed[0].goIDs))
|
||||
var s = samplesCount - 1
|
||||
for _, bucket := range bucketsReversed {
|
||||
var elapsedSinceStartNS = bucket.relativeTimeNS - relativeStartNS
|
||||
for i, goID := range bucket.goIDs {
|
||||
trace.Samples[s].ElapsedSinceStartNS = elapsedSinceStartNS
|
||||
trace.Samples[s].ThreadID = goID
|
||||
trace.Samples[s].StackID = bucket.stackIDs[i]
|
||||
s--
|
||||
|
||||
if _, goroutineExists := trace.ThreadMetadata[goID]; !goroutineExists {
|
||||
trace.ThreadMetadata[goID] = &profileThreadMetadata{
|
||||
Name: "Goroutine " + strconv.FormatUint(goID, 10),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Collect all buckets of samples in the given time range while holding a read lock.
|
||||
func (p *profileRecorder) getBuckets(relativeStartNS, relativeEndNS uint64) (samplesCount int, buckets []*profileSamplesBucket, trace *profileTrace) {
|
||||
p.mutex.RLock()
|
||||
defer p.mutex.RUnlock()
|
||||
|
||||
// sampleBucketsHead points at the last stored bucket so it's a good starting point to search backwards for the end.
|
||||
var end = p.samplesBucketsHead
|
||||
for end.Value != nil && end.Value.(*profileSamplesBucket).relativeTimeNS > relativeEndNS {
|
||||
end = end.Prev()
|
||||
}
|
||||
|
||||
// Edge case - no items stored before the given endTime.
|
||||
if end.Value == nil {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
{ // Find the first item after the given startTime.
|
||||
var start = end
|
||||
var prevBucket *profileSamplesBucket
|
||||
samplesCount = 0
|
||||
buckets = make([]*profileSamplesBucket, 0, int64((relativeEndNS-relativeStartNS)/uint64(profilerSamplingRate.Nanoseconds()))+1)
|
||||
for start.Value != nil {
|
||||
var bucket = start.Value.(*profileSamplesBucket)
|
||||
|
||||
// If this bucket's time is before the requests start time, don't collect it (and stop iterating further).
|
||||
if bucket.relativeTimeNS < relativeStartNS {
|
||||
break
|
||||
}
|
||||
|
||||
// If this bucket time is greater than previous the bucket's time, we have exhausted the whole ring buffer
|
||||
// before we were able to find the start time. That means the start time is not present and we must break.
|
||||
// This happens if the slice duration exceeds the ring buffer capacity.
|
||||
if prevBucket != nil && bucket.relativeTimeNS > prevBucket.relativeTimeNS {
|
||||
break
|
||||
}
|
||||
|
||||
samplesCount += len(bucket.goIDs)
|
||||
buckets = append(buckets, bucket)
|
||||
|
||||
start = start.Prev()
|
||||
prevBucket = bucket
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case - if the period requested was too short and we haven't collected enough samples.
|
||||
if len(buckets) < 2 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
trace = &profileTrace{
|
||||
Frames: p.frames,
|
||||
Stacks: p.stacks,
|
||||
}
|
||||
return samplesCount, buckets, trace
|
||||
}
|
||||
|
||||
func (p *profileRecorder) onTick() {
|
||||
elapsedNs := time.Since(p.startTime).Nanoseconds()
|
||||
|
||||
if p.testProfilerPanic > 0 {
|
||||
Logger.Printf("Profiler testProfilerPanic == %v\n", p.testProfilerPanic)
|
||||
if p.testProfilerPanic == 1 {
|
||||
Logger.Println("Profiler panicking onTick()")
|
||||
panic("This is an expected panic in Profiler.OnTick() during tests")
|
||||
}
|
||||
p.testProfilerPanic--
|
||||
}
|
||||
|
||||
records := p.collectRecords()
|
||||
p.processRecords(uint64(elapsedNs), records)
|
||||
|
||||
// Free up some memory if we don't need such a large buffer anymore.
|
||||
if len(p.stacksBuffer) > len(records)*3 {
|
||||
p.stacksBuffer = make([]byte, len(records)*3)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *profileRecorder) collectRecords() []byte {
|
||||
for {
|
||||
// Capture stacks for all existing goroutines.
|
||||
// Note: runtime.GoroutineProfile() would be better but we can't use it at the moment because
|
||||
// it doesn't give us `gid` for each routine, see https://github.com/golang/go/issues/59663
|
||||
n := runtime.Stack(p.stacksBuffer, true)
|
||||
|
||||
// If we couldn't read everything, increase the buffer and try again.
|
||||
if n >= len(p.stacksBuffer) && n < stackBufferLimit {
|
||||
var newSize = n * 2
|
||||
if newSize > n+stackBufferMaxGrowth {
|
||||
newSize = n + stackBufferMaxGrowth
|
||||
}
|
||||
if newSize > stackBufferLimit {
|
||||
newSize = stackBufferLimit
|
||||
}
|
||||
p.stacksBuffer = make([]byte, newSize)
|
||||
} else {
|
||||
return p.stacksBuffer[0:n]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *profileRecorder) processRecords(elapsedNs uint64, stacksBuffer []byte) {
|
||||
var traces = traceparser.Parse(stacksBuffer)
|
||||
var length = traces.Length()
|
||||
|
||||
// Shouldn't happen but let's be safe and don't store empty buckets.
|
||||
if length == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var bucket = &profileSamplesBucket{
|
||||
relativeTimeNS: elapsedNs,
|
||||
stackIDs: make([]int, length),
|
||||
goIDs: make([]uint64, length),
|
||||
}
|
||||
|
||||
// reset buffers
|
||||
p.newFrames = p.newFrames[:0]
|
||||
p.newStacks = p.newStacks[:0]
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
var stack = traces.Item(i)
|
||||
bucket.stackIDs[i] = p.addStackTrace(stack)
|
||||
bucket.goIDs[i] = stack.GoID()
|
||||
}
|
||||
|
||||
p.mutex.Lock()
|
||||
defer p.mutex.Unlock()
|
||||
|
||||
p.stacks = append(p.stacks, p.newStacks...)
|
||||
p.frames = append(p.frames, p.newFrames...)
|
||||
|
||||
p.samplesBucketsHead = p.samplesBucketsHead.Next()
|
||||
p.samplesBucketsHead.Value = bucket
|
||||
}
|
||||
|
||||
func (p *profileRecorder) addStackTrace(capturedStack traceparser.Trace) int {
|
||||
iter := capturedStack.Frames()
|
||||
stack := make(profileStack, 0, iter.LengthUpperBound())
|
||||
|
||||
// Originally, we've used `capturedStack.UniqueIdentifier()` as a key but that was incorrect because it also
|
||||
// contains function arguments and we want to group stacks by function name and file/line only.
|
||||
// Instead, we need to parse frames and we use a list of their indexes as a key.
|
||||
// We reuse the same buffer for each stack to avoid allocations; this is a hot spot.
|
||||
var expectedBufferLen = cap(stack) * 5 // 4 bytes per frame + 1 byte for space
|
||||
if cap(p.stackKeyBuffer) < expectedBufferLen {
|
||||
p.stackKeyBuffer = make([]byte, 0, expectedBufferLen)
|
||||
} else {
|
||||
p.stackKeyBuffer = p.stackKeyBuffer[:0]
|
||||
}
|
||||
|
||||
for iter.HasNext() {
|
||||
var frame = iter.Next()
|
||||
if frameIndex := p.addFrame(frame); frameIndex >= 0 {
|
||||
stack = append(stack, frameIndex)
|
||||
|
||||
p.stackKeyBuffer = append(p.stackKeyBuffer, 0) // space
|
||||
|
||||
// The following code is just like binary.AppendUvarint() which isn't yet available in Go 1.18.
|
||||
x := uint64(frameIndex) + 1
|
||||
for x >= 0x80 {
|
||||
p.stackKeyBuffer = append(p.stackKeyBuffer, byte(x)|0x80)
|
||||
x >>= 7
|
||||
}
|
||||
p.stackKeyBuffer = append(p.stackKeyBuffer, byte(x))
|
||||
}
|
||||
}
|
||||
|
||||
stackIndex, exists := p.stackIndexes[string(p.stackKeyBuffer)]
|
||||
if !exists {
|
||||
stackIndex = len(p.stacks) + len(p.newStacks)
|
||||
p.newStacks = append(p.newStacks, stack)
|
||||
p.stackIndexes[string(p.stackKeyBuffer)] = stackIndex
|
||||
}
|
||||
|
||||
return stackIndex
|
||||
}
|
||||
|
||||
func (p *profileRecorder) addFrame(capturedFrame traceparser.Frame) int {
|
||||
// NOTE: Don't convert to string yet, it's expensive and compiler can avoid it when
|
||||
// indexing into a map (only needs a copy when adding a new key to the map).
|
||||
var key = capturedFrame.UniqueIdentifier()
|
||||
|
||||
frameIndex, exists := p.frameIndexes[string(key)]
|
||||
if !exists {
|
||||
module, function := splitQualifiedFunctionName(string(capturedFrame.Func()))
|
||||
file, line := capturedFrame.File()
|
||||
frame := newFrame(module, function, string(file), line)
|
||||
frameIndex = len(p.frames) + len(p.newFrames)
|
||||
p.newFrames = append(p.newFrames, &frame)
|
||||
p.frameIndexes[string(key)] = frameIndex
|
||||
}
|
||||
return frameIndex
|
||||
}
|
||||
|
||||
type profileSamplesBucket struct {
|
||||
relativeTimeNS uint64
|
||||
stackIDs []int
|
||||
goIDs []uint64
|
||||
}
|
||||
|
||||
// A Ticker holds a channel that delivers “ticks” of a clock at intervals.
|
||||
type profilerTicker interface {
|
||||
// Stop turns off a ticker. After Stop, no more ticks will be sent.
|
||||
Stop()
|
||||
|
||||
// TickSource returns a read-only channel of ticks.
|
||||
TickSource() <-chan time.Time
|
||||
|
||||
// Ticked is called by the Profiler after a tick is processed to notify the ticker. Used for testing.
|
||||
Ticked()
|
||||
}
|
||||
|
||||
type timeTicker struct {
|
||||
*time.Ticker
|
||||
}
|
||||
|
||||
func (t *timeTicker) TickSource() <-chan time.Time {
|
||||
return t.C
|
||||
}
|
||||
|
||||
func (t *timeTicker) Ticked() {}
|
||||
|
||||
func profilerTickerFactoryDefault(d time.Duration) profilerTicker {
|
||||
return &timeTicker{time.NewTicker(d)}
|
||||
}
|
||||
|
||||
// We allow overriding the ticker for tests. CI is terribly flaky
|
||||
// because the time.Ticker doesn't guarantee regular ticks - they may come (a lot) later than the given interval.
|
||||
var profilerTickerFactory = profilerTickerFactoryDefault
|
||||
5
vendor/github.com/getsentry/sentry-go/profiler_other.go
generated
vendored
Normal file
5
vendor/github.com/getsentry/sentry-go/profiler_other.go
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !windows
|
||||
|
||||
package sentry
|
||||
|
||||
func onProfilerStart() {}
|
||||
24
vendor/github.com/getsentry/sentry-go/profiler_windows.go
generated
vendored
Normal file
24
vendor/github.com/getsentry/sentry-go/profiler_windows.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// This works around the ticker resolution on Windows being ~15ms by default.
|
||||
// See https://github.com/golang/go/issues/44343
|
||||
func setTimeTickerResolution() {
|
||||
var winmmDLL = syscall.NewLazyDLL("winmm.dll")
|
||||
if winmmDLL != nil {
|
||||
var timeBeginPeriod = winmmDLL.NewProc("timeBeginPeriod")
|
||||
if timeBeginPeriod != nil {
|
||||
timeBeginPeriod.Call(uintptr(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var setupTickerResolutionOnce sync.Once
|
||||
|
||||
func onProfilerStart() {
|
||||
setupTickerResolutionOnce.Do(setTimeTickerResolution)
|
||||
}
|
||||
96
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
96
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -26,13 +25,13 @@ import (
|
||||
type Scope struct {
|
||||
mu sync.RWMutex
|
||||
breadcrumbs []*Breadcrumb
|
||||
attachments []*Attachment
|
||||
user User
|
||||
tags map[string]string
|
||||
contexts map[string]interface{}
|
||||
contexts map[string]Context
|
||||
extra map[string]interface{}
|
||||
fingerprint []string
|
||||
level Level
|
||||
transaction string
|
||||
request *http.Request
|
||||
// requestBody holds a reference to the original request.Body.
|
||||
requestBody interface {
|
||||
@@ -50,8 +49,9 @@ type Scope struct {
|
||||
func NewScope() *Scope {
|
||||
scope := Scope{
|
||||
breadcrumbs: make([]*Breadcrumb, 0),
|
||||
attachments: make([]*Attachment, 0),
|
||||
tags: make(map[string]string),
|
||||
contexts: make(map[string]interface{}),
|
||||
contexts: make(map[string]Context),
|
||||
extra: make(map[string]interface{}),
|
||||
fingerprint: make([]string, 0),
|
||||
}
|
||||
@@ -83,6 +83,22 @@ func (scope *Scope) ClearBreadcrumbs() {
|
||||
scope.breadcrumbs = []*Breadcrumb{}
|
||||
}
|
||||
|
||||
// AddAttachment adds new attachment to the current scope.
|
||||
func (scope *Scope) AddAttachment(attachment *Attachment) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.attachments = append(scope.attachments, attachment)
|
||||
}
|
||||
|
||||
// ClearAttachments clears all attachments from the current scope.
|
||||
func (scope *Scope) ClearAttachments() {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.attachments = []*Attachment{}
|
||||
}
|
||||
|
||||
// SetUser sets the user for the current scope.
|
||||
func (scope *Scope) SetUser(user User) {
|
||||
scope.mu.Lock()
|
||||
@@ -146,7 +162,7 @@ const maxRequestBodyBytes = 10 * 1024
|
||||
|
||||
// A limitedBuffer is like a bytes.Buffer, but limited to store at most Capacity
|
||||
// bytes. Any writes past the capacity are silently discarded, similar to
|
||||
// ioutil.Discard.
|
||||
// io.Discard.
|
||||
type limitedBuffer struct {
|
||||
Capacity int
|
||||
|
||||
@@ -209,7 +225,7 @@ func (scope *Scope) RemoveTag(key string) {
|
||||
}
|
||||
|
||||
// SetContext adds a context to the current scope.
|
||||
func (scope *Scope) SetContext(key string, value interface{}) {
|
||||
func (scope *Scope) SetContext(key string, value Context) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
@@ -217,7 +233,7 @@ func (scope *Scope) SetContext(key string, value interface{}) {
|
||||
}
|
||||
|
||||
// SetContexts assigns multiple contexts to the current scope.
|
||||
func (scope *Scope) SetContexts(contexts map[string]interface{}) {
|
||||
func (scope *Scope) SetContexts(contexts map[string]Context) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
@@ -276,22 +292,6 @@ func (scope *Scope) SetLevel(level Level) {
|
||||
scope.level = level
|
||||
}
|
||||
|
||||
// SetTransaction sets the transaction name for the current transaction.
|
||||
func (scope *Scope) SetTransaction(name string) {
|
||||
scope.mu.Lock()
|
||||
defer scope.mu.Unlock()
|
||||
|
||||
scope.transaction = name
|
||||
}
|
||||
|
||||
// Transaction returns the transaction name for the current transaction.
|
||||
func (scope *Scope) Transaction() (name string) {
|
||||
scope.mu.RLock()
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
return scope.transaction
|
||||
}
|
||||
|
||||
// Clone returns a copy of the current scope with all data copied over.
|
||||
func (scope *Scope) Clone() *Scope {
|
||||
scope.mu.RLock()
|
||||
@@ -301,11 +301,13 @@ func (scope *Scope) Clone() *Scope {
|
||||
clone.user = scope.user
|
||||
clone.breadcrumbs = make([]*Breadcrumb, len(scope.breadcrumbs))
|
||||
copy(clone.breadcrumbs, scope.breadcrumbs)
|
||||
clone.attachments = make([]*Attachment, len(scope.attachments))
|
||||
copy(clone.attachments, scope.attachments)
|
||||
for key, value := range scope.tags {
|
||||
clone.tags[key] = value
|
||||
}
|
||||
for key, value := range scope.contexts {
|
||||
clone.contexts[key] = value
|
||||
clone.contexts[key] = cloneContext(value)
|
||||
}
|
||||
for key, value := range scope.extra {
|
||||
clone.extra[key] = value
|
||||
@@ -313,7 +315,6 @@ func (scope *Scope) Clone() *Scope {
|
||||
clone.fingerprint = make([]string, len(scope.fingerprint))
|
||||
copy(clone.fingerprint, scope.fingerprint)
|
||||
clone.level = scope.level
|
||||
clone.transaction = scope.transaction
|
||||
clone.request = scope.request
|
||||
clone.requestBody = scope.requestBody
|
||||
clone.eventProcessors = scope.eventProcessors
|
||||
@@ -339,16 +340,16 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||
defer scope.mu.RUnlock()
|
||||
|
||||
if len(scope.breadcrumbs) > 0 {
|
||||
if event.Breadcrumbs == nil {
|
||||
event.Breadcrumbs = []*Breadcrumb{}
|
||||
}
|
||||
|
||||
event.Breadcrumbs = append(event.Breadcrumbs, scope.breadcrumbs...)
|
||||
}
|
||||
|
||||
if len(scope.attachments) > 0 {
|
||||
event.Attachments = append(event.Attachments, scope.attachments...)
|
||||
}
|
||||
|
||||
if len(scope.tags) > 0 {
|
||||
if event.Tags == nil {
|
||||
event.Tags = make(map[string]string)
|
||||
event.Tags = make(map[string]string, len(scope.tags))
|
||||
}
|
||||
|
||||
for key, value := range scope.tags {
|
||||
@@ -358,7 +359,7 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||
|
||||
if len(scope.contexts) > 0 {
|
||||
if event.Contexts == nil {
|
||||
event.Contexts = make(map[string]interface{})
|
||||
event.Contexts = make(map[string]Context)
|
||||
}
|
||||
|
||||
for key, value := range scope.contexts {
|
||||
@@ -370,13 +371,17 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||
// to link errors and traces/spans in Sentry.
|
||||
continue
|
||||
}
|
||||
event.Contexts[key] = value
|
||||
|
||||
// Ensure we are not overwriting event fields
|
||||
if _, ok := event.Contexts[key]; !ok {
|
||||
event.Contexts[key] = cloneContext(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(scope.extra) > 0 {
|
||||
if event.Extra == nil {
|
||||
event.Extra = make(map[string]interface{})
|
||||
event.Extra = make(map[string]interface{}, len(scope.extra))
|
||||
}
|
||||
|
||||
for key, value := range scope.extra {
|
||||
@@ -384,24 +389,18 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||
}
|
||||
}
|
||||
|
||||
if (reflect.DeepEqual(event.User, User{})) {
|
||||
if event.User.IsEmpty() {
|
||||
event.User = scope.user
|
||||
}
|
||||
|
||||
if (event.Fingerprint == nil || len(event.Fingerprint) == 0) &&
|
||||
len(scope.fingerprint) > 0 {
|
||||
event.Fingerprint = make([]string, len(scope.fingerprint))
|
||||
copy(event.Fingerprint, scope.fingerprint)
|
||||
if len(event.Fingerprint) == 0 {
|
||||
event.Fingerprint = append(event.Fingerprint, scope.fingerprint...)
|
||||
}
|
||||
|
||||
if scope.level != "" {
|
||||
event.Level = scope.level
|
||||
}
|
||||
|
||||
if scope.transaction != "" {
|
||||
event.Transaction = scope.transaction
|
||||
}
|
||||
|
||||
if event.Request == nil && scope.request != nil {
|
||||
event.Request = NewRequest(scope.request)
|
||||
// NOTE: The SDK does not attempt to send partial request body data.
|
||||
@@ -429,3 +428,16 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
// cloneContext returns a new context with keys and values copied from the passed one.
|
||||
//
|
||||
// Note: a new Context (map) is returned, but the function does NOT do
|
||||
// a proper deep copy: if some context values are pointer types (e.g. maps),
|
||||
// they won't be properly copied.
|
||||
func cloneContext(c Context) Context {
|
||||
res := Context{}
|
||||
for k, v := range c {
|
||||
res[k] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
13
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
13
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
@@ -5,16 +5,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Version is the version of the SDK.
|
||||
const Version = "0.13.0"
|
||||
// The version of the SDK.
|
||||
const SDKVersion = "0.27.0"
|
||||
|
||||
// apiVersion is the minimum version of the Sentry API compatible with the
|
||||
// sentry-go SDK.
|
||||
const apiVersion = "7"
|
||||
|
||||
// userAgent is the User-Agent of outgoing HTTP requests.
|
||||
const userAgent = "sentry-go/" + Version
|
||||
|
||||
// Init initializes the SDK with options. The returned error is non-nil if
|
||||
// options is invalid, for instance if a malformed DSN is provided.
|
||||
func Init(options ClientOptions) error {
|
||||
@@ -48,6 +45,12 @@ func CaptureException(exception error) *EventID {
|
||||
return hub.CaptureException(exception)
|
||||
}
|
||||
|
||||
// CaptureCheckIn captures a (cron) monitor check-in.
|
||||
func CaptureCheckIn(checkIn *CheckIn, monitorConfig *MonitorConfig) *EventID {
|
||||
hub := CurrentHub()
|
||||
return hub.CaptureCheckIn(checkIn, monitorConfig)
|
||||
}
|
||||
|
||||
// CaptureEvent captures an event on the currently active client if any.
|
||||
//
|
||||
// The event must already be assembled. Typically code would instead use
|
||||
|
||||
4
vendor/github.com/getsentry/sentry-go/sourcereader.go
generated
vendored
4
vendor/github.com/getsentry/sentry-go/sourcereader.go
generated
vendored
@@ -2,7 +2,7 @@ package sentry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ func (sr *sourceReader) readContextLines(filename string, line, context int) ([]
|
||||
lines, ok := sr.cache[filename]
|
||||
|
||||
if !ok {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
sr.cache[filename] = nil
|
||||
return nil, 0
|
||||
|
||||
9
vendor/github.com/getsentry/sentry-go/span_recorder.go
generated
vendored
9
vendor/github.com/getsentry/sentry-go/span_recorder.go
generated
vendored
@@ -4,11 +4,6 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// maxSpans limits the number of recorded spans per transaction. The limit is
|
||||
// meant to bound memory usage and prevent too large transaction events that
|
||||
// would be rejected by Sentry.
|
||||
const maxSpans = 1000
|
||||
|
||||
// A spanRecorder stores a span tree that makes up a transaction. Safe for
|
||||
// concurrent use. It is okay to add child spans from multiple goroutines.
|
||||
type spanRecorder struct {
|
||||
@@ -20,6 +15,10 @@ type spanRecorder struct {
|
||||
// record stores a span. The first stored span is assumed to be the root of a
|
||||
// span tree.
|
||||
func (r *spanRecorder) record(s *Span) {
|
||||
maxSpans := defaultMaxSpans
|
||||
if client := CurrentHub().Client(); client != nil {
|
||||
maxSpans = client.options.MaxSpans
|
||||
}
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if len(r.spans) >= maxSpans {
|
||||
|
||||
229
vendor/github.com/getsentry/sentry-go/stacktrace.go
generated
vendored
229
vendor/github.com/getsentry/sentry-go/stacktrace.go
generated
vendored
@@ -2,7 +2,6 @@ package sentry
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -32,8 +31,8 @@ func NewStacktrace() *Stacktrace {
|
||||
return nil
|
||||
}
|
||||
|
||||
frames := extractFrames(pcs[:n])
|
||||
frames = filterFrames(frames)
|
||||
runtimeFrames := extractFrames(pcs[:n])
|
||||
frames := createFrames(runtimeFrames)
|
||||
|
||||
stacktrace := Stacktrace{
|
||||
Frames: frames,
|
||||
@@ -62,8 +61,8 @@ func ExtractStacktrace(err error) *Stacktrace {
|
||||
return nil
|
||||
}
|
||||
|
||||
frames := extractFrames(pcs)
|
||||
frames = filterFrames(frames)
|
||||
runtimeFrames := extractFrames(pcs)
|
||||
frames := createFrames(runtimeFrames)
|
||||
|
||||
stacktrace := Stacktrace{
|
||||
Frames: frames,
|
||||
@@ -73,39 +72,38 @@ func ExtractStacktrace(err error) *Stacktrace {
|
||||
}
|
||||
|
||||
func extractReflectedStacktraceMethod(err error) reflect.Value {
|
||||
var method reflect.Value
|
||||
errValue := reflect.ValueOf(err)
|
||||
|
||||
// https://github.com/go-errors/errors
|
||||
methodStackFrames := errValue.MethodByName("StackFrames")
|
||||
if methodStackFrames.IsValid() {
|
||||
return methodStackFrames
|
||||
}
|
||||
|
||||
// https://github.com/pkg/errors
|
||||
methodStackTrace := errValue.MethodByName("StackTrace")
|
||||
if methodStackTrace.IsValid() {
|
||||
return methodStackTrace
|
||||
}
|
||||
|
||||
// https://github.com/pingcap/errors
|
||||
methodGetStackTracer := reflect.ValueOf(err).MethodByName("GetStackTracer")
|
||||
// https://github.com/pkg/errors
|
||||
methodStackTrace := reflect.ValueOf(err).MethodByName("StackTrace")
|
||||
// https://github.com/go-errors/errors
|
||||
methodStackFrames := reflect.ValueOf(err).MethodByName("StackFrames")
|
||||
|
||||
methodGetStackTracer := errValue.MethodByName("GetStackTracer")
|
||||
if methodGetStackTracer.IsValid() {
|
||||
stacktracer := methodGetStackTracer.Call(make([]reflect.Value, 0))[0]
|
||||
stacktracer := methodGetStackTracer.Call(nil)[0]
|
||||
stacktracerStackTrace := reflect.ValueOf(stacktracer).MethodByName("StackTrace")
|
||||
|
||||
if stacktracerStackTrace.IsValid() {
|
||||
method = stacktracerStackTrace
|
||||
return stacktracerStackTrace
|
||||
}
|
||||
}
|
||||
|
||||
if methodStackTrace.IsValid() {
|
||||
method = methodStackTrace
|
||||
}
|
||||
|
||||
if methodStackFrames.IsValid() {
|
||||
method = methodStackFrames
|
||||
}
|
||||
|
||||
return method
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
func extractPcs(method reflect.Value) []uintptr {
|
||||
var pcs []uintptr
|
||||
|
||||
stacktrace := method.Call(make([]reflect.Value, 0))[0]
|
||||
stacktrace := method.Call(nil)[0]
|
||||
|
||||
if stacktrace.Kind() != reflect.Slice {
|
||||
return nil
|
||||
@@ -167,13 +165,7 @@ type Frame struct {
|
||||
Symbol string `json:"symbol,omitempty"`
|
||||
// Module is, despite the name, the Sentry protocol equivalent of a Go
|
||||
// package's import path.
|
||||
Module string `json:"module,omitempty"`
|
||||
// Package is not used for Go stack trace frames. In other platforms it
|
||||
// refers to a container where the Module can be found. For example, a
|
||||
// Java JAR, a .NET Assembly, or a native dynamic library.
|
||||
// It exists for completeness, allowing the construction and reporting
|
||||
// of custom event payloads.
|
||||
Package string `json:"package,omitempty"`
|
||||
Module string `json:"module,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
AbsPath string `json:"abs_path,omitempty"`
|
||||
Lineno int `json:"lineno,omitempty"`
|
||||
@@ -181,40 +173,24 @@ type Frame struct {
|
||||
PreContext []string `json:"pre_context,omitempty"`
|
||||
ContextLine string `json:"context_line,omitempty"`
|
||||
PostContext []string `json:"post_context,omitempty"`
|
||||
InApp bool `json:"in_app,omitempty"`
|
||||
InApp bool `json:"in_app"`
|
||||
Vars map[string]interface{} `json:"vars,omitempty"`
|
||||
// Package and the below are not used for Go stack trace frames. In
|
||||
// other platforms it refers to a container where the Module can be
|
||||
// found. For example, a Java JAR, a .NET Assembly, or a native
|
||||
// dynamic library. They exists for completeness, allowing the
|
||||
// construction and reporting of custom event payloads.
|
||||
Package string `json:"package,omitempty"`
|
||||
InstructionAddr string `json:"instruction_addr,omitempty"`
|
||||
AddrMode string `json:"addr_mode,omitempty"`
|
||||
SymbolAddr string `json:"symbol_addr,omitempty"`
|
||||
ImageAddr string `json:"image_addr,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
StackStart bool `json:"stack_start,omitempty"`
|
||||
}
|
||||
|
||||
// NewFrame assembles a stacktrace frame out of runtime.Frame.
|
||||
func NewFrame(f runtime.Frame) Frame {
|
||||
var abspath, relpath string
|
||||
// NOTE: f.File paths historically use forward slash as path separator even
|
||||
// on Windows, though this is not yet documented, see
|
||||
// https://golang.org/issues/3335. In any case, filepath.IsAbs can work with
|
||||
// paths with either slash or backslash on Windows.
|
||||
switch {
|
||||
case f.File == "":
|
||||
relpath = unknown
|
||||
// Leave abspath as the empty string to be omitted when serializing
|
||||
// event as JSON.
|
||||
abspath = ""
|
||||
case filepath.IsAbs(f.File):
|
||||
abspath = f.File
|
||||
// TODO: in the general case, it is not trivial to come up with a
|
||||
// "project relative" path with the data we have in run time.
|
||||
// We shall not use filepath.Base because it creates ambiguous paths and
|
||||
// affects the "Suspect Commits" feature.
|
||||
// For now, leave relpath empty to be omitted when serializing the event
|
||||
// as JSON. Improve this later.
|
||||
relpath = ""
|
||||
default:
|
||||
// f.File is a relative path. This may happen when the binary is built
|
||||
// with the -trimpath flag.
|
||||
relpath = f.File
|
||||
// Omit abspath when serializing the event as JSON.
|
||||
abspath = ""
|
||||
}
|
||||
|
||||
function := f.Function
|
||||
var pkg string
|
||||
|
||||
@@ -222,15 +198,56 @@ func NewFrame(f runtime.Frame) Frame {
|
||||
pkg, function = splitQualifiedFunctionName(function)
|
||||
}
|
||||
|
||||
return newFrame(pkg, function, f.File, f.Line)
|
||||
}
|
||||
|
||||
// Like filepath.IsAbs() but doesn't care what platform you run this on.
|
||||
// I.e. it also recognizies `/path/to/file` when run on Windows.
|
||||
func isAbsPath(path string) bool {
|
||||
if len(path) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// If the volume name starts with a double slash, this is an absolute path.
|
||||
if len(path) >= 1 && (path[0] == '/' || path[0] == '\\') {
|
||||
return true
|
||||
}
|
||||
|
||||
// Windows absolute path, see https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
|
||||
if len(path) >= 3 && path[1] == ':' && (path[2] == '/' || path[2] == '\\') {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func newFrame(module string, function string, file string, line int) Frame {
|
||||
frame := Frame{
|
||||
AbsPath: abspath,
|
||||
Filename: relpath,
|
||||
Lineno: f.Line,
|
||||
Module: pkg,
|
||||
Lineno: line,
|
||||
Module: module,
|
||||
Function: function,
|
||||
}
|
||||
|
||||
frame.InApp = isInAppFrame(frame)
|
||||
switch {
|
||||
case len(file) == 0:
|
||||
frame.Filename = unknown
|
||||
// Leave abspath as the empty string to be omitted when serializing event as JSON.
|
||||
case isAbsPath(file):
|
||||
frame.AbsPath = file
|
||||
// TODO: in the general case, it is not trivial to come up with a
|
||||
// "project relative" path with the data we have in run time.
|
||||
// We shall not use filepath.Base because it creates ambiguous paths and
|
||||
// affects the "Suspect Commits" feature.
|
||||
// For now, leave relpath empty to be omitted when serializing the event
|
||||
// as JSON. Improve this later.
|
||||
default:
|
||||
// f.File is a relative path. This may happen when the binary is built
|
||||
// with the -trimpath flag.
|
||||
frame.Filename = file
|
||||
// Omit abspath when serializing the event as JSON.
|
||||
}
|
||||
|
||||
setInAppFrame(&frame)
|
||||
|
||||
return frame
|
||||
}
|
||||
@@ -240,63 +257,89 @@ func NewFrame(f runtime.Frame) Frame {
|
||||
// runtime.Frame.Function values.
|
||||
func splitQualifiedFunctionName(name string) (pkg string, fun string) {
|
||||
pkg = packageName(name)
|
||||
fun = strings.TrimPrefix(name, pkg+".")
|
||||
if len(pkg) > 0 {
|
||||
fun = name[len(pkg)+1:]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func extractFrames(pcs []uintptr) []Frame {
|
||||
var frames []Frame
|
||||
func extractFrames(pcs []uintptr) []runtime.Frame {
|
||||
var frames = make([]runtime.Frame, 0, len(pcs))
|
||||
callersFrames := runtime.CallersFrames(pcs)
|
||||
|
||||
for {
|
||||
callerFrame, more := callersFrames.Next()
|
||||
|
||||
frames = append([]Frame{
|
||||
NewFrame(callerFrame),
|
||||
}, frames...)
|
||||
frames = append(frames, callerFrame)
|
||||
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// TODO don't append and reverse, put in the right place from the start.
|
||||
// reverse
|
||||
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
|
||||
frames[i], frames[j] = frames[j], frames[i]
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
||||
// filterFrames filters out stack frames that are not meant to be reported to
|
||||
// Sentry. Those are frames internal to the SDK or Go.
|
||||
func filterFrames(frames []Frame) []Frame {
|
||||
// createFrames creates Frame objects while filtering out frames that are not
|
||||
// meant to be reported to Sentry, those are frames internal to the SDK or Go.
|
||||
func createFrames(frames []runtime.Frame) []Frame {
|
||||
if len(frames) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
filteredFrames := make([]Frame, 0, len(frames))
|
||||
result := make([]Frame, 0, len(frames))
|
||||
|
||||
for _, frame := range frames {
|
||||
// Skip Go internal frames.
|
||||
if frame.Module == "runtime" || frame.Module == "testing" {
|
||||
continue
|
||||
function := frame.Function
|
||||
var pkg string
|
||||
if function != "" {
|
||||
pkg, function = splitQualifiedFunctionName(function)
|
||||
}
|
||||
// Skip Sentry internal frames, except for frames in _test packages (for
|
||||
// testing).
|
||||
if strings.HasPrefix(frame.Module, "github.com/getsentry/sentry-go") &&
|
||||
!strings.HasSuffix(frame.Module, "_test") {
|
||||
continue
|
||||
|
||||
if !shouldSkipFrame(pkg) {
|
||||
result = append(result, newFrame(pkg, function, frame.File, frame.Line))
|
||||
}
|
||||
filteredFrames = append(filteredFrames, frame)
|
||||
}
|
||||
|
||||
return filteredFrames
|
||||
return result
|
||||
}
|
||||
|
||||
func isInAppFrame(frame Frame) bool {
|
||||
if strings.HasPrefix(frame.AbsPath, build.Default.GOROOT) ||
|
||||
strings.Contains(frame.Module, "vendor") ||
|
||||
strings.Contains(frame.Module, "third_party") {
|
||||
return false
|
||||
// TODO ID: why do we want to do this?
|
||||
// I'm not aware of other SDKs skipping all Sentry frames, regardless of their position in the stactrace.
|
||||
// For example, in the .NET SDK, only the first frames are skipped until the call to the SDK.
|
||||
// As is, this will also hide any intermediate frames in the stack and make debugging issues harder.
|
||||
func shouldSkipFrame(module string) bool {
|
||||
// Skip Go internal frames.
|
||||
if module == "runtime" || module == "testing" {
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
// Skip Sentry internal frames, except for frames in _test packages (for testing).
|
||||
if strings.HasPrefix(module, "github.com/getsentry/sentry-go") &&
|
||||
!strings.HasSuffix(module, "_test") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// On Windows, GOROOT has backslashes, but we want forward slashes.
|
||||
var goRoot = strings.ReplaceAll(build.Default.GOROOT, "\\", "/")
|
||||
|
||||
func setInAppFrame(frame *Frame) {
|
||||
if strings.HasPrefix(frame.AbsPath, goRoot) ||
|
||||
strings.Contains(frame.Module, "vendor") ||
|
||||
strings.Contains(frame.Module, "third_party") {
|
||||
frame.InApp = false
|
||||
} else {
|
||||
frame.InApp = true
|
||||
}
|
||||
}
|
||||
|
||||
func callerFunctionName() string {
|
||||
@@ -312,9 +355,7 @@ func callerFunctionName() string {
|
||||
// It replicates https://golang.org/pkg/debug/gosym/#Sym.PackageName, avoiding a
|
||||
// dependency on debug/gosym.
|
||||
func packageName(name string) string {
|
||||
// A prefix of "type." and "go." is a compiler-generated symbol that doesn't belong to any package.
|
||||
// See variable reservedimports in cmd/compile/internal/gc/subr.go
|
||||
if strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.") {
|
||||
if isCompilerGeneratedSymbol(name) {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
15
vendor/github.com/getsentry/sentry-go/stacktrace_below_go1.20.go
generated
vendored
Normal file
15
vendor/github.com/getsentry/sentry-go/stacktrace_below_go1.20.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build !go1.20
|
||||
|
||||
package sentry
|
||||
|
||||
import "strings"
|
||||
|
||||
func isCompilerGeneratedSymbol(name string) bool {
|
||||
// In versions of Go below 1.20 a prefix of "type." and "go." is a
|
||||
// compiler-generated symbol that doesn't belong to any package.
|
||||
// See variable reservedimports in cmd/compile/internal/gc/subr.go
|
||||
if strings.HasPrefix(name, "go.") || strings.HasPrefix(name, "type.") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
15
vendor/github.com/getsentry/sentry-go/stacktrace_go1.20.go
generated
vendored
Normal file
15
vendor/github.com/getsentry/sentry-go/stacktrace_go1.20.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
//go:build go1.20
|
||||
|
||||
package sentry
|
||||
|
||||
import "strings"
|
||||
|
||||
func isCompilerGeneratedSymbol(name string) bool {
|
||||
// In versions of Go 1.20 and above a prefix of "type:" and "go:" is a
|
||||
// compiler-generated symbol that doesn't belong to any package.
|
||||
// See variable reservedimports in cmd/compile/internal/gc/subr.go
|
||||
if strings.HasPrefix(name, "go:") || strings.HasPrefix(name, "type:") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
90
vendor/github.com/getsentry/sentry-go/traces_profiler.go
generated
vendored
Normal file
90
vendor/github.com/getsentry/sentry-go/traces_profiler.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Checks whether the transaction should be profiled (according to ProfilesSampleRate)
|
||||
// and starts a profiler if so.
|
||||
func (span *Span) sampleTransactionProfile() {
|
||||
var sampleRate = span.clientOptions().ProfilesSampleRate
|
||||
switch {
|
||||
case sampleRate < 0.0 || sampleRate > 1.0:
|
||||
Logger.Printf("Skipping transaction profiling: ProfilesSampleRate out of range [0.0, 1.0]: %f\n", sampleRate)
|
||||
case sampleRate == 0.0 || rng.Float64() >= sampleRate:
|
||||
Logger.Printf("Skipping transaction profiling: ProfilesSampleRate is: %f\n", sampleRate)
|
||||
default:
|
||||
startProfilerOnce.Do(startGlobalProfiler)
|
||||
if globalProfiler == nil {
|
||||
Logger.Println("Skipping transaction profiling: the profiler couldn't be started")
|
||||
} else {
|
||||
span.collectProfile = collectTransactionProfile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transactionProfiler collects a profile for a given span.
|
||||
type transactionProfiler func(span *Span) *profileInfo
|
||||
|
||||
var startProfilerOnce sync.Once
|
||||
var globalProfiler profiler
|
||||
|
||||
func startGlobalProfiler() {
|
||||
globalProfiler = startProfiling(time.Now())
|
||||
}
|
||||
|
||||
func collectTransactionProfile(span *Span) *profileInfo {
|
||||
result := globalProfiler.GetSlice(span.StartTime, span.EndTime)
|
||||
if result == nil || result.trace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
info := &profileInfo{
|
||||
Version: "1",
|
||||
EventID: uuid(),
|
||||
// See https://github.com/getsentry/sentry-go/pull/626#discussion_r1204870340 for explanation why we use the Transaction time.
|
||||
Timestamp: span.StartTime,
|
||||
Trace: result.trace,
|
||||
Transaction: profileTransaction{
|
||||
DurationNS: uint64(span.EndTime.Sub(span.StartTime).Nanoseconds()),
|
||||
Name: span.Name,
|
||||
TraceID: span.TraceID.String(),
|
||||
},
|
||||
}
|
||||
if len(info.Transaction.Name) == 0 {
|
||||
// Name is required by Relay so use the operation name if the span name is empty.
|
||||
info.Transaction.Name = span.Op
|
||||
}
|
||||
if result.callerGoID > 0 {
|
||||
info.Transaction.ActiveThreadID = result.callerGoID
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (info *profileInfo) UpdateFromEvent(event *Event) {
|
||||
info.Environment = event.Environment
|
||||
info.Platform = event.Platform
|
||||
info.Release = event.Release
|
||||
info.Dist = event.Dist
|
||||
info.Transaction.ID = event.EventID
|
||||
|
||||
if runtimeContext, ok := event.Contexts["runtime"]; ok {
|
||||
if value, ok := runtimeContext["name"]; !ok {
|
||||
info.Runtime.Name = value.(string)
|
||||
}
|
||||
if value, ok := runtimeContext["version"]; !ok {
|
||||
info.Runtime.Version = value.(string)
|
||||
}
|
||||
}
|
||||
if osContext, ok := event.Contexts["os"]; ok {
|
||||
if value, ok := osContext["name"]; !ok {
|
||||
info.OS.Name = value.(string)
|
||||
}
|
||||
}
|
||||
if deviceContext, ok := event.Contexts["device"]; ok {
|
||||
if value, ok := deviceContext["arch"]; !ok {
|
||||
info.Device.Architecture = value.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
164
vendor/github.com/getsentry/sentry-go/traces_sampler.go
generated
vendored
164
vendor/github.com/getsentry/sentry-go/traces_sampler.go
generated
vendored
@@ -1,171 +1,19 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/getsentry/sentry-go/internal/crypto/randutil"
|
||||
)
|
||||
|
||||
// A TracesSampler makes sampling decisions for spans.
|
||||
//
|
||||
// In addition to the sampling context passed to the Sample method,
|
||||
// implementations may keep and use internal state to make decisions.
|
||||
//
|
||||
// Sampling is one of the last steps when starting a new span, such that the
|
||||
// sampler can inspect most of the state of the span to make a decision.
|
||||
//
|
||||
// Implementations must be safe for concurrent use by multiple goroutines.
|
||||
type TracesSampler interface {
|
||||
Sample(ctx SamplingContext) Sampled
|
||||
}
|
||||
|
||||
// Implementation note:
|
||||
//
|
||||
// TracesSampler.Sample return type is Sampled (instead of bool or float64), so
|
||||
// that we can compose samplers by letting a sampler return SampledUndefined to
|
||||
// defer the decision to the next sampler.
|
||||
//
|
||||
// For example, a hypothetical InheritFromParentSampler would return
|
||||
// SampledUndefined if there is no parent span in the SamplingContext, deferring
|
||||
// the sampling decision to another sampler, like a UniformSampler.
|
||||
//
|
||||
// var _ TracesSampler = sentry.TracesSamplers{
|
||||
// sentry.InheritFromParentSampler,
|
||||
// sentry.UniformTracesSampler(0.1),
|
||||
// }
|
||||
//
|
||||
// Another example, we can provide a sampler that returns SampledFalse if the
|
||||
// SamplingContext matches some condition, and SampledUndefined otherwise:
|
||||
//
|
||||
// var _ TracesSampler = sentry.TracesSamplers{
|
||||
// sentry.IgnoreTransaction(regexp.MustCompile(`^\w+ /(favicon.ico|healthz)`),
|
||||
// sentry.InheritFromParentSampler,
|
||||
// sentry.UniformTracesSampler(0.1),
|
||||
// }
|
||||
//
|
||||
// If after running all samplers the decision is still undefined, the
|
||||
// span/transaction is not sampled.
|
||||
|
||||
// A SamplingContext is passed to a TracesSampler to determine a sampling
|
||||
// decision.
|
||||
//
|
||||
// TODO(tracing): possibly expand SamplingContext to include custom /
|
||||
// user-provided data.
|
||||
type SamplingContext struct {
|
||||
Span *Span // The current span, always non-nil.
|
||||
Parent *Span // The parent span, may be nil.
|
||||
}
|
||||
|
||||
// TODO(tracing): possibly expand SamplingContext to include custom /
|
||||
// user-provided data.
|
||||
//
|
||||
// Unlike in other SDKs, the current http.Request is not part of the
|
||||
// SamplingContext to avoid bloating it with possibly unnecessary values that
|
||||
// could confuse people or have negative performance consequences.
|
||||
//
|
||||
// For the request to be provided in a SamplingContext, a request pointer would
|
||||
// most likely need to be stored in the span context and it would open precedent
|
||||
// for more arbitrary data like fasthttp.Request.
|
||||
//
|
||||
// Users wanting to influence the sampling decision based on the request can
|
||||
// still do so, either by updating the transaction directly on their HTTP
|
||||
// handler:
|
||||
//
|
||||
// func(w http.ResponseWriter, r *http.Request) {
|
||||
// transaction := sentry.TransactionFromContext(r.Context())
|
||||
// if r.Header.Get("X-Custom-Sampling") == "yes" {
|
||||
// transaction.Sampled = sentry.SampledTrue
|
||||
// } else {
|
||||
// transaction.Sampled = sentry.SampledFalse
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Or by having their own middleware that stores arbitrary data in the request
|
||||
// context (a pointer to the request itself included):
|
||||
//
|
||||
// type myContextKey struct{}
|
||||
// type myContextData struct {
|
||||
// request *http.Request
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// func middleware(h http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// data := &myContextData{
|
||||
// request: r,
|
||||
// }
|
||||
// ctx := context.WithValue(r.Context(), myContextKey{}, data)
|
||||
// h.ServeHTTP(w, r.WithContext(ctx))
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// err := sentry.Init(sentry.ClientOptions{
|
||||
// // A custom TracesSampler can access data from the span's context:
|
||||
// TracesSampler: sentry.TracesSamplerFunc(func(ctx sentry.SamplingContext) bool {
|
||||
// data, ok := ctx.Span.Context().Value(myContextKey{}).(*myContextData)
|
||||
// if !ok {
|
||||
// return false
|
||||
// }
|
||||
// return data.request.URL.Hostname() == "example.com"
|
||||
// }),
|
||||
// })
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// Note, however, that for the middleware to be effective, it would have to run
|
||||
// before sentryhttp's own middleware, meaning the middleware itself is not
|
||||
// instrumented to send panics to Sentry and it is not part of the timed
|
||||
// transaction.
|
||||
//
|
||||
// If neither of those prove to be sufficient, we can consider including a
|
||||
// (possibly nil) *http.Request field to SamplingContext. In that case, the SDK
|
||||
// would need to track the request either in the Scope or the Span.Context.
|
||||
//
|
||||
// Alternatively, add a map-like type or simply a generic interface{} similar to
|
||||
// the CustomSamplingContext type in the Java SDK:
|
||||
//
|
||||
// type SamplingContext struct {
|
||||
// Span *Span // The current span, always non-nil.
|
||||
// Parent *Span // The parent span, may be nil.
|
||||
// CustomData interface{}
|
||||
// }
|
||||
//
|
||||
// func CustomSamplingContext(data interface{}) SpanOption {
|
||||
// return func(s *Span) {
|
||||
// s.customSamplingContext = data
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// // ...
|
||||
// span := sentry.StartSpan(ctx, "op", CustomSamplingContext(data))
|
||||
// // ...
|
||||
// }
|
||||
|
||||
// The TracesSamplerFunc type is an adapter to allow the use of ordinary
|
||||
// The TracesSample type is an adapter to allow the use of ordinary
|
||||
// functions as a TracesSampler.
|
||||
type TracesSamplerFunc func(ctx SamplingContext) Sampled
|
||||
type TracesSampler func(ctx SamplingContext) float64
|
||||
|
||||
var _ TracesSampler = TracesSamplerFunc(nil)
|
||||
|
||||
func (f TracesSamplerFunc) Sample(ctx SamplingContext) Sampled {
|
||||
func (f TracesSampler) Sample(ctx SamplingContext) float64 {
|
||||
return f(ctx)
|
||||
}
|
||||
|
||||
// UniformTracesSampler is a TracesSampler that samples root spans randomly at a
|
||||
// uniform rate.
|
||||
type UniformTracesSampler float64
|
||||
|
||||
var _ TracesSampler = UniformTracesSampler(0)
|
||||
|
||||
func (s UniformTracesSampler) Sample(ctx SamplingContext) Sampled {
|
||||
if s < 0.0 || s > 1.0 {
|
||||
panic(fmt.Errorf("sampling rate out of range [0.0, 1.0]: %f", s))
|
||||
}
|
||||
if randutil.Float64() < float64(s) {
|
||||
return SampledTrue
|
||||
}
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
// TODO(tracing): implement and export basic TracesSampler implementations:
|
||||
// parent-based, span ID / trace ID based, etc. It should be possible to compose
|
||||
// parent-based with other samplers.
|
||||
|
||||
592
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
592
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
@@ -9,9 +9,15 @@ import (
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
SentryTraceHeader = "sentry-trace"
|
||||
SentryBaggageHeader = "baggage"
|
||||
)
|
||||
|
||||
// A Span is the building block of a Sentry transaction. Spans build up a tree
|
||||
// structure of timed operations. The span tree makes up a transaction event
|
||||
// that is sent to Sentry when the root span is finished.
|
||||
@@ -21,6 +27,7 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||
TraceID TraceID `json:"trace_id"`
|
||||
SpanID SpanID `json:"span_id"`
|
||||
ParentSpanID SpanID `json:"parent_span_id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Op string `json:"op,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Status SpanStatus `json:"status,omitempty"`
|
||||
@@ -28,24 +35,43 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||
StartTime time.Time `json:"start_timestamp"`
|
||||
EndTime time.Time `json:"timestamp"`
|
||||
Data map[string]interface{} `json:"data,omitempty"`
|
||||
Sampled Sampled `json:"-"`
|
||||
Source TransactionSource `json:"-"`
|
||||
|
||||
Sampled Sampled `json:"-"`
|
||||
|
||||
// mu protects concurrent writes to map fields
|
||||
mu sync.RWMutex
|
||||
// sample rate the span was sampled with.
|
||||
sampleRate float64
|
||||
// ctx is the context where the span was started. Always non-nil.
|
||||
ctx context.Context
|
||||
|
||||
// Dynamic Sampling context
|
||||
dynamicSamplingContext DynamicSamplingContext
|
||||
// parent refers to the immediate local parent span. A remote parent span is
|
||||
// only referenced by setting ParentSpanID.
|
||||
parent *Span
|
||||
|
||||
// isTransaction is true only for the root span of a local span tree. The
|
||||
// root span is the first span started in a context. Note that a local root
|
||||
// span may have a remote parent belonging to the same trace, therefore
|
||||
// isTransaction depends on ctx and not on parent.
|
||||
isTransaction bool
|
||||
|
||||
// recorder stores all spans in a transaction. Guaranteed to be non-nil.
|
||||
recorder *spanRecorder
|
||||
// span context, can only be set on transactions
|
||||
contexts map[string]Context
|
||||
// collectProfile is a function that collects a profile of the current transaction. May be nil.
|
||||
collectProfile transactionProfiler
|
||||
// a Once instance to make sure that Finish() is only called once.
|
||||
finishOnce sync.Once
|
||||
}
|
||||
|
||||
// TraceParentContext describes the context of a (remote) parent span.
|
||||
//
|
||||
// The context is normally extracted from a received "sentry-trace" header and
|
||||
// used to initialize a new transaction.
|
||||
//
|
||||
// Note: the name might be not the best one. It was taken mostly to stay aligned
|
||||
// with other SDKs, and it alludes to W3C "traceparent" header (https://www.w3.org/TR/trace-context/),
|
||||
// which serves a similar purpose to "sentry-trace". We should eventually consider
|
||||
// making this type internal-only and give it a better name.
|
||||
type TraceParentContext struct {
|
||||
TraceID TraceID
|
||||
ParentSpanID SpanID
|
||||
Sampled Sampled
|
||||
}
|
||||
|
||||
// (*) Note on maligned:
|
||||
@@ -81,14 +107,18 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||
// defaults
|
||||
Op: operation,
|
||||
StartTime: time.Now(),
|
||||
Sampled: SampledUndefined,
|
||||
|
||||
ctx: context.WithValue(ctx, spanContextKey{}, &span),
|
||||
parent: parent,
|
||||
isTransaction: !hasParent,
|
||||
ctx: context.WithValue(ctx, spanContextKey{}, &span),
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
if hasParent {
|
||||
span.TraceID = parent.TraceID
|
||||
} else {
|
||||
// Only set the Source if this is a transaction
|
||||
span.Source = SourceCustom
|
||||
|
||||
// Implementation note:
|
||||
//
|
||||
// While math/rand is ~2x faster than crypto/rand (exact
|
||||
@@ -146,38 +176,27 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||
}
|
||||
span.recorder.record(&span)
|
||||
|
||||
hub := hubFromContext(ctx)
|
||||
|
||||
// Update scope so that all events include a trace context, allowing
|
||||
// Sentry to correlate errors to transactions/spans.
|
||||
hubFromContext(ctx).Scope().SetContext("trace", span.traceContext())
|
||||
hub.Scope().SetContext("trace", span.traceContext().Map())
|
||||
|
||||
// Start profiling only if it's a sampled root transaction.
|
||||
if span.IsTransaction() && span.Sampled.Bool() {
|
||||
span.sampleTransactionProfile()
|
||||
}
|
||||
|
||||
return &span
|
||||
}
|
||||
|
||||
// Finish sets the span's end time, unless already set. If the span is the root
|
||||
// of a span tree, Finish sends the span tree to Sentry as a transaction.
|
||||
//
|
||||
// The logic is executed at most once per span, so that (incorrectly) calling it twice
|
||||
// never double sends to Sentry.
|
||||
func (s *Span) Finish() {
|
||||
// TODO(tracing): maybe make Finish run at most once, such that
|
||||
// (incorrectly) calling it twice never double sends to Sentry.
|
||||
|
||||
if s.EndTime.IsZero() {
|
||||
s.EndTime = monotonicTimeSince(s.StartTime)
|
||||
}
|
||||
if !s.Sampled.Bool() {
|
||||
return
|
||||
}
|
||||
event := s.toEvent()
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(tracing): add breadcrumbs
|
||||
// (see https://github.com/getsentry/sentry-python/blob/f6f3525f8812f609/sentry_sdk/tracing.py#L372)
|
||||
|
||||
hub := hubFromContext(s.ctx)
|
||||
if hub.Scope().Transaction() == "" {
|
||||
Logger.Printf("Missing transaction name for span with op = %q", s.Op)
|
||||
}
|
||||
hub.CaptureEvent(event)
|
||||
s.finishOnce.Do(s.doFinish)
|
||||
}
|
||||
|
||||
// Context returns the context containing the span.
|
||||
@@ -195,12 +214,66 @@ func (s *Span) StartChild(operation string, options ...SpanOption) *Span {
|
||||
// accessing the tags map directly as SetTag takes care of initializing the map
|
||||
// when necessary.
|
||||
func (s *Span) SetTag(name, value string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.Tags == nil {
|
||||
s.Tags = make(map[string]string)
|
||||
}
|
||||
s.Tags[name] = value
|
||||
}
|
||||
|
||||
// SetData sets a data on the span. It is recommended to use SetData instead of
|
||||
// accessing the data map directly as SetData takes care of initializing the map
|
||||
// when necessary.
|
||||
func (s *Span) SetData(name, value string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.Data == nil {
|
||||
s.Data = make(map[string]interface{})
|
||||
}
|
||||
s.Data[name] = value
|
||||
}
|
||||
|
||||
// SetContext sets a context on the span. It is recommended to use SetContext instead of
|
||||
// accessing the contexts map directly as SetContext takes care of initializing the map
|
||||
// when necessary.
|
||||
func (s *Span) SetContext(key string, value Context) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.contexts == nil {
|
||||
s.contexts = make(map[string]Context)
|
||||
}
|
||||
s.contexts[key] = value
|
||||
}
|
||||
|
||||
// IsTransaction checks if the given span is a transaction.
|
||||
func (s *Span) IsTransaction() bool {
|
||||
return s.parent == nil
|
||||
}
|
||||
|
||||
// GetTransaction returns the transaction that contains this span.
|
||||
//
|
||||
// For transaction spans it returns itself. For spans that were created manually
|
||||
// the method returns "nil".
|
||||
func (s *Span) GetTransaction() *Span {
|
||||
spanRecorder := s.spanRecorder()
|
||||
if spanRecorder == nil {
|
||||
// This probably means that the Span was created manually (not via
|
||||
// StartTransaction/StartSpan or StartChild).
|
||||
// Return "nil" to indicate that it's not a normal situation.
|
||||
return nil
|
||||
}
|
||||
recorderRoot := spanRecorder.root()
|
||||
if recorderRoot == nil {
|
||||
// Same as above: manually created Span.
|
||||
return nil
|
||||
}
|
||||
return recorderRoot
|
||||
}
|
||||
|
||||
// TODO(tracing): maybe add shortcuts to get/set transaction name. Right now the
|
||||
// transaction name is in the Scope, as it has existed there historically, prior
|
||||
// to tracing.
|
||||
@@ -210,8 +283,9 @@ func (s *Span) SetTag(name, value string) {
|
||||
// func (s *Span) TransactionName() string
|
||||
// func (s *Span) SetTransactionName(name string)
|
||||
|
||||
// ToSentryTrace returns the trace propagation value used with the sentry-trace
|
||||
// HTTP header.
|
||||
// ToSentryTrace returns the seralized TraceParentContext from a transaction/span.
|
||||
// Use this function to propagate the TraceParentContext to a downstream SDK,
|
||||
// either as the value of the "sentry-trace" HTTP header, or as an html "sentry-trace" meta tag.
|
||||
func (s *Span) ToSentryTrace() string {
|
||||
// TODO(tracing): add instrumentation for outgoing HTTP requests using
|
||||
// ToSentryTrace.
|
||||
@@ -226,25 +300,76 @@ func (s *Span) ToSentryTrace() string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ToBaggage returns the serialized DynamicSamplingContext from a transaction.
|
||||
// Use this function to propagate the DynamicSamplingContext to a downstream SDK,
|
||||
// either as the value of the "baggage" HTTP header, or as an html "baggage" meta tag.
|
||||
func (s *Span) ToBaggage() string {
|
||||
if containingTransaction := s.GetTransaction(); containingTransaction != nil {
|
||||
// In case there is currently no frozen DynamicSamplingContext attached to the transaction,
|
||||
// create one from the properties of the transaction.
|
||||
if !s.dynamicSamplingContext.IsFrozen() {
|
||||
// This will return a frozen DynamicSamplingContext.
|
||||
s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(containingTransaction)
|
||||
}
|
||||
|
||||
return containingTransaction.dynamicSamplingContext.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetDynamicSamplingContext sets the given dynamic sampling context on the
|
||||
// current transaction.
|
||||
func (s *Span) SetDynamicSamplingContext(dsc DynamicSamplingContext) {
|
||||
if s.IsTransaction() {
|
||||
s.dynamicSamplingContext = dsc
|
||||
}
|
||||
}
|
||||
|
||||
// doFinish runs the actual Span.Finish() logic.
|
||||
func (s *Span) doFinish() {
|
||||
if s.EndTime.IsZero() {
|
||||
s.EndTime = monotonicTimeSince(s.StartTime)
|
||||
}
|
||||
|
||||
if !s.Sampled.Bool() {
|
||||
return
|
||||
}
|
||||
event := s.toEvent()
|
||||
if event == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s.collectProfile != nil {
|
||||
event.sdkMetaData.transactionProfile = s.collectProfile(s)
|
||||
}
|
||||
|
||||
// TODO(tracing): add breadcrumbs
|
||||
// (see https://github.com/getsentry/sentry-python/blob/f6f3525f8812f609/sentry_sdk/tracing.py#L372)
|
||||
|
||||
hub := hubFromContext(s.ctx)
|
||||
hub.CaptureEvent(event)
|
||||
}
|
||||
|
||||
// sentryTracePattern matches either
|
||||
//
|
||||
// TRACE_ID - SPAN_ID
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}
|
||||
// TRACE_ID - SPAN_ID
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}
|
||||
//
|
||||
// or
|
||||
//
|
||||
// TRACE_ID - SPAN_ID - SAMPLED
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}-[01]
|
||||
// TRACE_ID - SPAN_ID - SAMPLED
|
||||
// [[:xdigit:]]{32}-[[:xdigit:]]{16}-[01]
|
||||
var sentryTracePattern = regexp.MustCompile(`^([[:xdigit:]]{32})-([[:xdigit:]]{16})(?:-([01]))?$`)
|
||||
|
||||
// updateFromSentryTrace parses a sentry-trace HTTP header (as returned by
|
||||
// ToSentryTrace) and updates fields of the span. If the header cannot be
|
||||
// recognized as valid, the span is left unchanged.
|
||||
func (s *Span) updateFromSentryTrace(header []byte) {
|
||||
// recognized as valid, the span is left unchanged. The returned value indicates
|
||||
// whether the span was updated.
|
||||
func (s *Span) updateFromSentryTrace(header []byte) (updated bool) {
|
||||
m := sentryTracePattern.FindSubmatch(header)
|
||||
if m == nil {
|
||||
// no match
|
||||
return
|
||||
return false
|
||||
}
|
||||
_, _ = hex.Decode(s.TraceID[:], m[1])
|
||||
_, _ = hex.Decode(s.ParentSpanID[:], m[2])
|
||||
@@ -256,6 +381,18 @@ func (s *Span) updateFromSentryTrace(header []byte) {
|
||||
s.Sampled = SampledTrue
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Span) updateFromBaggage(header []byte) {
|
||||
if s.IsTransaction() {
|
||||
dsc, err := DynamicSamplingContextFromHeader(header)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.dynamicSamplingContext = dsc
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Span) MarshalJSON() ([]byte, error) {
|
||||
@@ -275,46 +412,107 @@ func (s *Span) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Span) clientOptions() *ClientOptions {
|
||||
client := hubFromContext(s.ctx).Client()
|
||||
if client != nil {
|
||||
return &client.options
|
||||
}
|
||||
return &ClientOptions{}
|
||||
}
|
||||
|
||||
func (s *Span) sample() Sampled {
|
||||
// https://develop.sentry.dev/sdk/unified-api/tracing/#sampling
|
||||
// #1 explicit sampling decision via StartSpan options.
|
||||
clientOptions := s.clientOptions()
|
||||
// https://develop.sentry.dev/sdk/performance/#sampling
|
||||
// #1 tracing is not enabled.
|
||||
if !clientOptions.EnableTracing {
|
||||
Logger.Printf("Dropping transaction: EnableTracing is set to %t", clientOptions.EnableTracing)
|
||||
s.sampleRate = 0.0
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
// #2 explicit sampling decision via StartSpan/StartTransaction options.
|
||||
if s.Sampled != SampledUndefined {
|
||||
Logger.Printf("Using explicit sampling decision from StartSpan/StartTransaction: %v", s.Sampled)
|
||||
switch s.Sampled {
|
||||
case SampledTrue:
|
||||
s.sampleRate = 1.0
|
||||
case SampledFalse:
|
||||
s.sampleRate = 0.0
|
||||
}
|
||||
return s.Sampled
|
||||
}
|
||||
hub := hubFromContext(s.ctx)
|
||||
var clientOptions ClientOptions
|
||||
client := hub.Client()
|
||||
if client != nil {
|
||||
clientOptions = hub.Client().Options()
|
||||
}
|
||||
samplingContext := SamplingContext{Span: s, Parent: s.parent}
|
||||
|
||||
// Variant for non-transaction spans: they inherit the parent decision.
|
||||
// TracesSampler only runs for the root span.
|
||||
// Note: non-transaction should always have a parent, but we check both
|
||||
// conditions anyway -- the first for semantic meaning, the second to
|
||||
// avoid a nil pointer dereference.
|
||||
if !s.isTransaction && s.parent != nil {
|
||||
if !s.IsTransaction() && s.parent != nil {
|
||||
return s.parent.Sampled
|
||||
}
|
||||
// #2 use TracesSampler from ClientOptions.
|
||||
|
||||
// #3 use TracesSampler from ClientOptions.
|
||||
sampler := clientOptions.TracesSampler
|
||||
if sampler != nil {
|
||||
return sampler.Sample(samplingContext)
|
||||
samplingContext := SamplingContext{
|
||||
Span: s,
|
||||
Parent: s.parent,
|
||||
}
|
||||
// #3 inherit parent decision.
|
||||
|
||||
if sampler != nil {
|
||||
tracesSamplerSampleRate := sampler.Sample(samplingContext)
|
||||
s.sampleRate = tracesSamplerSampleRate
|
||||
if tracesSamplerSampleRate < 0.0 || tracesSamplerSampleRate > 1.0 {
|
||||
Logger.Printf("Dropping transaction: Returned TracesSampler rate is out of range [0.0, 1.0]: %f", tracesSamplerSampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
if tracesSamplerSampleRate == 0 {
|
||||
Logger.Printf("Dropping transaction: Returned TracesSampler rate is: %f", tracesSamplerSampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
if rng.Float64() < tracesSamplerSampleRate {
|
||||
return SampledTrue
|
||||
}
|
||||
Logger.Printf("Dropping transaction: TracesSampler returned rate: %f", tracesSamplerSampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
// #4 inherit parent decision.
|
||||
if s.parent != nil {
|
||||
Logger.Printf("Using sampling decision from parent: %v", s.parent.Sampled)
|
||||
switch s.parent.Sampled {
|
||||
case SampledTrue:
|
||||
s.sampleRate = 1.0
|
||||
case SampledFalse:
|
||||
s.sampleRate = 0.0
|
||||
}
|
||||
return s.parent.Sampled
|
||||
}
|
||||
// #4 uniform sampling using TracesSampleRate.
|
||||
sampler = UniformTracesSampler(clientOptions.TracesSampleRate)
|
||||
return sampler.Sample(samplingContext)
|
||||
|
||||
// #5 use TracesSampleRate from ClientOptions.
|
||||
sampleRate := clientOptions.TracesSampleRate
|
||||
s.sampleRate = sampleRate
|
||||
if sampleRate < 0.0 || sampleRate > 1.0 {
|
||||
Logger.Printf("Dropping transaction: TracesSamplerRate out of range [0.0, 1.0]: %f", sampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
if sampleRate == 0.0 {
|
||||
Logger.Printf("Dropping transaction: TracesSampleRate rate is: %f", sampleRate)
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
if rng.Float64() < sampleRate {
|
||||
return SampledTrue
|
||||
}
|
||||
|
||||
return SampledFalse
|
||||
}
|
||||
|
||||
func (s *Span) toEvent() *Event {
|
||||
if !s.isTransaction {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.IsTransaction() {
|
||||
return nil // only transactions can be transformed into events
|
||||
}
|
||||
hub := hubFromContext(s.ctx)
|
||||
|
||||
children := s.recorder.children()
|
||||
finished := make([]*Span, 0, len(children))
|
||||
@@ -326,17 +524,39 @@ func (s *Span) toEvent() *Event {
|
||||
finished = append(finished, child)
|
||||
}
|
||||
|
||||
// Create and attach a DynamicSamplingContext to the transaction.
|
||||
// If the DynamicSamplingContext is not frozen at this point, we can assume being head of trace.
|
||||
if !s.dynamicSamplingContext.IsFrozen() {
|
||||
s.dynamicSamplingContext = DynamicSamplingContextFromTransaction(s)
|
||||
}
|
||||
|
||||
contexts := map[string]Context{}
|
||||
for k, v := range s.contexts {
|
||||
contexts[k] = cloneContext(v)
|
||||
}
|
||||
contexts["trace"] = s.traceContext().Map()
|
||||
|
||||
// Make sure that the transaction source is valid
|
||||
transactionSource := s.Source
|
||||
if !transactionSource.isValid() {
|
||||
transactionSource = SourceCustom
|
||||
}
|
||||
|
||||
return &Event{
|
||||
Type: transactionType,
|
||||
Transaction: hub.Scope().Transaction(),
|
||||
Contexts: map[string]interface{}{
|
||||
"trace": s.traceContext(),
|
||||
Transaction: s.Name,
|
||||
Contexts: contexts,
|
||||
Tags: s.Tags,
|
||||
Extra: s.Data,
|
||||
Timestamp: s.EndTime,
|
||||
StartTime: s.StartTime,
|
||||
Spans: finished,
|
||||
TransactionInfo: &TransactionInfo{
|
||||
Source: transactionSource,
|
||||
},
|
||||
sdkMetaData: SDKMetaData{
|
||||
dsc: s.dynamicSamplingContext,
|
||||
},
|
||||
Tags: s.Tags,
|
||||
Extra: s.Data,
|
||||
Timestamp: s.EndTime,
|
||||
StartTime: s.StartTime,
|
||||
Spans: finished,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,6 +574,23 @@ func (s *Span) traceContext() *TraceContext {
|
||||
// spanRecorder stores the span tree. Guaranteed to be non-nil.
|
||||
func (s *Span) spanRecorder() *spanRecorder { return s.recorder }
|
||||
|
||||
// ParseTraceParentContext parses a sentry-trace header and builds a TraceParentContext from the
|
||||
// parsed values. If the header was parsed correctly, the second returned argument
|
||||
// ("valid") will be set to true, otherwise (e.g., empty or malformed header) it will
|
||||
// be false.
|
||||
func ParseTraceParentContext(header []byte) (traceParentContext TraceParentContext, valid bool) {
|
||||
s := Span{}
|
||||
updated := s.updateFromSentryTrace(header)
|
||||
if !updated {
|
||||
return TraceParentContext{}, false
|
||||
}
|
||||
return TraceParentContext{
|
||||
TraceID: s.TraceID,
|
||||
ParentSpanID: s.ParentSpanID,
|
||||
Sampled: s.Sampled,
|
||||
}, true
|
||||
}
|
||||
|
||||
// TraceID identifies a trace.
|
||||
type TraceID [16]byte
|
||||
|
||||
@@ -394,6 +631,36 @@ var (
|
||||
zeroSpanID SpanID
|
||||
)
|
||||
|
||||
// Contains information about how the name of the transaction was determined.
|
||||
type TransactionSource string
|
||||
|
||||
const (
|
||||
SourceCustom TransactionSource = "custom"
|
||||
SourceURL TransactionSource = "url"
|
||||
SourceRoute TransactionSource = "route"
|
||||
SourceView TransactionSource = "view"
|
||||
SourceComponent TransactionSource = "component"
|
||||
SourceTask TransactionSource = "task"
|
||||
)
|
||||
|
||||
// A set of all valid transaction sources.
|
||||
var allTransactionSources = map[TransactionSource]struct{}{
|
||||
SourceCustom: {},
|
||||
SourceURL: {},
|
||||
SourceRoute: {},
|
||||
SourceView: {},
|
||||
SourceComponent: {},
|
||||
SourceTask: {},
|
||||
}
|
||||
|
||||
// isValid returns 'true' if the given transaction source is a valid
|
||||
// source as recognized by the envelope protocol:
|
||||
// https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
|
||||
func (ts TransactionSource) isValid() bool {
|
||||
_, found := allTransactionSources[ts]
|
||||
return found
|
||||
}
|
||||
|
||||
// SpanStatus is the status of a span.
|
||||
type SpanStatus uint8
|
||||
|
||||
@@ -499,15 +766,40 @@ func (tc *TraceContext) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (tc TraceContext) Map() map[string]interface{} {
|
||||
m := map[string]interface{}{
|
||||
"trace_id": tc.TraceID,
|
||||
"span_id": tc.SpanID,
|
||||
}
|
||||
|
||||
if tc.ParentSpanID != [8]byte{} {
|
||||
m["parent_span_id"] = tc.ParentSpanID
|
||||
}
|
||||
|
||||
if tc.Op != "" {
|
||||
m["op"] = tc.Op
|
||||
}
|
||||
|
||||
if tc.Description != "" {
|
||||
m["description"] = tc.Description
|
||||
}
|
||||
|
||||
if tc.Status > 0 && tc.Status < maxSpanStatus {
|
||||
m["status"] = tc.Status
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Sampled signifies a sampling decision.
|
||||
type Sampled int8
|
||||
|
||||
// The possible trace sampling decisions are: SampledFalse, SampledUndefined
|
||||
// (default) and SampledTrue.
|
||||
const (
|
||||
SampledFalse Sampled = -1 + iota
|
||||
SampledUndefined
|
||||
SampledTrue
|
||||
SampledFalse Sampled = -1
|
||||
SampledUndefined Sampled = 0
|
||||
SampledTrue Sampled = 1
|
||||
)
|
||||
|
||||
func (s Sampled) String() string {
|
||||
@@ -531,23 +823,85 @@ func (s Sampled) Bool() bool {
|
||||
// A SpanOption is a function that can modify the properties of a span.
|
||||
type SpanOption func(s *Span)
|
||||
|
||||
// The TransactionName option sets the name of the current transaction.
|
||||
// WithTransactionName option sets the name of the current transaction.
|
||||
//
|
||||
// A span tree has a single transaction name, therefore using this option when
|
||||
// starting a span affects the span tree as a whole, potentially overwriting a
|
||||
// name set previously.
|
||||
func TransactionName(name string) SpanOption {
|
||||
func WithTransactionName(name string) SpanOption {
|
||||
return func(s *Span) {
|
||||
hubFromContext(s.Context()).Scope().SetTransaction(name)
|
||||
s.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithDescription sets the description of a span.
|
||||
func WithDescription(description string) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Description = description
|
||||
}
|
||||
}
|
||||
|
||||
// WithOpName sets the operation name for a given span.
|
||||
func WithOpName(name string) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Op = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithTransactionSource sets the source of the transaction name.
|
||||
//
|
||||
// Note: if the transaction source is not a valid source (as described
|
||||
// by the spec https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations),
|
||||
// it will be corrected to "custom" eventually, before the transaction is sent.
|
||||
func WithTransactionSource(source TransactionSource) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Source = source
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpanSampled updates the sampling flag for a given span.
|
||||
func WithSpanSampled(sampled Sampled) SpanOption {
|
||||
return func(s *Span) {
|
||||
s.Sampled = sampled
|
||||
}
|
||||
}
|
||||
|
||||
// ContinueFromRequest returns a span option that updates the span to continue
|
||||
// an existing trace. If it cannot detect an existing trace in the request, the
|
||||
// span will be left unchanged.
|
||||
//
|
||||
// ContinueFromRequest is an alias for:
|
||||
//
|
||||
// ContinueFromHeaders(r.Header.Get(SentryTraceHeader), r.Header.Get(SentryBaggageHeader)).
|
||||
func ContinueFromRequest(r *http.Request) SpanOption {
|
||||
return ContinueFromHeaders(r.Header.Get(SentryTraceHeader), r.Header.Get(SentryBaggageHeader))
|
||||
}
|
||||
|
||||
// ContinueFromHeaders returns a span option that updates the span to continue
|
||||
// an existing TraceID and propagates the Dynamic Sampling context.
|
||||
func ContinueFromHeaders(trace, baggage string) SpanOption {
|
||||
return func(s *Span) {
|
||||
if trace != "" {
|
||||
s.updateFromSentryTrace([]byte(trace))
|
||||
}
|
||||
if baggage != "" {
|
||||
s.updateFromBaggage([]byte(baggage))
|
||||
}
|
||||
|
||||
// In case a sentry-trace header is present but there are no sentry-related
|
||||
// values in the baggage, create an empty, frozen DynamicSamplingContext.
|
||||
if trace != "" && !s.dynamicSamplingContext.HasEntries() {
|
||||
s.dynamicSamplingContext = DynamicSamplingContext{
|
||||
Frozen: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ContinueFromTrace returns a span option that updates the span to continue
|
||||
// an existing TraceID.
|
||||
func ContinueFromTrace(trace string) SpanOption {
|
||||
return func(s *Span) {
|
||||
trace := r.Header.Get("sentry-trace")
|
||||
if trace == "" {
|
||||
return
|
||||
}
|
||||
@@ -567,29 +921,65 @@ func TransactionFromContext(ctx context.Context) *Span {
|
||||
return nil
|
||||
}
|
||||
|
||||
// spanFromContext returns the last span stored in the context or a dummy
|
||||
// non-nil span.
|
||||
//
|
||||
// TODO(tracing): consider exporting this. Without this, users cannot retrieve a
|
||||
// span from a context since spanContextKey is not exported.
|
||||
//
|
||||
// This can be added retroactively, and in the meantime think better whether it
|
||||
// should return nil (like GetHubFromContext), always non-nil (like
|
||||
// HubFromContext), or both: two exported functions.
|
||||
//
|
||||
// Note the equivalence:
|
||||
//
|
||||
// SpanFromContext(ctx).StartChild(...) === StartSpan(ctx, ...)
|
||||
//
|
||||
// So we don't aim spanFromContext at creating spans, but mutating existing
|
||||
// spans that you'd have no access otherwise (because it was created in code you
|
||||
// do not control, for example SDK auto-instrumentation).
|
||||
//
|
||||
// For now we provide TransactionFromContext, which solves the more common case
|
||||
// of setting tags, etc, on the current transaction.
|
||||
func spanFromContext(ctx context.Context) *Span {
|
||||
// SpanFromContext returns the last span stored in the context, or nil if no span
|
||||
// is set on the context.
|
||||
func SpanFromContext(ctx context.Context) *Span {
|
||||
if span, ok := ctx.Value(spanContextKey{}).(*Span); ok {
|
||||
return span
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartTransaction will create a transaction (root span) if there's no existing
|
||||
// transaction in the context otherwise, it will return the existing transaction.
|
||||
func StartTransaction(ctx context.Context, name string, options ...SpanOption) *Span {
|
||||
currentTransaction, exists := ctx.Value(spanContextKey{}).(*Span)
|
||||
if exists {
|
||||
return currentTransaction
|
||||
}
|
||||
|
||||
options = append(options, WithTransactionName(name))
|
||||
return StartSpan(
|
||||
ctx,
|
||||
"",
|
||||
options...,
|
||||
)
|
||||
}
|
||||
|
||||
// HTTPtoSpanStatus converts an HTTP status code to a SpanStatus.
|
||||
func HTTPtoSpanStatus(code int) SpanStatus {
|
||||
if code < http.StatusBadRequest {
|
||||
return SpanStatusOK
|
||||
}
|
||||
if http.StatusBadRequest <= code && code < http.StatusInternalServerError {
|
||||
switch code {
|
||||
case http.StatusForbidden:
|
||||
return SpanStatusPermissionDenied
|
||||
case http.StatusNotFound:
|
||||
return SpanStatusNotFound
|
||||
case http.StatusTooManyRequests:
|
||||
return SpanStatusResourceExhausted
|
||||
case http.StatusRequestEntityTooLarge:
|
||||
return SpanStatusFailedPrecondition
|
||||
case http.StatusUnauthorized:
|
||||
return SpanStatusUnauthenticated
|
||||
case http.StatusConflict:
|
||||
return SpanStatusAlreadyExists
|
||||
default:
|
||||
return SpanStatusInvalidArgument
|
||||
}
|
||||
}
|
||||
if http.StatusInternalServerError <= code && code < 600 {
|
||||
switch code {
|
||||
case http.StatusGatewayTimeout:
|
||||
return SpanStatusDeadlineExceeded
|
||||
case http.StatusNotImplemented:
|
||||
return SpanStatusUnimplemented
|
||||
case http.StatusServiceUnavailable:
|
||||
return SpanStatusUnavailable
|
||||
default:
|
||||
return SpanStatusInternalError
|
||||
}
|
||||
}
|
||||
return SpanStatusUnknown
|
||||
}
|
||||
|
||||
170
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
170
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
@@ -7,7 +7,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
@@ -39,11 +38,13 @@ type Transport interface {
|
||||
|
||||
func getProxyConfig(options ClientOptions) func(*http.Request) (*url.URL, error) {
|
||||
if options.HTTPSProxy != "" {
|
||||
return func(_ *http.Request) (*url.URL, error) {
|
||||
return func(*http.Request) (*url.URL, error) {
|
||||
return url.Parse(options.HTTPSProxy)
|
||||
}
|
||||
} else if options.HTTPProxy != "" {
|
||||
return func(_ *http.Request) (*url.URL, error) {
|
||||
}
|
||||
|
||||
if options.HTTPProxy != "" {
|
||||
return func(*http.Request) (*url.URL, error) {
|
||||
return url.Parse(options.HTTPProxy)
|
||||
}
|
||||
}
|
||||
@@ -53,7 +54,7 @@ func getProxyConfig(options ClientOptions) func(*http.Request) (*url.URL, error)
|
||||
|
||||
func getTLSConfig(options ClientOptions) *tls.Config {
|
||||
if options.CaCerts != nil {
|
||||
//#nosec G402 -- We should be using `MinVersion: tls.VersionTLS12`,
|
||||
// #nosec G402 -- We should be using `MinVersion: tls.VersionTLS12`,
|
||||
// but we don't want to break peoples code without the major bump.
|
||||
return &tls.Config{
|
||||
RootCAs: options.CaCerts,
|
||||
@@ -93,64 +94,149 @@ func getRequestBodyFromEvent(event *Event) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func transactionEnvelopeFromBody(eventID EventID, sentAt time.Time, body json.RawMessage) (*bytes.Buffer, error) {
|
||||
var b bytes.Buffer
|
||||
enc := json.NewEncoder(&b)
|
||||
// envelope header
|
||||
func encodeAttachment(enc *json.Encoder, b io.Writer, attachment *Attachment) error {
|
||||
// Attachment header
|
||||
err := enc.Encode(struct {
|
||||
EventID EventID `json:"event_id"`
|
||||
SentAt time.Time `json:"sent_at"`
|
||||
Type string `json:"type"`
|
||||
Length int `json:"length"`
|
||||
Filename string `json:"filename"`
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
}{
|
||||
EventID: eventID,
|
||||
SentAt: sentAt,
|
||||
Type: "attachment",
|
||||
Length: len(attachment.Payload),
|
||||
Filename: attachment.Filename,
|
||||
ContentType: attachment.ContentType,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
// item header
|
||||
err = enc.Encode(struct {
|
||||
|
||||
// Attachment payload
|
||||
if _, err = b.Write(attachment.Payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// "Envelopes should be terminated with a trailing newline."
|
||||
//
|
||||
// [1]: https://develop.sentry.dev/sdk/envelopes/#envelopes
|
||||
if _, err := b.Write([]byte("\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeEnvelopeItem(enc *json.Encoder, itemType string, body json.RawMessage) error {
|
||||
// Item header
|
||||
err := enc.Encode(struct {
|
||||
Type string `json:"type"`
|
||||
Length int `json:"length"`
|
||||
}{
|
||||
Type: transactionType,
|
||||
Type: itemType,
|
||||
Length: len(body),
|
||||
})
|
||||
if err == nil {
|
||||
// payload
|
||||
err = enc.Encode(body)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMessage) (*bytes.Buffer, error) {
|
||||
var b bytes.Buffer
|
||||
enc := json.NewEncoder(&b)
|
||||
|
||||
// Construct the trace envelope header
|
||||
var trace = map[string]string{}
|
||||
if dsc := event.sdkMetaData.dsc; dsc.HasEntries() {
|
||||
for k, v := range dsc.Entries {
|
||||
trace[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Envelope header
|
||||
err := enc.Encode(struct {
|
||||
EventID EventID `json:"event_id"`
|
||||
SentAt time.Time `json:"sent_at"`
|
||||
Dsn string `json:"dsn"`
|
||||
Sdk map[string]string `json:"sdk"`
|
||||
Trace map[string]string `json:"trace,omitempty"`
|
||||
}{
|
||||
EventID: event.EventID,
|
||||
SentAt: sentAt,
|
||||
Trace: trace,
|
||||
Dsn: dsn.String(),
|
||||
Sdk: map[string]string{
|
||||
"name": event.Sdk.Name,
|
||||
"version": event.Sdk.Version,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// payload
|
||||
err = enc.Encode(body)
|
||||
|
||||
if event.Type == transactionType || event.Type == checkInType {
|
||||
err = encodeEnvelopeItem(enc, event.Type, body)
|
||||
} else {
|
||||
err = encodeEnvelopeItem(enc, eventType, body)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Attachments
|
||||
for _, attachment := range event.Attachments {
|
||||
if err := encodeAttachment(enc, &b, attachment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Profile data
|
||||
if event.sdkMetaData.transactionProfile != nil {
|
||||
body, err = json.Marshal(event.sdkMetaData.transactionProfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = encodeEnvelopeItem(enc, profileType, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
func getRequestFromEvent(event *Event, dsn *Dsn) (r *http.Request, err error) {
|
||||
defer func() {
|
||||
if r != nil {
|
||||
r.Header.Set("User-Agent", userAgent)
|
||||
r.Header.Set("User-Agent", fmt.Sprintf("%s/%s", event.Sdk.Name, event.Sdk.Version))
|
||||
r.Header.Set("Content-Type", "application/x-sentry-envelope")
|
||||
|
||||
auth := fmt.Sprintf("Sentry sentry_version=%s, "+
|
||||
"sentry_client=%s/%s, sentry_key=%s", apiVersion, event.Sdk.Name, event.Sdk.Version, dsn.publicKey)
|
||||
|
||||
// The key sentry_secret is effectively deprecated and no longer needs to be set.
|
||||
// However, since it was required in older self-hosted versions,
|
||||
// it should still passed through to Sentry if set.
|
||||
if dsn.secretKey != "" {
|
||||
auth = fmt.Sprintf("%s, sentry_secret=%s", auth, dsn.secretKey)
|
||||
}
|
||||
|
||||
r.Header.Set("X-Sentry-Auth", auth)
|
||||
}
|
||||
}()
|
||||
body := getRequestBodyFromEvent(event)
|
||||
if body == nil {
|
||||
return nil, errors.New("event could not be marshaled")
|
||||
}
|
||||
if event.Type == transactionType {
|
||||
b, err := transactionEnvelopeFromBody(event.EventID, time.Now(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return http.NewRequest(
|
||||
http.MethodPost,
|
||||
dsn.EnvelopeAPIURL().String(),
|
||||
b,
|
||||
)
|
||||
envelope, err := envelopeFromBody(event, dsn, time.Now(), body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return http.NewRequest(
|
||||
http.MethodPost,
|
||||
dsn.StoreAPIURL().String(),
|
||||
bytes.NewReader(body),
|
||||
dsn.GetAPIURL().String(),
|
||||
envelope,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -275,10 +361,6 @@ func (t *HTTPTransport) SendEvent(event *Event) {
|
||||
return
|
||||
}
|
||||
|
||||
for headerKey, headerValue := range t.dsn.RequestHeaders() {
|
||||
request.Header.Set(headerKey, headerValue)
|
||||
}
|
||||
|
||||
// <-t.buffer is equivalent to acquiring a lock to access the current batch.
|
||||
// A few lines below, t.buffer <- b releases the lock.
|
||||
//
|
||||
@@ -401,7 +483,7 @@ func (t *HTTPTransport) worker() {
|
||||
t.mu.Unlock()
|
||||
// Drain body up to a limit and close it, allowing the
|
||||
// transport to reuse TCP connections.
|
||||
_, _ = io.CopyN(ioutil.Discard, response.Body, maxDrainResponseBytes)
|
||||
_, _ = io.CopyN(io.Discard, response.Body, maxDrainResponseBytes)
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
@@ -500,10 +582,6 @@ func (t *HTTPSyncTransport) SendEvent(event *Event) {
|
||||
return
|
||||
}
|
||||
|
||||
for headerKey, headerValue := range t.dsn.RequestHeaders() {
|
||||
request.Header.Set(headerKey, headerValue)
|
||||
}
|
||||
|
||||
var eventType string
|
||||
if event.Type == transactionType {
|
||||
eventType = "transaction"
|
||||
@@ -529,7 +607,7 @@ func (t *HTTPSyncTransport) SendEvent(event *Event) {
|
||||
|
||||
// Drain body up to a limit and close it, allowing the
|
||||
// transport to reuse TCP connections.
|
||||
_, _ = io.CopyN(ioutil.Discard, response.Body, maxDrainResponseBytes)
|
||||
_, _ = io.CopyN(io.Discard, response.Body, maxDrainResponseBytes)
|
||||
response.Body.Close()
|
||||
}
|
||||
|
||||
@@ -556,14 +634,16 @@ func (t *HTTPSyncTransport) disabled(c ratelimit.Category) bool {
|
||||
// Only used internally when an empty DSN is provided, which effectively disables the SDK.
|
||||
type noopTransport struct{}
|
||||
|
||||
func (t *noopTransport) Configure(options ClientOptions) {
|
||||
var _ Transport = noopTransport{}
|
||||
|
||||
func (noopTransport) Configure(ClientOptions) {
|
||||
Logger.Println("Sentry client initialized with an empty DSN. Using noopTransport. No events will be delivered.")
|
||||
}
|
||||
|
||||
func (t *noopTransport) SendEvent(event *Event) {
|
||||
func (noopTransport) SendEvent(*Event) {
|
||||
Logger.Println("Event dropped due to noopTransport usage.")
|
||||
}
|
||||
|
||||
func (t *noopTransport) Flush(_ time.Duration) bool {
|
||||
func (noopTransport) Flush(time.Duration) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
57
vendor/github.com/getsentry/sentry-go/util.go
generated
vendored
57
vendor/github.com/getsentry/sentry-go/util.go
generated
vendored
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -35,7 +36,7 @@ func monotonicTimeSince(start time.Time) (end time.Time) {
|
||||
return start.Add(time.Since(start))
|
||||
}
|
||||
|
||||
//nolint: deadcode, unused
|
||||
// nolint: deadcode, unused
|
||||
func prettyPrint(data interface{}) {
|
||||
dbg, _ := json.MarshalIndent(data, "", " ")
|
||||
fmt.Println(string(dbg))
|
||||
@@ -66,26 +67,48 @@ func defaultRelease() (release string) {
|
||||
}
|
||||
}
|
||||
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
buildInfoVcsRevision := revisionFromBuildInfo(info)
|
||||
if len(buildInfoVcsRevision) > 0 {
|
||||
return buildInfoVcsRevision
|
||||
}
|
||||
}
|
||||
|
||||
// Derive a version string from Git. Example outputs:
|
||||
// v1.0.1-0-g9de4
|
||||
// v2.0-8-g77df-dirty
|
||||
// 4f72d7
|
||||
cmd := exec.Command("git", "describe", "--long", "--always", "--dirty")
|
||||
b, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Either Git is not available or the current directory is not a
|
||||
// Git repository.
|
||||
var s strings.Builder
|
||||
fmt.Fprintf(&s, "Release detection failed: %v", err)
|
||||
if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 {
|
||||
fmt.Fprintf(&s, ": %s", err.Stderr)
|
||||
if _, err := exec.LookPath("git"); err == nil {
|
||||
cmd := exec.Command("git", "describe", "--long", "--always", "--dirty")
|
||||
b, err := cmd.Output()
|
||||
if err != nil {
|
||||
// Either Git is not available or the current directory is not a
|
||||
// Git repository.
|
||||
var s strings.Builder
|
||||
fmt.Fprintf(&s, "Release detection failed: %v", err)
|
||||
if err, ok := err.(*exec.ExitError); ok && len(err.Stderr) > 0 {
|
||||
fmt.Fprintf(&s, ": %s", err.Stderr)
|
||||
}
|
||||
Logger.Print(s.String())
|
||||
} else {
|
||||
release = strings.TrimSpace(string(b))
|
||||
Logger.Printf("Using release from Git: %s", release)
|
||||
return release
|
||||
}
|
||||
Logger.Print(s.String())
|
||||
Logger.Print("Some Sentry features will not be available. See https://docs.sentry.io/product/releases/.")
|
||||
Logger.Print("To stop seeing this message, pass a Release to sentry.Init or set the SENTRY_RELEASE environment variable.")
|
||||
return ""
|
||||
}
|
||||
release = strings.TrimSpace(string(b))
|
||||
Logger.Printf("Using release from Git: %s", release)
|
||||
return release
|
||||
|
||||
Logger.Print("Some Sentry features will not be available. See https://docs.sentry.io/product/releases/.")
|
||||
Logger.Print("To stop seeing this message, pass a Release to sentry.Init or set the SENTRY_RELEASE environment variable.")
|
||||
return ""
|
||||
}
|
||||
|
||||
func revisionFromBuildInfo(info *debug.BuildInfo) string {
|
||||
for _, setting := range info.Settings {
|
||||
if setting.Key == "vcs.revision" && setting.Value != "" {
|
||||
Logger.Printf("Using release from debug info: %s", setting.Value)
|
||||
return setting.Value
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user