Upgrade versions of vendored libs
This commit is contained in:
4
vendor/github.com/getsentry/sentry-go/.golangci.yml
generated
vendored
4
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,12 +22,10 @@ linters:
|
||||
- prealloc
|
||||
- revive
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- varcheck
|
||||
- whitespace
|
||||
issues:
|
||||
exclude-rules:
|
||||
|
||||
221
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
221
vendor/github.com/getsentry/sentry-go/CHANGELOG.md
generated
vendored
@@ -1,5 +1,226 @@
|
||||
# Changelog
|
||||
|
||||
## 0.28.1
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.28.1.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Implement `http.ResponseWriter` to hook into various parts of the response process ([#837](https://github.com/getsentry/sentry-go/pull/837))
|
||||
|
||||
## 0.28.0
|
||||
|
||||
The Sentry SDK team is happy to announce the immediate availability of Sentry Go SDK v0.28.0.
|
||||
|
||||
### Features
|
||||
|
||||
- Add a `Fiber` performance tracing & error reporting integration ([#795](https://github.com/getsentry/sentry-go/pull/795))
|
||||
- Add performance tracing to the `Echo` integration ([#722](https://github.com/getsentry/sentry-go/pull/722))
|
||||
- Add performance tracing to the `FastHTTP` integration ([#732](https://github.com/getsentry/sentry-go/pull/723))
|
||||
- Add performance tracing to the `Iris` integration ([#809](https://github.com/getsentry/sentry-go/pull/809))
|
||||
- Add performance tracing to the `Negroni` integration ([#808](https://github.com/getsentry/sentry-go/pull/808))
|
||||
- Add `FailureIssueThreshold` & `RecoveryThreshold` to `MonitorConfig` ([#775](https://github.com/getsentry/sentry-go/pull/775))
|
||||
- Use `errors.Unwrap()` to create exception groups ([#792](https://github.com/getsentry/sentry-go/pull/792))
|
||||
- Add support for matching on strings for `ClientOptions.IgnoreErrors` & `ClientOptions.IgnoreTransactions` ([#819](https://github.com/getsentry/sentry-go/pull/819))
|
||||
- Add `http.request.method` attribute for performance span data ([#786](https://github.com/getsentry/sentry-go/pull/786))
|
||||
- Accept `interface{}` for span data values ([#784](https://github.com/getsentry/sentry-go/pull/784))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Fix missing stack trace for parsing error in `logrusentry` ([#689](https://github.com/getsentry/sentry-go/pull/689))
|
||||
|
||||
## 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.
|
||||
|
||||
2
vendor/github.com/getsentry/sentry-go/Makefile
generated
vendored
2
vendor/github.com/getsentry/sentry-go/Makefile
generated
vendored
@@ -49,7 +49,7 @@ test-coverage: $(COVERAGE_REPORT_DIR) clean-report-dir ## Test with coverage en
|
||||
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 -coverpkg=./... -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_PROFILE)" ./... && \
|
||||
$(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;
|
||||
|
||||
2
vendor/github.com/getsentry/sentry-go/README.md
generated
vendored
2
vendor/github.com/getsentry/sentry-go/README.md
generated
vendored
@@ -13,7 +13,6 @@
|
||||
[](https://github.com/getsentry/sentry-go/actions?query=workflow%3Ago-workflow)
|
||||
[](https://goreportcard.com/report/github.com/getsentry/sentry-go)
|
||||
[](https://discord.gg/Ww9hbqr)
|
||||
[](https://godoc.org/github.com/getsentry/sentry-go)
|
||||
[](https://pkg.go.dev/github.com/getsentry/sentry-go)
|
||||
|
||||
`sentry-go` provides a Sentry client implementation for the Go programming
|
||||
@@ -85,7 +84,6 @@ checkout the official documentation:
|
||||
|
||||
- [Bug Tracker](https://github.com/getsentry/sentry-go/issues)
|
||||
- [GitHub Project](https://github.com/getsentry/sentry-go)
|
||||
- [](https://godoc.org/github.com/getsentry/sentry-go)
|
||||
- [](https://pkg.go.dev/github.com/getsentry/sentry-go)
|
||||
- [](https://docs.sentry.io/platforms/go/)
|
||||
- [](https://github.com/getsentry/sentry-go/discussions)
|
||||
|
||||
121
vendor/github.com/getsentry/sentry-go/check_in.go
generated
vendored
Normal file
121
vendor/github.com/getsentry/sentry-go/check_in.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
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"`
|
||||
// The number of consecutive failed check-ins it takes before an issue is created.
|
||||
FailureIssueThreshold int64 `json:"failure_issue_threshold,omitempty"`
|
||||
// The number of consecutive OK check-ins it takes before an issue is resolved.
|
||||
RecoveryThreshold int64 `json:"recovery_threshold,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"`
|
||||
}
|
||||
157
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
157
vendor/github.com/getsentry/sentry-go/client.go
generated
vendored
@@ -17,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.
|
||||
//
|
||||
@@ -130,10 +133,16 @@ type ClientOptions struct {
|
||||
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
|
||||
@@ -215,15 +224,20 @@ type ClientOptions struct {
|
||||
// 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
|
||||
@@ -237,6 +251,28 @@ type Client struct {
|
||||
// single goroutine) or hub methods (for concurrent programs, for example web
|
||||
// servers).
|
||||
func NewClient(options ClientOptions) (*Client, error) {
|
||||
// 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 {
|
||||
debugWriter := options.DebugWriter
|
||||
if debugWriter == nil {
|
||||
@@ -298,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()
|
||||
@@ -338,6 +376,8 @@ func (client *Client) setupIntegrations() {
|
||||
new(environmentIntegration),
|
||||
new(modulesIntegration),
|
||||
new(ignoreErrorsIntegration),
|
||||
new(ignoreTransactionsIntegration),
|
||||
new(globalTagsIntegration),
|
||||
}
|
||||
|
||||
if client.options.Integrations != nil {
|
||||
@@ -371,7 +411,8 @@ 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
|
||||
}
|
||||
|
||||
@@ -387,6 +428,16 @@ func (client *Client) CaptureException(exception error, hint *EventHint, scope E
|
||||
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
|
||||
@@ -472,7 +523,7 @@ func (client *Client) EventFromMessage(message string, level Level) *Event {
|
||||
event.Level = level
|
||||
event.Message = message
|
||||
|
||||
if client.Options().AttachStacktrace {
|
||||
if client.options.AttachStacktrace {
|
||||
event.Threads = []Thread{{
|
||||
Stacktrace: NewStacktrace(),
|
||||
Crashed: false,
|
||||
@@ -498,6 +549,47 @@ func (client *Client) EventFromException(exception error, level Level) *Event {
|
||||
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-- {
|
||||
@@ -512,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
|
||||
}
|
||||
@@ -552,15 +620,15 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
|
||||
if hint == nil {
|
||||
hint = &EventHint{}
|
||||
}
|
||||
if event.Type == transactionType && options.BeforeSendTransaction != nil {
|
||||
if event.Type == transactionType && client.options.BeforeSendTransaction != nil {
|
||||
// Transaction events
|
||||
if event = options.BeforeSendTransaction(event, hint); event == nil {
|
||||
if event = client.options.BeforeSendTransaction(event, hint); event == nil {
|
||||
Logger.Println("Transaction dropped due to BeforeSendTransaction callback.")
|
||||
return nil
|
||||
}
|
||||
} else if event.Type != transactionType && options.BeforeSend != nil {
|
||||
} else if event.Type != transactionType && event.Type != checkInType && client.options.BeforeSend != nil {
|
||||
// All other events
|
||||
if event = options.BeforeSend(event, hint); event == nil {
|
||||
if event = client.options.BeforeSend(event, hint); event == nil {
|
||||
Logger.Println("Event dropped due to BeforeSend callback.")
|
||||
return nil
|
||||
}
|
||||
@@ -573,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())
|
||||
}
|
||||
|
||||
@@ -585,7 +654,7 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
|
||||
}
|
||||
|
||||
if event.ServerName == "" {
|
||||
event.ServerName = client.Options().ServerName
|
||||
event.ServerName = client.options.ServerName
|
||||
|
||||
if event.ServerName == "" {
|
||||
event.ServerName = hostname
|
||||
@@ -593,20 +662,20 @@ func (client *Client) prepareEvent(event *Event, hint *EventHint, scope EventMod
|
||||
}
|
||||
|
||||
if event.Release == "" {
|
||||
event.Release = client.Options().Release
|
||||
event.Release = client.options.Release
|
||||
}
|
||||
|
||||
if event.Dist == "" {
|
||||
event.Dist = client.Options().Dist
|
||||
event.Dist = client.options.Dist
|
||||
}
|
||||
|
||||
if event.Environment == "" {
|
||||
event.Environment = client.Options().Environment
|
||||
event.Environment = client.options.Environment
|
||||
}
|
||||
|
||||
event.Platform = "go"
|
||||
event.Sdk = SdkInfo{
|
||||
Name: SDKIdentifier,
|
||||
Name: client.GetSDKIdentifier(),
|
||||
Version: SDKVersion,
|
||||
Integrations: client.listIntegrations(),
|
||||
Packages: []SdkPackage{{
|
||||
@@ -640,10 +709,14 @@ 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 {
|
||||
func (client *Client) listIntegrations() []string {
|
||||
integrations := make([]string, len(client.integrations))
|
||||
for i, integration := range client.integrations {
|
||||
integrations[i] = integration.Name()
|
||||
@@ -651,7 +724,7 @@ func (client Client) listIntegrations() []string {
|
||||
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
|
||||
|
||||
12
vendor/github.com/getsentry/sentry-go/dsn.go
generated
vendored
12
vendor/github.com/getsentry/sentry-go/dsn.go
generated
vendored
@@ -90,11 +90,10 @@ func NewDsn(rawURL string) (*Dsn, error) {
|
||||
// Port
|
||||
var port int
|
||||
if parsedURL.Port() != "" {
|
||||
parsedPort, err := strconv.Atoi(parsedURL.Port())
|
||||
port, err = strconv.Atoi(parsedURL.Port())
|
||||
if err != nil {
|
||||
return nil, &DsnParseError{"invalid port"}
|
||||
}
|
||||
port = parsedPort
|
||||
} else {
|
||||
port = scheme.defaultPort()
|
||||
}
|
||||
@@ -196,10 +195,15 @@ func (dsn Dsn) GetAPIURL() *url.URL {
|
||||
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)
|
||||
|
||||
12
vendor/github.com/getsentry/sentry-go/dynamic_sampling_context.go
generated
vendored
12
vendor/github.com/getsentry/sentry-go/dynamic_sampling_context.go
generated
vendored
@@ -52,8 +52,6 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext {
|
||||
}
|
||||
}
|
||||
|
||||
options := client.Options()
|
||||
|
||||
if traceID := span.TraceID.String(); traceID != "" {
|
||||
entries["trace_id"] = traceID
|
||||
}
|
||||
@@ -66,10 +64,10 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext {
|
||||
entries["public_key"] = publicKey
|
||||
}
|
||||
}
|
||||
if release := options.Release; release != "" {
|
||||
if release := client.options.Release; release != "" {
|
||||
entries["release"] = release
|
||||
}
|
||||
if environment := options.Environment; environment != "" {
|
||||
if environment := client.options.Environment; environment != "" {
|
||||
entries["environment"] = environment
|
||||
}
|
||||
|
||||
@@ -84,6 +82,12 @@ func DynamicSamplingContextFromTransaction(span *Span) DynamicSamplingContext {
|
||||
entries["user_segment"] = userSegment
|
||||
}
|
||||
|
||||
if span.Sampled.Bool() {
|
||||
entries["sampled"] = "true"
|
||||
} else {
|
||||
entries["sampled"] = "false"
|
||||
}
|
||||
|
||||
return DynamicSamplingContext{
|
||||
Entries: entries,
|
||||
Frozen: true,
|
||||
|
||||
19
vendor/github.com/getsentry/sentry-go/hub.go
generated
vendored
19
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,17 +292,16 @@ func (hub *Hub) AddBreadcrumb(breadcrumb *Breadcrumb, hint *BreadcrumbHint) {
|
||||
return
|
||||
}
|
||||
|
||||
options := client.Options()
|
||||
max := options.MaxBreadcrumbs
|
||||
max := client.options.MaxBreadcrumbs
|
||||
if max < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if options.BeforeBreadcrumb != nil {
|
||||
if client.options.BeforeBreadcrumb != nil {
|
||||
if hint == nil {
|
||||
hint = &BreadcrumbHint{}
|
||||
}
|
||||
if breadcrumb = options.BeforeBreadcrumb(breadcrumb, hint); breadcrumb == nil {
|
||||
if breadcrumb = client.options.BeforeBreadcrumb(breadcrumb, hint); breadcrumb == nil {
|
||||
Logger.Println("breadcrumb dropped due to BeforeBreadcrumb callback.")
|
||||
return
|
||||
}
|
||||
|
||||
110
vendor/github.com/getsentry/sentry-go/integrations.go
generated
vendored
110
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,7 +70,7 @@ 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 {
|
||||
@@ -130,16 +131,16 @@ 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 {
|
||||
for _, pattern := range iei.ignoreErrors {
|
||||
if pattern.Match([]byte(suspect)) {
|
||||
if pattern.Match([]byte(suspect)) || strings.Contains(suspect, pattern.String()) {
|
||||
Logger.Printf("Event dropped due to being matched by `IgnoreErrors` option."+
|
||||
"| Value matched: %s | Filter used: %s", suspect, pattern)
|
||||
return nil
|
||||
@@ -177,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)) || strings.Contains(suspect, pattern.String()) {
|
||||
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
|
||||
// ================================
|
||||
@@ -198,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
|
||||
@@ -291,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
|
||||
}
|
||||
|
||||
150
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
150
vendor/github.com/getsentry/sentry-go/interfaces.go
generated
vendored
@@ -3,6 +3,7 @@ package sentry
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -11,14 +12,21 @@ import (
|
||||
"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"
|
||||
|
||||
// eventType is the type of an error event.
|
||||
const eventType = "event"
|
||||
// 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"
|
||||
|
||||
// metricType is the type of a metric event.
|
||||
const metricType = "statsd"
|
||||
|
||||
// Level marks the severity of the event.
|
||||
type Level string
|
||||
@@ -32,15 +40,6 @@ 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"`
|
||||
@@ -103,6 +102,14 @@ 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 {
|
||||
@@ -158,6 +165,13 @@ type Request struct {
|
||||
Env map[string]string `json:"env,omitempty"`
|
||||
}
|
||||
|
||||
var sensitiveHeaders = map[string]struct{}{
|
||||
"Authorization": {},
|
||||
"Cookie": {},
|
||||
"X-Forwarded-For": {},
|
||||
"X-Real-Ip": {},
|
||||
}
|
||||
|
||||
// NewRequest returns a new Sentry Request from the given http.Request.
|
||||
//
|
||||
// NewRequest avoids operations that depend on network access. In particular, it
|
||||
@@ -173,7 +187,7 @@ func NewRequest(r *http.Request) *Request {
|
||||
var env map[string]string
|
||||
headers := map[string]string{}
|
||||
|
||||
if client := CurrentHub().Client(); client != nil && client.Options().SendDefaultPII {
|
||||
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
|
||||
@@ -188,7 +202,6 @@ func NewRequest(r *http.Request) *Request {
|
||||
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, ",")
|
||||
@@ -210,11 +223,15 @@ 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"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
HelpLink string `json:"help_link,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Handled *bool `json:"handled,omitempty"`
|
||||
ParentID *int `json:"parent_id,omitempty"`
|
||||
ExceptionID int `json:"exception_id"`
|
||||
IsExceptionGroup bool `json:"is_exception_group,omitempty"`
|
||||
Data map[string]any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// SetUnhandled indicates that the exception is an unhandled exception, i.e.
|
||||
@@ -229,7 +246,7 @@ 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"`
|
||||
}
|
||||
@@ -237,7 +254,8 @@ type Exception struct {
|
||||
// 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
|
||||
dsc DynamicSamplingContext
|
||||
transactionProfile *profileInfo
|
||||
}
|
||||
|
||||
// Contains information about how the name of the transaction was determined.
|
||||
@@ -304,6 +322,8 @@ type Event struct {
|
||||
Request *Request `json:"request,omitempty"`
|
||||
Exception []Exception `json:"exception,omitempty"`
|
||||
DebugMeta *DebugMeta `json:"debug_meta,omitempty"`
|
||||
Attachments []*Attachment `json:"-"`
|
||||
Metrics []Metric `json:"-"`
|
||||
|
||||
// The fields below are only relevant for transactions.
|
||||
|
||||
@@ -312,6 +332,11 @@ type Event struct {
|
||||
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
|
||||
@@ -320,27 +345,43 @@ type Event struct {
|
||||
// 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.
|
||||
// into while unwrapping the errors. If maxErrorDepth is -1, we will
|
||||
// unwrap all errors in the chain.
|
||||
func (e *Event) SetException(exception error, maxErrorDepth int) {
|
||||
err := exception
|
||||
if err == nil {
|
||||
if exception == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i < maxErrorDepth && err != nil; i++ {
|
||||
err := exception
|
||||
|
||||
for i := 0; err != nil && (i < maxErrorDepth || maxErrorDepth == -1); i++ {
|
||||
// Add the current error to the exception slice with its details
|
||||
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
|
||||
|
||||
// Attempt to unwrap the error using the standard library's Unwrap method.
|
||||
// If errors.Unwrap returns nil, it means either there is no error to unwrap,
|
||||
// or the error does not implement the Unwrap method.
|
||||
unwrappedErr := errors.Unwrap(err)
|
||||
|
||||
if unwrappedErr != nil {
|
||||
// The error was successfully unwrapped using the standard library's Unwrap method.
|
||||
err = unwrappedErr
|
||||
continue
|
||||
}
|
||||
|
||||
cause, ok := err.(interface{ Cause() error })
|
||||
if !ok {
|
||||
// We cannot unwrap the error further.
|
||||
break
|
||||
}
|
||||
|
||||
// The error implements the Cause method, indicating it may have been wrapped
|
||||
// using the github.com/pkg/errors package.
|
||||
err = cause.Cause()
|
||||
}
|
||||
|
||||
// Add a trace of the current stack to the most recent error in a chain if
|
||||
@@ -351,8 +392,23 @@ func (e *Event) SetException(exception error, maxErrorDepth int) {
|
||||
e.Exception[0].Stacktrace = NewStacktrace()
|
||||
}
|
||||
|
||||
if len(e.Exception) <= 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// event.Exception should be sorted such that the most recent error is last.
|
||||
reverse(e.Exception)
|
||||
|
||||
for i := range e.Exception {
|
||||
e.Exception[i].Mechanism = &Mechanism{
|
||||
IsExceptionGroup: true,
|
||||
ExceptionID: i,
|
||||
}
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
e.Exception[i].Mechanism.ParentID = Pointer(i - 1)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Event.Contexts map[string]interface{} => map[string]EventContext,
|
||||
@@ -372,6 +428,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()
|
||||
}
|
||||
@@ -446,15 +504,37 @@ 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{
|
||||
return &Event{
|
||||
Contexts: make(map[string]Context),
|
||||
Extra: make(map[string]interface{}),
|
||||
Tags: make(map[string]string),
|
||||
Modules: make(map[string]string),
|
||||
}
|
||||
return &event
|
||||
}
|
||||
|
||||
// Thread specifies threads that were running at the time of an event.
|
||||
|
||||
17
vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go
generated
vendored
17
vendor/github.com/getsentry/sentry-go/internal/ratelimit/category.go
generated
vendored
@@ -32,15 +32,14 @@ var knownCategories = map[Category]struct{}{
|
||||
|
||||
// String returns the category formatted for debugging.
|
||||
func (c Category) String() string {
|
||||
switch c {
|
||||
case "":
|
||||
if c == "" {
|
||||
return "CategoryAll"
|
||||
default:
|
||||
caser := cases.Title(language.English)
|
||||
rv := "Category"
|
||||
for _, w := range strings.Fields(string(c)) {
|
||||
rv += caser.String(w)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
caser := cases.Title(language.English)
|
||||
rv := "Category"
|
||||
for _, w := range strings.Fields(string(c)) {
|
||||
rv += caser.String(w)
|
||||
}
|
||||
return rv
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
427
vendor/github.com/getsentry/sentry-go/metrics.go
generated
vendored
Normal file
427
vendor/github.com/getsentry/sentry-go/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,427 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"math"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
NumberOrString interface {
|
||||
int | string
|
||||
}
|
||||
|
||||
void struct{}
|
||||
)
|
||||
|
||||
var (
|
||||
member void
|
||||
keyRegex = regexp.MustCompile(`[^a-zA-Z0-9_/.-]+`)
|
||||
valueRegex = regexp.MustCompile(`[^\w\d\s_:/@\.{}\[\]$-]+`)
|
||||
unitRegex = regexp.MustCompile(`[^a-z]+`)
|
||||
)
|
||||
|
||||
type MetricUnit struct {
|
||||
unit string
|
||||
}
|
||||
|
||||
func (m MetricUnit) toString() string {
|
||||
return m.unit
|
||||
}
|
||||
|
||||
func NanoSecond() MetricUnit {
|
||||
return MetricUnit{
|
||||
"nanosecond",
|
||||
}
|
||||
}
|
||||
|
||||
func MicroSecond() MetricUnit {
|
||||
return MetricUnit{
|
||||
"microsecond",
|
||||
}
|
||||
}
|
||||
|
||||
func MilliSecond() MetricUnit {
|
||||
return MetricUnit{
|
||||
"millisecond",
|
||||
}
|
||||
}
|
||||
|
||||
func Second() MetricUnit {
|
||||
return MetricUnit{
|
||||
"second",
|
||||
}
|
||||
}
|
||||
|
||||
func Minute() MetricUnit {
|
||||
return MetricUnit{
|
||||
"minute",
|
||||
}
|
||||
}
|
||||
|
||||
func Hour() MetricUnit {
|
||||
return MetricUnit{
|
||||
"hour",
|
||||
}
|
||||
}
|
||||
|
||||
func Day() MetricUnit {
|
||||
return MetricUnit{
|
||||
"day",
|
||||
}
|
||||
}
|
||||
|
||||
func Week() MetricUnit {
|
||||
return MetricUnit{
|
||||
"week",
|
||||
}
|
||||
}
|
||||
|
||||
func Bit() MetricUnit {
|
||||
return MetricUnit{
|
||||
"bit",
|
||||
}
|
||||
}
|
||||
|
||||
func Byte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"byte",
|
||||
}
|
||||
}
|
||||
|
||||
func KiloByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"kilobyte",
|
||||
}
|
||||
}
|
||||
|
||||
func KibiByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"kibibyte",
|
||||
}
|
||||
}
|
||||
|
||||
func MegaByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"megabyte",
|
||||
}
|
||||
}
|
||||
|
||||
func MebiByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"mebibyte",
|
||||
}
|
||||
}
|
||||
|
||||
func GigaByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"gigabyte",
|
||||
}
|
||||
}
|
||||
|
||||
func GibiByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"gibibyte",
|
||||
}
|
||||
}
|
||||
|
||||
func TeraByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"terabyte",
|
||||
}
|
||||
}
|
||||
|
||||
func TebiByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"tebibyte",
|
||||
}
|
||||
}
|
||||
|
||||
func PetaByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"petabyte",
|
||||
}
|
||||
}
|
||||
|
||||
func PebiByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"pebibyte",
|
||||
}
|
||||
}
|
||||
|
||||
func ExaByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"exabyte",
|
||||
}
|
||||
}
|
||||
|
||||
func ExbiByte() MetricUnit {
|
||||
return MetricUnit{
|
||||
"exbibyte",
|
||||
}
|
||||
}
|
||||
|
||||
func Ratio() MetricUnit {
|
||||
return MetricUnit{
|
||||
"ratio",
|
||||
}
|
||||
}
|
||||
|
||||
func Percent() MetricUnit {
|
||||
return MetricUnit{
|
||||
"percent",
|
||||
}
|
||||
}
|
||||
|
||||
func CustomUnit(unit string) MetricUnit {
|
||||
return MetricUnit{
|
||||
unitRegex.ReplaceAllString(unit, ""),
|
||||
}
|
||||
}
|
||||
|
||||
type Metric interface {
|
||||
GetType() string
|
||||
GetTags() map[string]string
|
||||
GetKey() string
|
||||
GetUnit() string
|
||||
GetTimestamp() int64
|
||||
SerializeValue() string
|
||||
SerializeTags() string
|
||||
}
|
||||
|
||||
type abstractMetric struct {
|
||||
key string
|
||||
unit MetricUnit
|
||||
tags map[string]string
|
||||
// A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||||
timestamp int64
|
||||
}
|
||||
|
||||
func (am abstractMetric) GetTags() map[string]string {
|
||||
return am.tags
|
||||
}
|
||||
|
||||
func (am abstractMetric) GetKey() string {
|
||||
return am.key
|
||||
}
|
||||
|
||||
func (am abstractMetric) GetUnit() string {
|
||||
return am.unit.toString()
|
||||
}
|
||||
|
||||
func (am abstractMetric) GetTimestamp() int64 {
|
||||
return am.timestamp
|
||||
}
|
||||
|
||||
func (am abstractMetric) SerializeTags() string {
|
||||
var sb strings.Builder
|
||||
|
||||
values := make([]string, 0, len(am.tags))
|
||||
for k := range am.tags {
|
||||
values = append(values, k)
|
||||
}
|
||||
sortSlice(values)
|
||||
|
||||
for _, key := range values {
|
||||
val := sanitizeValue(am.tags[key])
|
||||
key = sanitizeKey(key)
|
||||
sb.WriteString(fmt.Sprintf("%s:%s,", key, val))
|
||||
}
|
||||
s := sb.String()
|
||||
if len(s) > 0 {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Counter Metric.
|
||||
type CounterMetric struct {
|
||||
value float64
|
||||
abstractMetric
|
||||
}
|
||||
|
||||
func (c *CounterMetric) Add(value float64) {
|
||||
c.value += value
|
||||
}
|
||||
|
||||
func (c CounterMetric) GetType() string {
|
||||
return "c"
|
||||
}
|
||||
|
||||
func (c CounterMetric) SerializeValue() string {
|
||||
return fmt.Sprintf(":%v", c.value)
|
||||
}
|
||||
|
||||
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||||
func NewCounterMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) CounterMetric {
|
||||
am := abstractMetric{
|
||||
key,
|
||||
unit,
|
||||
tags,
|
||||
timestamp,
|
||||
}
|
||||
|
||||
return CounterMetric{
|
||||
value,
|
||||
am,
|
||||
}
|
||||
}
|
||||
|
||||
// Distribution Metric.
|
||||
type DistributionMetric struct {
|
||||
values []float64
|
||||
abstractMetric
|
||||
}
|
||||
|
||||
func (d *DistributionMetric) Add(value float64) {
|
||||
d.values = append(d.values, value)
|
||||
}
|
||||
|
||||
func (d DistributionMetric) GetType() string {
|
||||
return "d"
|
||||
}
|
||||
|
||||
func (d DistributionMetric) SerializeValue() string {
|
||||
var sb strings.Builder
|
||||
for _, el := range d.values {
|
||||
sb.WriteString(fmt.Sprintf(":%v", el))
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||||
func NewDistributionMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) DistributionMetric {
|
||||
am := abstractMetric{
|
||||
key,
|
||||
unit,
|
||||
tags,
|
||||
timestamp,
|
||||
}
|
||||
|
||||
return DistributionMetric{
|
||||
[]float64{value},
|
||||
am,
|
||||
}
|
||||
}
|
||||
|
||||
// Gauge Metric.
|
||||
type GaugeMetric struct {
|
||||
last float64
|
||||
min float64
|
||||
max float64
|
||||
sum float64
|
||||
count float64
|
||||
abstractMetric
|
||||
}
|
||||
|
||||
func (g *GaugeMetric) Add(value float64) {
|
||||
g.last = value
|
||||
g.min = math.Min(g.min, value)
|
||||
g.max = math.Max(g.max, value)
|
||||
g.sum += value
|
||||
g.count++
|
||||
}
|
||||
|
||||
func (g GaugeMetric) GetType() string {
|
||||
return "g"
|
||||
}
|
||||
|
||||
func (g GaugeMetric) SerializeValue() string {
|
||||
return fmt.Sprintf(":%v:%v:%v:%v:%v", g.last, g.min, g.max, g.sum, g.count)
|
||||
}
|
||||
|
||||
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||||
func NewGaugeMetric(key string, unit MetricUnit, tags map[string]string, timestamp int64, value float64) GaugeMetric {
|
||||
am := abstractMetric{
|
||||
key,
|
||||
unit,
|
||||
tags,
|
||||
timestamp,
|
||||
}
|
||||
|
||||
return GaugeMetric{
|
||||
value, // last
|
||||
value, // min
|
||||
value, // max
|
||||
value, // sum
|
||||
value, // count
|
||||
am,
|
||||
}
|
||||
}
|
||||
|
||||
// Set Metric.
|
||||
type SetMetric[T NumberOrString] struct {
|
||||
values map[T]void
|
||||
abstractMetric
|
||||
}
|
||||
|
||||
func (s *SetMetric[T]) Add(value T) {
|
||||
s.values[value] = member
|
||||
}
|
||||
|
||||
func (s SetMetric[T]) GetType() string {
|
||||
return "s"
|
||||
}
|
||||
|
||||
func (s SetMetric[T]) SerializeValue() string {
|
||||
_hash := func(s string) uint32 {
|
||||
return crc32.ChecksumIEEE([]byte(s))
|
||||
}
|
||||
|
||||
values := make([]T, 0, len(s.values))
|
||||
for k := range s.values {
|
||||
values = append(values, k)
|
||||
}
|
||||
sortSlice(values)
|
||||
|
||||
var sb strings.Builder
|
||||
for _, el := range values {
|
||||
switch any(el).(type) {
|
||||
case int:
|
||||
sb.WriteString(fmt.Sprintf(":%v", el))
|
||||
case string:
|
||||
s := fmt.Sprintf("%v", el)
|
||||
sb.WriteString(fmt.Sprintf(":%d", _hash(s)))
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// timestamp: A unix timestamp (full seconds elapsed since 1970-01-01 00:00 UTC).
|
||||
func NewSetMetric[T NumberOrString](key string, unit MetricUnit, tags map[string]string, timestamp int64, value T) SetMetric[T] {
|
||||
am := abstractMetric{
|
||||
key,
|
||||
unit,
|
||||
tags,
|
||||
timestamp,
|
||||
}
|
||||
|
||||
return SetMetric[T]{
|
||||
map[T]void{
|
||||
value: member,
|
||||
},
|
||||
am,
|
||||
}
|
||||
}
|
||||
|
||||
func sanitizeKey(s string) string {
|
||||
return keyRegex.ReplaceAllString(s, "_")
|
||||
}
|
||||
|
||||
func sanitizeValue(s string) string {
|
||||
return valueRegex.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
type Ordered interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
|
||||
}
|
||||
|
||||
func sortSlice[T Ordered](s []T) {
|
||||
sort.Slice(s, func(i, j int) bool {
|
||||
return s[i] < s[j]
|
||||
})
|
||||
}
|
||||
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)
|
||||
}
|
||||
41
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
41
vendor/github.com/getsentry/sentry-go/scope.go
generated
vendored
@@ -25,6 +25,7 @@ import (
|
||||
type Scope struct {
|
||||
mu sync.RWMutex
|
||||
breadcrumbs []*Breadcrumb
|
||||
attachments []*Attachment
|
||||
user User
|
||||
tags map[string]string
|
||||
contexts map[string]Context
|
||||
@@ -48,6 +49,7 @@ 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]Context),
|
||||
extra: make(map[string]interface{}),
|
||||
@@ -81,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()
|
||||
@@ -283,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
|
||||
@@ -323,6 +343,10 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||
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, len(scope.tags))
|
||||
@@ -350,7 +374,7 @@ func (scope *Scope) ApplyToEvent(event *Event, hint *EventHint) *Event {
|
||||
|
||||
// Ensure we are not overwriting event fields
|
||||
if _, ok := event.Contexts[key]; !ok {
|
||||
event.Contexts[key] = value
|
||||
event.Contexts[key] = cloneContext(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,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
|
||||
}
|
||||
|
||||
40
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
40
vendor/github.com/getsentry/sentry-go/sentry.go
generated
vendored
@@ -5,22 +5,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Deprecated: Use SDKVersion instead.
|
||||
const Version = SDKVersion
|
||||
|
||||
// Version is the version of the SDK.
|
||||
const SDKVersion = "0.21.0"
|
||||
|
||||
// The identifier of the SDK.
|
||||
const SDKIdentifier = "sentry.go"
|
||||
// The version of the SDK.
|
||||
const SDKVersion = "0.28.1"
|
||||
|
||||
// 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/" + SDKVersion
|
||||
|
||||
// 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 {
|
||||
@@ -54,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
|
||||
@@ -75,18 +72,17 @@ func Recover() *EventID {
|
||||
|
||||
// RecoverWithContext captures a panic and passes relevant context object.
|
||||
func RecoverWithContext(ctx context.Context) *EventID {
|
||||
if err := recover(); err != nil {
|
||||
var hub *Hub
|
||||
|
||||
if HasHubOnContext(ctx) {
|
||||
hub = GetHubFromContext(ctx)
|
||||
} else {
|
||||
hub = CurrentHub()
|
||||
}
|
||||
|
||||
return hub.RecoverWithContext(ctx, err)
|
||||
err := recover()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
hub := GetHubFromContext(ctx)
|
||||
if hub == nil {
|
||||
hub = CurrentHub()
|
||||
}
|
||||
|
||||
return hub.RecoverWithContext(ctx, err)
|
||||
}
|
||||
|
||||
// WithScope is a shorthand for CurrentHub().WithScope.
|
||||
|
||||
2
vendor/github.com/getsentry/sentry-go/span_recorder.go
generated
vendored
2
vendor/github.com/getsentry/sentry-go/span_recorder.go
generated
vendored
@@ -17,7 +17,7 @@ type spanRecorder struct {
|
||||
func (r *spanRecorder) record(s *Span) {
|
||||
maxSpans := defaultMaxSpans
|
||||
if client := CurrentHub().Client(); client != nil {
|
||||
maxSpans = client.Options().MaxSpans
|
||||
maxSpans = client.options.MaxSpans
|
||||
}
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
166
vendor/github.com/getsentry/sentry-go/stacktrace.go
generated
vendored
166
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,
|
||||
@@ -174,7 +173,7 @@ 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
|
||||
@@ -192,34 +191,6 @@ type Frame struct {
|
||||
|
||||
// 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
|
||||
|
||||
@@ -227,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
|
||||
}
|
||||
@@ -245,24 +257,27 @@ 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 = make([]Frame, 0, len(pcs))
|
||||
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(frames, NewFrame(callerFrame))
|
||||
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]
|
||||
@@ -271,41 +286,60 @@ func extractFrames(pcs []uintptr) []Frame {
|
||||
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
|
||||
}
|
||||
|
||||
// reuse
|
||||
filteredFrames := frames[:0]
|
||||
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 {
|
||||
@@ -321,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
|
||||
}
|
||||
95
vendor/github.com/getsentry/sentry-go/traces_profiler.go
generated
vendored
Normal file
95
vendor/github.com/getsentry/sentry-go/traces_profiler.go
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
package sentry
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Checks whether the transaction should be profiled (according to ProfilesSampleRate)
|
||||
// and starts a profiler if so.
|
||||
func (s *Span) sampleTransactionProfile() {
|
||||
var sampleRate = s.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 {
|
||||
s.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
|
||||
|
||||
getStringFromContext := func(context map[string]interface{}, originalValue, key string) string {
|
||||
v, ok := context[key]
|
||||
if !ok {
|
||||
return originalValue
|
||||
}
|
||||
|
||||
if s, ok := v.(string); ok {
|
||||
return s
|
||||
}
|
||||
|
||||
return originalValue
|
||||
}
|
||||
|
||||
if runtimeContext, ok := event.Contexts["runtime"]; ok {
|
||||
info.Runtime.Name = getStringFromContext(runtimeContext, info.Runtime.Name, "name")
|
||||
info.Runtime.Version = getStringFromContext(runtimeContext, info.Runtime.Version, "version")
|
||||
}
|
||||
if osContext, ok := event.Contexts["os"]; ok {
|
||||
info.OS.Name = getStringFromContext(osContext, info.OS.Name, "name")
|
||||
}
|
||||
if deviceContext, ok := event.Contexts["device"]; ok {
|
||||
info.Device.Architecture = getStringFromContext(deviceContext, info.Device.Architecture, "arch")
|
||||
}
|
||||
}
|
||||
234
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
234
vendor/github.com/getsentry/sentry-go/tracing.go
generated
vendored
@@ -49,15 +49,14 @@ type Span struct { //nolint: maligned // prefer readability over optimal memory
|
||||
// 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.
|
||||
@@ -110,9 +109,8 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||
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 {
|
||||
@@ -171,42 +169,34 @@ func StartSpan(ctx context.Context, operation string, options ...SpanOption) *Sp
|
||||
|
||||
span.Sampled = span.sample()
|
||||
|
||||
span.recorder = &spanRecorder{}
|
||||
if hasParent {
|
||||
span.recorder = parent.spanRecorder()
|
||||
} else {
|
||||
span.recorder = &spanRecorder{}
|
||||
}
|
||||
|
||||
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().Map())
|
||||
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)
|
||||
hub.CaptureEvent(event)
|
||||
s.finishOnce.Do(s.doFinish)
|
||||
}
|
||||
|
||||
// Context returns the context containing the span.
|
||||
@@ -236,7 +226,11 @@ func (s *Span) SetTag(name, value string) {
|
||||
// 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) {
|
||||
func (s *Span) SetData(name string, value interface{}) {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
@@ -261,7 +255,7 @@ func (s *Span) SetContext(key string, value Context) {
|
||||
|
||||
// IsTransaction checks if the given span is a transaction.
|
||||
func (s *Span) IsTransaction() bool {
|
||||
return s.isTransaction
|
||||
return s.parent == nil
|
||||
}
|
||||
|
||||
// GetTransaction returns the transaction that contains this span.
|
||||
@@ -293,7 +287,7 @@ func (s *Span) GetTransaction() *Span {
|
||||
// func (s *Span) TransactionName() string
|
||||
// func (s *Span) SetTransactionName(name string)
|
||||
|
||||
// ToSentryTrace returns the seralized TraceParentContext from a transaction/sapn.
|
||||
// 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 {
|
||||
@@ -330,11 +324,36 @@ func (s *Span) ToBaggage() string {
|
||||
// SetDynamicSamplingContext sets the given dynamic sampling context on the
|
||||
// current transaction.
|
||||
func (s *Span) SetDynamicSamplingContext(dsc DynamicSamplingContext) {
|
||||
if s.isTransaction {
|
||||
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
|
||||
@@ -370,7 +389,7 @@ func (s *Span) updateFromSentryTrace(header []byte) (updated bool) {
|
||||
}
|
||||
|
||||
func (s *Span) updateFromBaggage(header []byte) {
|
||||
if s.isTransaction {
|
||||
if s.IsTransaction() {
|
||||
dsc, err := DynamicSamplingContextFromHeader(header)
|
||||
if err != nil {
|
||||
return
|
||||
@@ -397,14 +416,16 @@ func (s *Span) MarshalJSON() ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Span) sample() Sampled {
|
||||
hub := hubFromContext(s.ctx)
|
||||
var clientOptions ClientOptions
|
||||
client := hub.Client()
|
||||
func (s *Span) clientOptions() *ClientOptions {
|
||||
client := hubFromContext(s.ctx).Client()
|
||||
if client != nil {
|
||||
clientOptions = hub.Client().Options()
|
||||
return &client.options
|
||||
}
|
||||
return &ClientOptions{}
|
||||
}
|
||||
|
||||
func (s *Span) sample() Sampled {
|
||||
clientOptions := s.clientOptions()
|
||||
// https://develop.sentry.dev/sdk/performance/#sampling
|
||||
// #1 tracing is not enabled.
|
||||
if !clientOptions.EnableTracing {
|
||||
@@ -429,7 +450,7 @@ func (s *Span) sample() Sampled {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -493,7 +514,7 @@ func (s *Span) toEvent() *Event {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.isTransaction {
|
||||
if !s.IsTransaction() {
|
||||
return nil // only transactions can be transformed into events
|
||||
}
|
||||
|
||||
@@ -515,10 +536,16 @@ func (s *Span) toEvent() *Event {
|
||||
|
||||
contexts := map[string]Context{}
|
||||
for k, v := range s.contexts {
|
||||
contexts[k] = v
|
||||
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: s.Name,
|
||||
@@ -529,7 +556,7 @@ func (s *Span) toEvent() *Event {
|
||||
StartTime: s.StartTime,
|
||||
Spans: finished,
|
||||
TransactionInfo: &TransactionInfo{
|
||||
Source: s.Source,
|
||||
Source: transactionSource,
|
||||
},
|
||||
sdkMetaData: SDKMetaData{
|
||||
dsc: s.dynamicSamplingContext,
|
||||
@@ -620,6 +647,24 @@ const (
|
||||
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
|
||||
|
||||
@@ -782,17 +827,6 @@ 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.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Deprecated: Use WithTransactionSource() instead.
|
||||
func TransactionName(name string) SpanOption {
|
||||
return WithTransactionName(name)
|
||||
}
|
||||
|
||||
// WithTransactionName option sets the name of the current transaction.
|
||||
//
|
||||
// A span tree has a single transaction name, therefore using this option when
|
||||
@@ -804,11 +838,11 @@ func WithTransactionName(name string) SpanOption {
|
||||
}
|
||||
}
|
||||
|
||||
// OpName sets the operation name for a given span.
|
||||
//
|
||||
// Deprecated: Use WithOpName() instead.
|
||||
func OpName(name string) SpanOption {
|
||||
return WithOpName(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.
|
||||
@@ -818,27 +852,17 @@ func WithOpName(name string) SpanOption {
|
||||
}
|
||||
}
|
||||
|
||||
// TransctionSource sets the source of the transaction name.
|
||||
//
|
||||
// Deprecated: Use WithTransactionSource() instead.
|
||||
func TransctionSource(source TransactionSource) SpanOption {
|
||||
return WithTransactionSource(source)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// SpanSampled updates the sampling flag for a given span.
|
||||
//
|
||||
// Deprecated: Use WithSpanSampled() instead.
|
||||
func SpanSampled(sampled Sampled) SpanOption {
|
||||
return WithSpanSampled(sampled)
|
||||
}
|
||||
|
||||
// WithSpanSampled updates the sampling flag for a given span.
|
||||
func WithSpanSampled(sampled Sampled) SpanOption {
|
||||
return func(s *Span) {
|
||||
@@ -901,27 +925,9 @@ 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
|
||||
}
|
||||
@@ -943,3 +949,41 @@ func StartTransaction(ctx context.Context, name string, options ...SpanOption) *
|
||||
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
|
||||
}
|
||||
|
||||
210
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
210
vendor/github.com/getsentry/sentry-go/transport.go
generated
vendored
@@ -2,6 +2,7 @@ package sentry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -94,6 +95,103 @@ func getRequestBodyFromEvent(event *Event) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func marshalMetrics(metrics []Metric) []byte {
|
||||
var b bytes.Buffer
|
||||
for i, metric := range metrics {
|
||||
b.WriteString(metric.GetKey())
|
||||
if unit := metric.GetUnit(); unit != "" {
|
||||
b.WriteString(fmt.Sprintf("@%s", unit))
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("%s|%s", metric.SerializeValue(), metric.GetType()))
|
||||
if serializedTags := metric.SerializeTags(); serializedTags != "" {
|
||||
b.WriteString(fmt.Sprintf("|#%s", serializedTags))
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("|T%d", metric.GetTimestamp()))
|
||||
|
||||
if i < len(metrics)-1 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
return b.Bytes()
|
||||
}
|
||||
|
||||
func encodeMetric(enc *json.Encoder, b io.Writer, metrics []Metric) error {
|
||||
body := marshalMetrics(metrics)
|
||||
// Item header
|
||||
err := enc.Encode(struct {
|
||||
Type string `json:"type"`
|
||||
Length int `json:"length"`
|
||||
}{
|
||||
Type: metricType,
|
||||
Length: len(body),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// metric payload
|
||||
if _, err = b.Write(body); 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 err
|
||||
}
|
||||
|
||||
func encodeAttachment(enc *json.Encoder, b io.Writer, attachment *Attachment) error {
|
||||
// Attachment header
|
||||
err := enc.Encode(struct {
|
||||
Type string `json:"type"`
|
||||
Length int `json:"length"`
|
||||
Filename string `json:"filename"`
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
}{
|
||||
Type: "attachment",
|
||||
Length: len(attachment.Payload),
|
||||
Filename: attachment.Filename,
|
||||
ContentType: attachment.ContentType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 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: 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)
|
||||
@@ -127,38 +225,58 @@ func envelopeFromBody(event *Event, dsn *Dsn, sentAt time.Time, body json.RawMes
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var itemType string
|
||||
switch event.Type {
|
||||
case transactionType:
|
||||
itemType = transactionType
|
||||
case transactionType, checkInType:
|
||||
err = encodeEnvelopeItem(enc, event.Type, body)
|
||||
case metricType:
|
||||
err = encodeMetric(enc, &b, event.Metrics)
|
||||
default:
|
||||
itemType = eventType
|
||||
err = encodeEnvelopeItem(enc, eventType, body)
|
||||
}
|
||||
|
||||
// Item header
|
||||
err = enc.Encode(struct {
|
||||
Type string `json:"type"`
|
||||
Length int `json:"length"`
|
||||
}{
|
||||
Type: itemType,
|
||||
Length: len(body),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// payload
|
||||
err = enc.Encode(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) {
|
||||
func getRequestFromEvent(ctx context.Context, 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)
|
||||
@@ -169,7 +287,13 @@ func getRequestFromEvent(event *Event, dsn *Dsn) (r *http.Request, err error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return http.NewRequest(
|
||||
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
return http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
dsn.GetAPIURL().String(),
|
||||
envelope,
|
||||
@@ -280,8 +404,13 @@ func (t *HTTPTransport) Configure(options ClientOptions) {
|
||||
})
|
||||
}
|
||||
|
||||
// SendEvent assembles a new packet out of Event and sends it to remote server.
|
||||
// SendEvent assembles a new packet out of Event and sends it to the remote server.
|
||||
func (t *HTTPTransport) SendEvent(event *Event) {
|
||||
t.SendEventWithContext(context.Background(), event)
|
||||
}
|
||||
|
||||
// SendEventWithContext assembles a new packet out of Event and sends it to the remote server.
|
||||
func (t *HTTPTransport) SendEventWithContext(ctx context.Context, event *Event) {
|
||||
if t.dsn == nil {
|
||||
return
|
||||
}
|
||||
@@ -292,15 +421,11 @@ func (t *HTTPTransport) SendEvent(event *Event) {
|
||||
return
|
||||
}
|
||||
|
||||
request, err := getRequestFromEvent(event, t.dsn)
|
||||
request, err := getRequestFromEvent(ctx, event, t.dsn)
|
||||
if err != nil {
|
||||
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.
|
||||
//
|
||||
@@ -418,6 +543,13 @@ func (t *HTTPTransport) worker() {
|
||||
Logger.Printf("There was an issue with sending an event: %v", err)
|
||||
continue
|
||||
}
|
||||
if response.StatusCode >= 400 && response.StatusCode <= 599 {
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
Logger.Printf("Error while reading response code: %v", err)
|
||||
}
|
||||
Logger.Printf("Sending %s failed with the following error: %s", eventType, string(b))
|
||||
}
|
||||
t.mu.Lock()
|
||||
t.limits.Merge(ratelimit.FromResponse(response))
|
||||
t.mu.Unlock()
|
||||
@@ -507,8 +639,13 @@ func (t *HTTPSyncTransport) Configure(options ClientOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
// SendEvent assembles a new packet out of Event and sends it to remote server.
|
||||
// SendEvent assembles a new packet out of Event and sends it to the remote server.
|
||||
func (t *HTTPSyncTransport) SendEvent(event *Event) {
|
||||
t.SendEventWithContext(context.Background(), event)
|
||||
}
|
||||
|
||||
// SendEventWithContext assembles a new packet out of Event and sends it to the remote server.
|
||||
func (t *HTTPSyncTransport) SendEventWithContext(ctx context.Context, event *Event) {
|
||||
if t.dsn == nil {
|
||||
return
|
||||
}
|
||||
@@ -517,19 +654,18 @@ func (t *HTTPSyncTransport) SendEvent(event *Event) {
|
||||
return
|
||||
}
|
||||
|
||||
request, err := getRequestFromEvent(event, t.dsn)
|
||||
request, err := getRequestFromEvent(ctx, event, t.dsn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for headerKey, headerValue := range t.dsn.RequestHeaders() {
|
||||
request.Header.Set(headerKey, headerValue)
|
||||
}
|
||||
|
||||
var eventType string
|
||||
if event.Type == transactionType {
|
||||
switch {
|
||||
case event.Type == transactionType:
|
||||
eventType = "transaction"
|
||||
} else {
|
||||
case event.Type == metricType:
|
||||
eventType = metricType
|
||||
default:
|
||||
eventType = fmt.Sprintf("%s event", event.Level)
|
||||
}
|
||||
Logger.Printf(
|
||||
@@ -545,6 +681,14 @@ func (t *HTTPSyncTransport) SendEvent(event *Event) {
|
||||
Logger.Printf("There was an issue with sending an event: %v", err)
|
||||
return
|
||||
}
|
||||
if response.StatusCode >= 400 && response.StatusCode <= 599 {
|
||||
b, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
Logger.Printf("Error while reading response code: %v", err)
|
||||
}
|
||||
Logger.Printf("Sending %s failed with the following error: %s", eventType, string(b))
|
||||
}
|
||||
|
||||
t.mu.Lock()
|
||||
t.limits.Merge(ratelimit.FromResponse(response))
|
||||
t.mu.Unlock()
|
||||
|
||||
59
vendor/github.com/getsentry/sentry-go/util.go
generated
vendored
59
vendor/github.com/getsentry/sentry-go/util.go
generated
vendored
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -66,26 +67,52 @@ 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 ""
|
||||
}
|
||||
|
||||
func Pointer[T any](v T) *T {
|
||||
return &v
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user