mirror of
https://github.com/ManyakRus/starter.git
synced 2025-11-26 23:10:42 +02:00
684 lines
22 KiB
Go
684 lines
22 KiB
Go
package sentry
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/getsentry/sentry-go/attribute"
|
|
"github.com/getsentry/sentry-go/internal/protocol"
|
|
"github.com/getsentry/sentry-go/internal/ratelimit"
|
|
)
|
|
|
|
const eventType = "event"
|
|
const transactionType = "transaction"
|
|
const checkInType = "check_in"
|
|
|
|
var logEvent = struct {
|
|
Type string
|
|
ContentType string
|
|
}{
|
|
"log",
|
|
"application/vnd.sentry.items.log+json",
|
|
}
|
|
|
|
// Level marks the severity of the event.
|
|
type Level string
|
|
|
|
// Describes the severity of the event.
|
|
const (
|
|
LevelDebug Level = "debug"
|
|
LevelInfo Level = "info"
|
|
LevelWarning Level = "warning"
|
|
LevelError Level = "error"
|
|
LevelFatal Level = "fatal"
|
|
)
|
|
|
|
// SdkInfo contains all metadata about the SDK.
|
|
type SdkInfo = protocol.SdkInfo
|
|
type SdkPackage = protocol.SdkPackage
|
|
|
|
// TODO: This type could be more useful, as map of interface{} is too generic
|
|
// and requires a lot of type assertions in beforeBreadcrumb calls
|
|
// plus it could just be map[string]interface{} then.
|
|
|
|
// BreadcrumbHint contains information that can be associated with a Breadcrumb.
|
|
type BreadcrumbHint map[string]interface{}
|
|
|
|
// Breadcrumb specifies an application event that occurred before a Sentry event.
|
|
// An event may contain one or more breadcrumbs.
|
|
type Breadcrumb struct {
|
|
Type string `json:"type,omitempty"`
|
|
Category string `json:"category,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
Data map[string]interface{} `json:"data,omitempty"`
|
|
Level Level `json:"level,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
}
|
|
|
|
// TODO: provide constants for known breadcrumb types.
|
|
// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types.
|
|
|
|
// MarshalJSON converts the Breadcrumb struct to JSON.
|
|
func (b *Breadcrumb) MarshalJSON() ([]byte, error) {
|
|
// We want to omit time.Time zero values, otherwise the server will try to
|
|
// interpret dates too far in the past. However, encoding/json doesn't
|
|
// support the "omitempty" option for struct types. See
|
|
// https://golang.org/issues/11939.
|
|
//
|
|
// We overcome the limitation and achieve what we want by shadowing fields
|
|
// and a few type tricks.
|
|
|
|
// breadcrumb aliases Breadcrumb to allow calling json.Marshal without an
|
|
// infinite loop. It preserves all fields while none of the attached
|
|
// methods.
|
|
type breadcrumb Breadcrumb
|
|
|
|
if b.Timestamp.IsZero() {
|
|
return json.Marshal(struct {
|
|
// Embed all of the fields of Breadcrumb.
|
|
*breadcrumb
|
|
// Timestamp shadows the original Timestamp field and is meant to
|
|
// remain nil, triggering the omitempty behavior.
|
|
Timestamp json.RawMessage `json:"timestamp,omitempty"`
|
|
}{breadcrumb: (*breadcrumb)(b)})
|
|
}
|
|
return json.Marshal((*breadcrumb)(b))
|
|
}
|
|
|
|
// Logger provides a chaining API for structured logging to Sentry.
|
|
type Logger interface {
|
|
// Write implements the io.Writer interface. Currently, the [sentry.Hub] is
|
|
// context aware, in order to get the correct trace correlation. Using this
|
|
// might result in incorrect span association on logs. If you need to use
|
|
// Write it is recommended to create a NewLogger so that the associated context
|
|
// is passed correctly.
|
|
Write(p []byte) (n int, err error)
|
|
|
|
// SetAttributes allows attaching parameters to the logger using the attribute API.
|
|
// These attributes will be included in all subsequent log entries.
|
|
SetAttributes(...attribute.Builder)
|
|
|
|
// Trace defines the [sentry.LogLevel] for the log entry.
|
|
Trace() LogEntry
|
|
// Debug defines the [sentry.LogLevel] for the log entry.
|
|
Debug() LogEntry
|
|
// Info defines the [sentry.LogLevel] for the log entry.
|
|
Info() LogEntry
|
|
// Warn defines the [sentry.LogLevel] for the log entry.
|
|
Warn() LogEntry
|
|
// Error defines the [sentry.LogLevel] for the log entry.
|
|
Error() LogEntry
|
|
// Fatal defines the [sentry.LogLevel] for the log entry.
|
|
Fatal() LogEntry
|
|
// Panic defines the [sentry.LogLevel] for the log entry.
|
|
Panic() LogEntry
|
|
// GetCtx returns the [context.Context] set on the logger.
|
|
GetCtx() context.Context
|
|
}
|
|
|
|
// LogEntry defines the interface for a log entry that supports chaining attributes.
|
|
type LogEntry interface {
|
|
// WithCtx creates a new LogEntry with the specified context without overwriting the previous one.
|
|
WithCtx(ctx context.Context) LogEntry
|
|
// String adds a string attribute to the LogEntry.
|
|
String(key, value string) LogEntry
|
|
// Int adds an int attribute to the LogEntry.
|
|
Int(key string, value int) LogEntry
|
|
// Int64 adds an int64 attribute to the LogEntry.
|
|
Int64(key string, value int64) LogEntry
|
|
// Float64 adds a float64 attribute to the LogEntry.
|
|
Float64(key string, value float64) LogEntry
|
|
// Bool adds a bool attribute to the LogEntry.
|
|
Bool(key string, value bool) LogEntry
|
|
// Emit emits the LogEntry with the provided arguments.
|
|
Emit(args ...interface{})
|
|
// Emitf emits the LogEntry using a format string and arguments.
|
|
Emitf(format string, args ...interface{})
|
|
}
|
|
|
|
// 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 {
|
|
ID string `json:"id,omitempty"`
|
|
Email string `json:"email,omitempty"`
|
|
IPAddress string `json:"ip_address,omitempty"`
|
|
Username string `json:"username,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Data map[string]string `json:"data,omitempty"`
|
|
}
|
|
|
|
func (u User) IsEmpty() bool {
|
|
if u.ID != "" {
|
|
return false
|
|
}
|
|
|
|
if u.Email != "" {
|
|
return false
|
|
}
|
|
|
|
if u.IPAddress != "" {
|
|
return false
|
|
}
|
|
|
|
if u.Username != "" {
|
|
return false
|
|
}
|
|
|
|
if u.Name != "" {
|
|
return false
|
|
}
|
|
|
|
if len(u.Data) > 0 {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Request contains information on a HTTP request related to the event.
|
|
type Request struct {
|
|
URL string `json:"url,omitempty"`
|
|
Method string `json:"method,omitempty"`
|
|
Data string `json:"data,omitempty"`
|
|
QueryString string `json:"query_string,omitempty"`
|
|
Cookies string `json:"cookies,omitempty"`
|
|
Headers map[string]string `json:"headers,omitempty"`
|
|
Env map[string]string `json:"env,omitempty"`
|
|
}
|
|
|
|
var sensitiveHeaders = map[string]struct{}{
|
|
"_csrf": {},
|
|
"_csrf_token": {},
|
|
"_session": {},
|
|
"_xsrf": {},
|
|
"Api-Key": {},
|
|
"Apikey": {},
|
|
"Auth": {},
|
|
"Authorization": {},
|
|
"Cookie": {},
|
|
"Credentials": {},
|
|
"Csrf": {},
|
|
"Csrf-Token": {},
|
|
"Csrftoken": {},
|
|
"Ip-Address": {},
|
|
"Passwd": {},
|
|
"Password": {},
|
|
"Private-Key": {},
|
|
"Privatekey": {},
|
|
"Proxy-Authorization": {},
|
|
"Remote-Addr": {},
|
|
"Secret": {},
|
|
"Session": {},
|
|
"Sessionid": {},
|
|
"Token": {},
|
|
"User-Session": {},
|
|
"X-Api-Key": {},
|
|
"X-Csrftoken": {},
|
|
"X-Forwarded-For": {},
|
|
"X-Real-Ip": {},
|
|
"XSRF-TOKEN": {},
|
|
}
|
|
|
|
// NewRequest returns a new Sentry Request from the given http.Request.
|
|
//
|
|
// NewRequest avoids operations that depend on network access. In particular, it
|
|
// does not read r.Body.
|
|
func NewRequest(r *http.Request) *Request {
|
|
prot := protocol.SchemeHTTP
|
|
if r.TLS != nil || r.Header.Get("X-Forwarded-Proto") == "https" {
|
|
prot = protocol.SchemeHTTPS
|
|
}
|
|
url := fmt.Sprintf("%s://%s%s", prot, r.Host, r.URL.Path)
|
|
|
|
var cookies string
|
|
var env map[string]string
|
|
headers := map[string]string{}
|
|
|
|
if client := CurrentHub().Client(); client != nil && client.options.SendDefaultPII {
|
|
// We read only the first Cookie header because of the specification:
|
|
// https://tools.ietf.org/html/rfc6265#section-5.4
|
|
// When the user agent generates an HTTP request, the user agent MUST NOT
|
|
// attach more than one Cookie header field.
|
|
cookies = r.Header.Get("Cookie")
|
|
|
|
headers = make(map[string]string, len(r.Header))
|
|
for k, v := range r.Header {
|
|
headers[k] = strings.Join(v, ",")
|
|
}
|
|
|
|
if addr, port, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
|
env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
|
|
}
|
|
} else {
|
|
for k, v := range r.Header {
|
|
if _, ok := sensitiveHeaders[k]; !ok {
|
|
headers[k] = strings.Join(v, ",")
|
|
}
|
|
}
|
|
}
|
|
|
|
headers["Host"] = r.Host
|
|
|
|
return &Request{
|
|
URL: url,
|
|
Method: r.Method,
|
|
QueryString: r.URL.RawQuery,
|
|
Cookies: cookies,
|
|
Headers: headers,
|
|
Env: env,
|
|
}
|
|
}
|
|
|
|
// Mechanism is the mechanism by which an exception was generated and handled.
|
|
type Mechanism struct {
|
|
Type string `json:"type"`
|
|
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.
|
|
// from a panic.
|
|
func (m *Mechanism) SetUnhandled() {
|
|
m.Handled = Pointer(false)
|
|
}
|
|
|
|
// Exception specifies an error that occurred.
|
|
type Exception struct {
|
|
Type string `json:"type,omitempty"` // used as the main issue title
|
|
Value string `json:"value,omitempty"` // used as the main issue subtitle
|
|
Module string `json:"module,omitempty"`
|
|
ThreadID uint64 `json:"thread_id,omitempty"`
|
|
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
|
|
Mechanism *Mechanism `json:"mechanism,omitempty"`
|
|
}
|
|
|
|
// SDKMetaData is a struct to stash data which is needed at some point in the SDK's event processing pipeline
|
|
// but which shouldn't get send to Sentry.
|
|
type SDKMetaData struct {
|
|
dsc DynamicSamplingContext
|
|
}
|
|
|
|
// Contains information about how the name of the transaction was determined.
|
|
type TransactionInfo struct {
|
|
Source TransactionSource `json:"source,omitempty"`
|
|
}
|
|
|
|
// The DebugMeta interface is not used in Golang apps, but may be populated
|
|
// when proxying Events from other platforms, like iOS, Android, and the
|
|
// Web. (See: https://develop.sentry.dev/sdk/event-payloads/debugmeta/ ).
|
|
type DebugMeta struct {
|
|
SdkInfo *DebugMetaSdkInfo `json:"sdk_info,omitempty"`
|
|
Images []DebugMetaImage `json:"images,omitempty"`
|
|
}
|
|
|
|
type DebugMetaSdkInfo struct {
|
|
SdkName string `json:"sdk_name,omitempty"`
|
|
VersionMajor int `json:"version_major,omitempty"`
|
|
VersionMinor int `json:"version_minor,omitempty"`
|
|
VersionPatchlevel int `json:"version_patchlevel,omitempty"`
|
|
}
|
|
|
|
type DebugMetaImage struct {
|
|
Type string `json:"type,omitempty"` // all
|
|
ImageAddr string `json:"image_addr,omitempty"` // macho,elf,pe
|
|
ImageSize int `json:"image_size,omitempty"` // macho,elf,pe
|
|
DebugID string `json:"debug_id,omitempty"` // macho,elf,pe,wasm,sourcemap
|
|
DebugFile string `json:"debug_file,omitempty"` // macho,elf,pe,wasm
|
|
CodeID string `json:"code_id,omitempty"` // macho,elf,pe,wasm
|
|
CodeFile string `json:"code_file,omitempty"` // macho,elf,pe,wasm,sourcemap
|
|
ImageVmaddr string `json:"image_vmaddr,omitempty"` // macho,elf,pe
|
|
Arch string `json:"arch,omitempty"` // macho,elf,pe
|
|
UUID string `json:"uuid,omitempty"` // proguard
|
|
}
|
|
|
|
// EventID is a hexadecimal string representing a unique uuid4 for an Event.
|
|
// An EventID must be 32 characters long, lowercase and not have any dashes.
|
|
type EventID string
|
|
|
|
type Context = map[string]interface{}
|
|
|
|
// Event is the fundamental data structure that is sent to Sentry.
|
|
type Event struct {
|
|
Breadcrumbs []*Breadcrumb `json:"breadcrumbs,omitempty"`
|
|
Contexts map[string]Context `json:"contexts,omitempty"`
|
|
Dist string `json:"dist,omitempty"`
|
|
Environment string `json:"environment,omitempty"`
|
|
EventID EventID `json:"event_id,omitempty"`
|
|
Extra map[string]interface{} `json:"extra,omitempty"`
|
|
Fingerprint []string `json:"fingerprint,omitempty"`
|
|
Level Level `json:"level,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
Platform string `json:"platform,omitempty"`
|
|
Release string `json:"release,omitempty"`
|
|
Sdk SdkInfo `json:"sdk,omitempty"`
|
|
ServerName string `json:"server_name,omitempty"`
|
|
Threads []Thread `json:"threads,omitempty"`
|
|
Tags map[string]string `json:"tags,omitempty"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Transaction string `json:"transaction,omitempty"`
|
|
User User `json:"user,omitempty"`
|
|
Logger string `json:"logger,omitempty"`
|
|
Modules map[string]string `json:"modules,omitempty"`
|
|
Request *Request `json:"request,omitempty"`
|
|
Exception []Exception `json:"exception,omitempty"`
|
|
DebugMeta *DebugMeta `json:"debug_meta,omitempty"`
|
|
Attachments []*Attachment `json:"-"`
|
|
|
|
// The fields below are only relevant for transactions.
|
|
|
|
Type string `json:"type,omitempty"`
|
|
StartTime time.Time `json:"start_timestamp"`
|
|
Spans []*Span `json:"spans,omitempty"`
|
|
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 only relevant for logs
|
|
Logs []Log `json:"items,omitempty"`
|
|
|
|
// The fields below are not part of the final JSON payload.
|
|
|
|
sdkMetaData SDKMetaData
|
|
}
|
|
|
|
// SetException appends the unwrapped errors to the event's exception list.
|
|
//
|
|
// maxErrorDepth is the maximum depth of the error chain we will look
|
|
// into while unwrapping the errors. If maxErrorDepth is -1, we will
|
|
// unwrap all errors in the chain.
|
|
func (e *Event) SetException(exception error, maxErrorDepth int) {
|
|
if exception == nil {
|
|
return
|
|
}
|
|
|
|
exceptions := convertErrorToExceptions(exception, maxErrorDepth)
|
|
if len(exceptions) == 0 {
|
|
return
|
|
}
|
|
|
|
e.Exception = exceptions
|
|
}
|
|
|
|
// ToEnvelope converts the Event to a Sentry envelope.
|
|
// This includes the event data and any attachments as separate envelope items.
|
|
func (e *Event) ToEnvelope(dsn *protocol.Dsn) (*protocol.Envelope, error) {
|
|
return e.ToEnvelopeWithTime(dsn, time.Now())
|
|
}
|
|
|
|
// ToEnvelopeWithTime converts the Event to a Sentry envelope with a specific sentAt time.
|
|
// This is primarily useful for testing with predictable timestamps.
|
|
func (e *Event) ToEnvelopeWithTime(dsn *protocol.Dsn, sentAt time.Time) (*protocol.Envelope, error) {
|
|
// Create envelope header with trace context
|
|
trace := make(map[string]string)
|
|
if dsc := e.sdkMetaData.dsc; dsc.HasEntries() {
|
|
for k, v := range dsc.Entries {
|
|
trace[k] = v
|
|
}
|
|
}
|
|
|
|
header := &protocol.EnvelopeHeader{
|
|
EventID: string(e.EventID),
|
|
SentAt: sentAt,
|
|
Trace: trace,
|
|
}
|
|
|
|
if dsn != nil {
|
|
header.Dsn = dsn.String()
|
|
}
|
|
|
|
header.Sdk = &e.Sdk
|
|
|
|
envelope := protocol.NewEnvelope(header)
|
|
|
|
eventBody, err := json.Marshal(e)
|
|
if err != nil {
|
|
// Try fallback: remove problematic fields and retry
|
|
e.Breadcrumbs = nil
|
|
e.Contexts = nil
|
|
e.Extra = map[string]interface{}{
|
|
"info": fmt.Sprintf("Could not encode original event as JSON. "+
|
|
"Succeeded by removing Breadcrumbs, Contexts and Extra. "+
|
|
"Please verify the data you attach to the scope. "+
|
|
"Error: %s", err),
|
|
}
|
|
|
|
eventBody, err = json.Marshal(e)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("event could not be marshaled even with fallback: %w", err)
|
|
}
|
|
|
|
DebugLogger.Printf("Event marshaling succeeded with fallback after removing problematic fields")
|
|
}
|
|
|
|
var mainItem *protocol.EnvelopeItem
|
|
switch e.Type {
|
|
case transactionType:
|
|
mainItem = protocol.NewEnvelopeItem(protocol.EnvelopeItemTypeTransaction, eventBody)
|
|
case checkInType:
|
|
mainItem = protocol.NewEnvelopeItem(protocol.EnvelopeItemTypeCheckIn, eventBody)
|
|
case logEvent.Type:
|
|
mainItem = protocol.NewLogItem(len(e.Logs), eventBody)
|
|
default:
|
|
mainItem = protocol.NewEnvelopeItem(protocol.EnvelopeItemTypeEvent, eventBody)
|
|
}
|
|
|
|
envelope.AddItem(mainItem)
|
|
for _, attachment := range e.Attachments {
|
|
attachmentItem := protocol.NewAttachmentItem(attachment.Filename, attachment.ContentType, attachment.Payload)
|
|
envelope.AddItem(attachmentItem)
|
|
}
|
|
|
|
return envelope, nil
|
|
}
|
|
|
|
// TODO: Event.Contexts map[string]interface{} => map[string]EventContext,
|
|
// to prevent accidentally storing T when we mean *T.
|
|
// For example, the TraceContext must be stored as *TraceContext to pick up the
|
|
// MarshalJSON method (and avoid copying).
|
|
// type EventContext interface{ EventContext() }
|
|
|
|
// MarshalJSON converts the Event struct to JSON.
|
|
func (e *Event) MarshalJSON() ([]byte, error) {
|
|
// We want to omit time.Time zero values, otherwise the server will try to
|
|
// interpret dates too far in the past. However, encoding/json doesn't
|
|
// support the "omitempty" option for struct types. See
|
|
// https://golang.org/issues/11939.
|
|
//
|
|
// We overcome the limitation and achieve what we want by shadowing fields
|
|
// and a few type tricks.
|
|
if e.Type == transactionType {
|
|
return e.transactionMarshalJSON()
|
|
}
|
|
|
|
if e.Type == checkInType {
|
|
return e.checkInMarshalJSON()
|
|
}
|
|
return e.defaultMarshalJSON()
|
|
}
|
|
|
|
func (e *Event) defaultMarshalJSON() ([]byte, error) {
|
|
// event aliases Event to allow calling json.Marshal without an infinite
|
|
// loop. It preserves all fields while none of the attached methods.
|
|
type event Event
|
|
|
|
// errorEvent is like Event with shadowed fields for customizing JSON
|
|
// marshaling.
|
|
type errorEvent struct {
|
|
*event
|
|
|
|
// Timestamp shadows the original Timestamp field. It allows us to
|
|
// include the timestamp when non-zero and omit it otherwise.
|
|
Timestamp json.RawMessage `json:"timestamp,omitempty"`
|
|
|
|
// The fields below are not part of error events and only make sense to
|
|
// be sent for transactions. They shadow the respective fields in Event
|
|
// and are meant to remain nil, triggering the omitempty behavior.
|
|
|
|
Type json.RawMessage `json:"type,omitempty"`
|
|
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
|
|
Spans json.RawMessage `json:"spans,omitempty"`
|
|
TransactionInfo json.RawMessage `json:"transaction_info,omitempty"`
|
|
}
|
|
|
|
x := errorEvent{event: (*event)(e)}
|
|
if !e.Timestamp.IsZero() {
|
|
b, err := e.Timestamp.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
x.Timestamp = b
|
|
}
|
|
return json.Marshal(x)
|
|
}
|
|
|
|
func (e *Event) transactionMarshalJSON() ([]byte, error) {
|
|
// event aliases Event to allow calling json.Marshal without an infinite
|
|
// loop. It preserves all fields while none of the attached methods.
|
|
type event Event
|
|
|
|
// transactionEvent is like Event with shadowed fields for customizing JSON
|
|
// marshaling.
|
|
type transactionEvent struct {
|
|
*event
|
|
|
|
// The fields below shadow the respective fields in Event. They allow us
|
|
// to include timestamps when non-zero and omit them otherwise.
|
|
|
|
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
|
|
Timestamp json.RawMessage `json:"timestamp,omitempty"`
|
|
}
|
|
|
|
x := transactionEvent{event: (*event)(e)}
|
|
if !e.Timestamp.IsZero() {
|
|
b, err := e.Timestamp.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
x.Timestamp = b
|
|
}
|
|
if !e.StartTime.IsZero() {
|
|
b, err := e.StartTime.MarshalJSON()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
x.StartTime = b
|
|
}
|
|
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,
|
|
FailureIssueThreshold: e.MonitorConfig.FailureIssueThreshold,
|
|
RecoveryThreshold: e.MonitorConfig.RecoveryThreshold,
|
|
}
|
|
}
|
|
|
|
return json.Marshal(checkIn)
|
|
}
|
|
|
|
func (e *Event) toCategory() ratelimit.Category {
|
|
switch e.Type {
|
|
case "":
|
|
return ratelimit.CategoryError
|
|
case transactionType:
|
|
return ratelimit.CategoryTransaction
|
|
case logEvent.Type:
|
|
return ratelimit.CategoryLog
|
|
case checkInType:
|
|
return ratelimit.CategoryMonitor
|
|
default:
|
|
return ratelimit.CategoryUnknown
|
|
}
|
|
}
|
|
|
|
// NewEvent creates a new Event.
|
|
func NewEvent() *Event {
|
|
return &Event{
|
|
Contexts: make(map[string]Context),
|
|
Extra: make(map[string]interface{}),
|
|
Tags: make(map[string]string),
|
|
Modules: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
// Thread specifies threads that were running at the time of an event.
|
|
type Thread struct {
|
|
ID string `json:"id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
|
|
Crashed bool `json:"crashed,omitempty"`
|
|
Current bool `json:"current,omitempty"`
|
|
}
|
|
|
|
// EventHint contains information that can be associated with an Event.
|
|
type EventHint struct {
|
|
Data interface{}
|
|
EventID string
|
|
OriginalException error
|
|
RecoveredException interface{}
|
|
Context context.Context
|
|
Request *http.Request
|
|
Response *http.Response
|
|
}
|
|
|
|
type Log struct {
|
|
Timestamp time.Time `json:"timestamp,omitempty"`
|
|
TraceID TraceID `json:"trace_id,omitempty"`
|
|
Level LogLevel `json:"level"`
|
|
Severity int `json:"severity_number,omitempty"`
|
|
Body string `json:"body,omitempty"`
|
|
Attributes map[string]Attribute `json:"attributes,omitempty"`
|
|
}
|
|
|
|
type AttrType string
|
|
|
|
const (
|
|
AttributeInvalid AttrType = ""
|
|
AttributeBool AttrType = "boolean"
|
|
AttributeInt AttrType = "integer"
|
|
AttributeFloat AttrType = "double"
|
|
AttributeString AttrType = "string"
|
|
)
|
|
|
|
type Attribute struct {
|
|
Value any `json:"value"`
|
|
Type AttrType `json:"type"`
|
|
}
|