Files
amcs/vendor/github.com/getsentry/sentry-go/interfaces.go
T
Hein 1adf50e3db
CI / build-and-test (push) Failing after 1s
Release / release (push) Failing after 19m26s
fix(go.sum): update ResolveSpec dependency to v1.0.87
2026-06-23 13:17:16 +02:00

936 lines
30 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/debuglog"
"github.com/getsentry/sentry-go/internal/protocol"
"github.com/getsentry/sentry-go/internal/ratelimit"
)
const errorType = ""
const eventType = "event"
const transactionType = "transaction"
const checkInType = "check_in"
var logEvent = struct {
Type string
ContentType string
}{
"log",
"application/vnd.sentry.items.log+json",
}
var traceMetricEvent = struct {
Type string
ContentType string
}{
"trace_metric",
"application/vnd.sentry.items.trace-metric+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,omitzero"`
}
// TODO: provide constants for known breadcrumb types.
// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types.
// 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
// LFatal defines the [sentry.LogLevel] for the log entry. This only sets
// the level to fatal, but does not panic or exit.
LFatal() 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
// StringSlice adds a string slice attribute to the LogEntry.
StringSlice(key string, value []string) 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
// Int64Slice adds an int64 slice attribute to the LogEntry.
Int64Slice(key string, value []int64) LogEntry
// Int64 adds an int64 attribute to the LogEntry.
Int64(key string, value int64) LogEntry
// Float64Slice adds a float64 slice attribute to the LogEntry.
Float64Slice(key string, value []float64) LogEntry
// Float64 adds a float64 attribute to the LogEntry.
Float64(key string, value float64) LogEntry
// BoolSlice adds a bool slice attribute to the LogEntry.
BoolSlice(key string, value []bool) 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{})
}
// Meter provides an interface for recording metrics.
type Meter interface {
// WithCtx returns a new Meter that uses the given context for trace/span association.
WithCtx(ctx context.Context) Meter
// SetAttributes allows attaching parameters to the meter using the attribute API.
// These attributes will be included in all subsequent metrics.
SetAttributes(attrs ...attribute.Builder)
// Count records a count metric.
Count(name string, count int64, opts ...MeterOption)
// Gauge records a gauge metric.
Gauge(name string, value float64, opts ...MeterOption)
// Distribution records a distribution metric.
Distribution(name string, sample float64, opts ...MeterOption)
}
// MeterOption configures a metric recording call.
type MeterOption func(*meterOptions)
type meterOptions struct {
unit string
scope *Scope
attributes map[string]attribute.Value
}
// WithUnit sets the unit for the metric (e.g., "millisecond", "byte").
func WithUnit(unit string) MeterOption {
return func(o *meterOptions) {
o.unit = unit
}
}
// WithScopeOverride sets a custom scope for the metric, overriding the default scope from the hub.
func WithScopeOverride(scope *Scope) MeterOption {
return func(o *meterOptions) {
o.scope = scope
}
}
// WithAttributes sets attributes for the metric.
func WithAttributes(attrs ...attribute.Builder) MeterOption {
return func(o *meterOptions) {
if o.attributes == nil {
o.attributes = make(map[string]attribute.Value, len(attrs))
}
for _, a := range attrs {
if a.Value.Type() == attribute.INVALID {
debuglog.Printf("invalid attribute: %v", a)
continue
}
o.attributes[a.Key] = a.Value
}
}
}
// 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"`
}
func sendDefaultPIIEnabled(client *Client) bool {
return client != nil && client.options.SendDefaultPII
}
func newRequest(r *http.Request, sendDefaultPII bool) *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 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 !IsSensitiveHeader(k) {
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,
}
}
// 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 {
return newRequest(r, sendDefaultPIIEnabled(CurrentHub().Client()))
}
// 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"`
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,omitzero"`
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,omitzero"`
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:"-"`
// The fields below are only relevant for metrics
Metrics []Metric `json:"-"`
// The fields below are not part of the final JSON payload.
sdkMetaData SDKMetaData
// Pre-serialized copies of mutable fields, set by MakeSerializationSafe.
serializedTags json.RawMessage
serializedContexts json.RawMessage
serializedBreadcrumbs json.RawMessage
serializedException json.RawMessage
serializedUser json.RawMessage
serializationSafe bool
}
// 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
}
// safeMarshal wraps json.Marshal with a recover guard.
//
// we shouldn't panic since we already pre serialized all user mutable fields, but using this just to be safe.
func (e *Event) safeMarshal() (b []byte, err error) {
defer func() {
if r := recover(); r != nil {
b = nil
err = fmt.Errorf("panic during event marshaling: %v", r)
}
}()
return json.Marshal(e)
}
// ToEnvelopeItem converts the Event to a Sentry envelope item.
func (e *Event) ToEnvelopeItem() (item *protocol.EnvelopeItem, err error) {
eventBody, err := e.safeMarshal()
if err != nil {
return nil, fmt.Errorf("could not encode event as JSON, skipping delivery: %w", err)
}
// TODO: all event types should be abstracted to implement EnvelopeItemConvertible and convert themselves.
switch e.Type {
case transactionType:
item = protocol.NewTransactionItem(e.GetSpanCount(), eventBody)
case checkInType:
item = protocol.NewEnvelopeItem(protocol.EnvelopeItemTypeCheckIn, eventBody)
case logEvent.Type:
item = protocol.NewLogItem(len(e.Logs), eventBody)
case traceMetricEvent.Type:
item = protocol.NewTraceMetricItem(len(e.Metrics), eventBody)
default:
item = protocol.NewEnvelopeItem(protocol.EnvelopeItemTypeEvent, eventBody)
}
return item, nil
}
// ToEnvelope converts the Event to a Sentry envelope.
func (e *Event) ToEnvelope(header *protocol.EnvelopeHeader) (*protocol.Envelope, error) {
item, err := e.ToEnvelopeItem()
if err != nil {
return nil, err
}
envelope := protocol.NewEnvelope(header)
envelope.AddItem(item)
for _, attachment := range e.Attachments {
attachmentItem := protocol.NewAttachmentItem(attachment.Filename, attachment.ContentType, attachment.Payload)
envelope.AddItem(attachmentItem)
}
return envelope, nil
}
// GetCategory returns the rate limit category for this event.
func (e *Event) GetCategory() ratelimit.Category {
return e.toCategory()
}
// GetEventID returns the event ID.
func (e *Event) GetEventID() string {
return string(e.EventID)
}
// GetSdkInfo returns SDK information for the envelope header.
func (e *Event) GetSdkInfo() *protocol.SdkInfo {
return &e.Sdk
}
// GetDynamicSamplingContext returns trace context for the envelope header.
func (e *Event) GetDynamicSamplingContext() map[string]string {
trace := make(map[string]string)
if dsc := e.sdkMetaData.dsc; dsc.HasEntries() {
for k, v := range dsc.Entries {
trace[k] = v
}
}
return trace
}
// GetSpanCount returns the number of spans in the transaction including the transaction itself. It is used for client
// reports. Returns 0 for non-transaction events.
func (e *Event) GetSpanCount() int {
if e.Type != transactionType {
return 0
}
return len(e.Spans) + 1
}
// GetLogByteSize returns the approximate total byte size of all logs in the event. It is used for client
// reports. Returns 0 for non-log events.
func (e *Event) GetLogByteSize() int {
if e.Type != logEvent.Type {
return 0
}
var size int
for i := range e.Logs {
size += e.Logs[i].ApproximateSize()
}
return size
}
// 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) {
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
// Use pre-serialized bytes for fields that contain user mutable data.
if e.hasPreSerializedFields() {
return e.preSerializedMarshalJSON()
}
if e.Type == transactionType {
return json.Marshal(struct{ *event }{(*event)(e)})
}
// metrics and logs should be serialized under the same `items` json field.
if e.Type == logEvent.Type {
type logEvent struct {
*event
Items []Log `json:"items,omitempty"`
Type json.RawMessage `json:"type,omitempty"`
}
return json.Marshal(logEvent{event: (*event)(e), Items: e.Logs})
}
if e.Type == traceMetricEvent.Type {
type metricEvent struct {
*event
Items []Metric `json:"items,omitempty"`
Type json.RawMessage `json:"type,omitempty"`
}
return json.Marshal(metricEvent{event: (*event)(e), Items: e.Metrics})
}
// errorEvent is like Event with shadowed fields for customizing JSON
// marshaling.
type errorEvent struct {
*event
// 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)}
return json.Marshal(x)
}
func (e *Event) hasPreSerializedFields() bool {
return e.serializationSafe
}
// preSerializedMarshalJSON handles marshaling when MakeSerializationSafe has
// pre-serialized mutable fields. Shadow structs ensure the json.RawMessage
// bytes are emitted directly, overriding the original fields.
func (e *Event) preSerializedMarshalJSON() ([]byte, error) {
type event Event
if e.Type == transactionType {
type safeTransaction struct {
*event
Tags json.RawMessage `json:"tags,omitempty"`
Contexts json.RawMessage `json:"contexts,omitempty"`
Breadcrumbs json.RawMessage `json:"breadcrumbs,omitempty"`
Exception json.RawMessage `json:"exception,omitempty"`
User json.RawMessage `json:"user,omitempty"`
}
return json.Marshal(safeTransaction{
event: (*event)(e),
Tags: e.serializedTags,
Contexts: e.serializedContexts,
Breadcrumbs: e.serializedBreadcrumbs,
Exception: e.serializedException,
User: e.serializedUser,
})
}
// Error event: also shadow transaction-only fields to exclude them.
type safeErrorEvent struct {
*event
Tags json.RawMessage `json:"tags,omitempty"`
Contexts json.RawMessage `json:"contexts,omitempty"`
Breadcrumbs json.RawMessage `json:"breadcrumbs,omitempty"`
Exception json.RawMessage `json:"exception,omitempty"`
User json.RawMessage `json:"user,omitempty"`
Type json.RawMessage `json:"type,omitempty"`
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
Spans json.RawMessage `json:"spans,omitempty"`
TransactionInfo json.RawMessage `json:"transaction_info,omitempty"`
}
return json.Marshal(safeErrorEvent{
event: (*event)(e),
Tags: e.serializedTags,
Contexts: e.serializedContexts,
Breadcrumbs: e.serializedBreadcrumbs,
Exception: e.serializedException,
User: e.serializedUser,
})
}
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 errorType:
return ratelimit.CategoryError
case transactionType:
return ratelimit.CategoryTransaction
case logEvent.Type:
return ratelimit.CategoryLog
case checkInType:
return ratelimit.CategoryMonitor
case traceMetricEvent.Type:
return ratelimit.CategoryTraceMetric
default:
return ratelimit.CategoryUnknown
}
}
// NewEvent creates a new Event.
func NewEvent() *Event {
return &Event{
Contexts: make(map[string]Context),
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,omitzero"`
TraceID TraceID `json:"trace_id"`
SpanID SpanID `json:"span_id,omitzero"`
Level LogLevel `json:"level"`
Severity int `json:"severity_number,omitempty"`
Body string `json:"body"`
Attributes map[string]attribute.Value `json:"attributes,omitempty"`
// approximateSize is the pre-computed approximate size in bytes.
approximateSize int
}
// ApproximateSize returns the pre-computed approximate serialized size in bytes.
func (l *Log) ApproximateSize() int {
return l.approximateSize
}
// computeLogSize estimates the serialized JSON size of a log entry.
func computeLogSize(l *Log) int {
// Base overhead: timestamp, trace_id, level, severity, JSON structure
size := len(l.Body) + 60
for k, v := range l.Attributes {
// Key + type/value JSON overhead
size += len(k) + 20
s := fmt.Sprint(v.AsInterface())
size += len(s)
}
return size
}
// MakeSerializationSafe is a no-op for Log, all fields are passed from the safe attribute API.
func (l *Log) MakeSerializationSafe() {}
// GetCategory returns the rate limit category for logs.
func (l *Log) GetCategory() ratelimit.Category {
return ratelimit.CategoryLog
}
// GetEventID returns empty string (event ID set when batching).
func (l *Log) GetEventID() string {
return ""
}
// GetSdkInfo returns nil (SDK info set when batching).
func (l *Log) GetSdkInfo() *protocol.SdkInfo {
return nil
}
// GetDynamicSamplingContext returns nil (trace context set when batching).
func (l *Log) GetDynamicSamplingContext() map[string]string {
return nil
}
type MetricType string
const (
MetricTypeInvalid MetricType = ""
MetricTypeCounter MetricType = "counter"
MetricTypeGauge MetricType = "gauge"
MetricTypeDistribution MetricType = "distribution"
)
type Metric struct {
Timestamp time.Time `json:"timestamp,omitzero"`
TraceID TraceID `json:"trace_id"`
SpanID SpanID `json:"span_id,omitzero"`
Type MetricType `json:"type"`
Name string `json:"name"`
Value MetricValue `json:"value"`
Unit string `json:"unit,omitempty"`
Attributes map[string]attribute.Value `json:"attributes,omitempty"`
}
// MakeSerializationSafe is a no-op for Metric, all fields are passed from the safe attribute API.
func (m *Metric) MakeSerializationSafe() {}
// GetCategory returns the rate limit category for metrics.
func (m *Metric) GetCategory() ratelimit.Category {
return ratelimit.CategoryTraceMetric
}
// GetEventID returns empty string (event ID set when batching).
func (m *Metric) GetEventID() string {
return ""
}
// GetSdkInfo returns nil (SDK info set when batching).
func (m *Metric) GetSdkInfo() *protocol.SdkInfo {
return nil
}
// GetDynamicSamplingContext returns nil (trace context set when batching).
func (m *Metric) GetDynamicSamplingContext() map[string]string {
return nil
}
// MetricValue stores metric values with full precision.
// It supports int64 (for counters) and float64 (for gauges and distributions).
type MetricValue struct {
value attribute.Value
}
// Int64MetricValue creates a MetricValue from an int64.
// Used for counter metrics to preserve full int64 precision.
func Int64MetricValue(v int64) MetricValue {
return MetricValue{value: attribute.Int64Value(v)}
}
// Float64MetricValue creates a MetricValue from a float64.
// Used for gauge and distribution metrics.
func Float64MetricValue(v float64) MetricValue {
return MetricValue{value: attribute.Float64Value(v)}
}
// Type returns the type of the stored value (attribute.INT64 or attribute.FLOAT64).
func (v MetricValue) Type() attribute.Type {
return v.value.Type()
}
// Int64 returns the value as int64 if it holds an int64.
// The second return value indicates whether the type matched.
func (v MetricValue) Int64() (int64, bool) {
if v.value.Type() == attribute.INT64 {
return v.value.AsInt64(), true
}
return 0, false
}
// Float64 returns the value as float64 if it holds a float64.
// The second return value indicates whether the type matched.
func (v MetricValue) Float64() (float64, bool) {
if v.value.Type() == attribute.FLOAT64 {
return v.value.AsFloat64(), true
}
return 0, false
}
// AsInterface returns the value as int64 or float64.
// Use type assertion or type switch to handle the result.
func (v MetricValue) AsInterface() any {
return v.value.AsInterface()
}
// MarshalJSON serializes the value as a bare number.
func (v MetricValue) MarshalJSON() ([]byte, error) {
return json.Marshal(v.value.AsInterface())
}
// MakeSerializationSafe pre-serializes all fields containing user mutable data to json.RawMessage, preventing race
// conditions when the event is later serialized on a background goroutine.
func (e *Event) MakeSerializationSafe() {
if len(e.Tags) > 0 {
if b, err := json.Marshal(e.Tags); err == nil {
e.serializedTags = b
}
}
if len(e.Contexts) > 0 {
if b, err := json.Marshal(e.Contexts); err == nil {
e.serializedContexts = b
}
}
if len(e.Breadcrumbs) > 0 {
if b, err := json.Marshal(e.Breadcrumbs); err == nil {
e.serializedBreadcrumbs = b
}
}
if len(e.Exception) > 0 {
if b, err := json.Marshal(e.Exception); err == nil {
e.serializedException = b
}
}
if !e.User.IsEmpty() {
if b, err := json.Marshal(e.User); err == nil {
e.serializedUser = b
}
}
for _, span := range e.Spans {
span.makeSerializationSafe()
}
e.serializationSafe = true
}