Scaffold Phase 0: agent -> Redpanda -> ingest -> ClickHouse -> api -> web
End-to-end log pipeline for Linux hosts, per /docs/architecture.md: - proto: shared gRPC contract (agent <-> ingest), Go bindings checked in - agent: Rust, musl-targeted, journald/file sourcing, RFC5424 parser, mTLS gRPC client, no required config for the common case - ingest: Go, single binary with --mode server|consumer|all; gRPC front end forwards to Redpanda unchanged, consumer normalizes and batch-writes to ClickHouse with at-least-once delivery - storage: ClickHouse schema + a plain SQL-file migration runner - api: minimal SELECT-only query endpoint, plain REST (not gRPC+gateway yet -- see api/README.md) - web: SvelteKit static SPA, one query page - transport: Redpanda compose + topic provisioning - cli: sentryctl ping stub - hack/dev-certs: throwaway CA + cert generation for local mTLS - root docker-compose.yml + docs/phase-0-runbook.md tie it together Not yet run end-to-end against real Docker/ClickHouse/Redpanda -- see the runbook's caveats section before relying on this working as-is.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# proto
|
||||
|
||||
Shared `.proto` contracts. Source of truth for the agent↔ingest gRPC
|
||||
service; each language generates its own bindings from these files rather
|
||||
than sharing generated code across languages.
|
||||
|
||||
- `sentry/logs/v1/logs.proto` — `LogIngest.PushBatch`, the only RPC an
|
||||
agent ever calls.
|
||||
|
||||
## Go bindings
|
||||
|
||||
Go is the one language here with pre-generated, checked-in bindings
|
||||
(`sentry/logs/v1/logs.pb.go`, `logs_grpc.pb.go`), living in this directory
|
||||
as its own module (`github.com/sentry/sentry/proto`) that `/ingest` and
|
||||
`/api` depend on via a local `replace` directive in their `go.mod`. Rust
|
||||
(`/agent`) instead generates its bindings at build time via `tonic-build`
|
||||
(see `agent/sentry-agent/build.rs`) — no checked-in Rust output.
|
||||
|
||||
To regenerate the Go bindings after changing `logs.proto`:
|
||||
|
||||
```sh
|
||||
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
||||
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
|
||||
|
||||
cd proto
|
||||
protoc --go_out=. --go_opt=paths=source_relative \
|
||||
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
|
||||
sentry/logs/v1/logs.proto
|
||||
go build ./...
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
module github.com/sentry/sentry/proto
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
google.golang.org/grpc v1.83.0
|
||||
google.golang.org/protobuf v1.36.12
|
||||
)
|
||||
|
||||
require (
|
||||
golang.org/x/net v0.55.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ=
|
||||
google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
@@ -0,0 +1,376 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.36.12
|
||||
// protoc v7.35.1
|
||||
// source: sentry/logs/v1/logs.proto
|
||||
|
||||
package logsv1
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
unsafe "unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// Severity follows OTel's severity number ranges (1-24), collapsed here to
|
||||
// the coarse names agents actually need to set. Numeric value stored
|
||||
// downstream may be a full OTel severity_number computed by ingest.
|
||||
type Severity int32
|
||||
|
||||
const (
|
||||
Severity_SEVERITY_UNSPECIFIED Severity = 0
|
||||
Severity_SEVERITY_TRACE Severity = 1
|
||||
Severity_SEVERITY_DEBUG Severity = 2
|
||||
Severity_SEVERITY_INFO Severity = 3
|
||||
Severity_SEVERITY_WARN Severity = 4
|
||||
Severity_SEVERITY_ERROR Severity = 5
|
||||
Severity_SEVERITY_FATAL Severity = 6
|
||||
)
|
||||
|
||||
// Enum value maps for Severity.
|
||||
var (
|
||||
Severity_name = map[int32]string{
|
||||
0: "SEVERITY_UNSPECIFIED",
|
||||
1: "SEVERITY_TRACE",
|
||||
2: "SEVERITY_DEBUG",
|
||||
3: "SEVERITY_INFO",
|
||||
4: "SEVERITY_WARN",
|
||||
5: "SEVERITY_ERROR",
|
||||
6: "SEVERITY_FATAL",
|
||||
}
|
||||
Severity_value = map[string]int32{
|
||||
"SEVERITY_UNSPECIFIED": 0,
|
||||
"SEVERITY_TRACE": 1,
|
||||
"SEVERITY_DEBUG": 2,
|
||||
"SEVERITY_INFO": 3,
|
||||
"SEVERITY_WARN": 4,
|
||||
"SEVERITY_ERROR": 5,
|
||||
"SEVERITY_FATAL": 6,
|
||||
}
|
||||
)
|
||||
|
||||
func (x Severity) Enum() *Severity {
|
||||
p := new(Severity)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x Severity) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (Severity) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_sentry_logs_v1_logs_proto_enumTypes[0].Descriptor()
|
||||
}
|
||||
|
||||
func (Severity) Type() protoreflect.EnumType {
|
||||
return &file_sentry_logs_v1_logs_proto_enumTypes[0]
|
||||
}
|
||||
|
||||
func (x Severity) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use Severity.Descriptor instead.
|
||||
func (Severity) EnumDescriptor() ([]byte, []int) {
|
||||
return file_sentry_logs_v1_logs_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
type LogRecord struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Unix epoch nanoseconds, set by the agent at time of read (not parse or
|
||||
// send time) to preserve original ordering as closely as possible.
|
||||
TimestampUnixNano int64 `protobuf:"varint,1,opt,name=timestamp_unix_nano,json=timestampUnixNano,proto3" json:"timestamp_unix_nano,omitempty"`
|
||||
// Hostname the agent is running on. Agent fills this from its own config
|
||||
// or system hostname; not trusted as an identity claim (mTLS client cert
|
||||
// is the identity boundary).
|
||||
Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"`
|
||||
// Logical service/unit name. For journald sources, this is typically the
|
||||
// systemd unit name; for file sources, it comes from agent config.
|
||||
Service string `protobuf:"bytes,3,opt,name=service,proto3" json:"service,omitempty"`
|
||||
Severity Severity `protobuf:"varint,4,opt,name=severity,proto3,enum=sentry.logs.v1.Severity" json:"severity,omitempty"`
|
||||
// Original, unparsed log line. Always populated, even when structured
|
||||
// fields below are also present, per the schema-on-read fallback
|
||||
// requirement in CLAUDE.md.
|
||||
Message string `protobuf:"bytes,5,opt,name=message,proto3" json:"message,omitempty"`
|
||||
// Structured fields extracted by the agent's parser (e.g. RFC 5424
|
||||
// syslog header fields). Empty when the raw-passthrough fallback fires.
|
||||
Attributes map[string]string `protobuf:"bytes,6,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *LogRecord) Reset() {
|
||||
*x = LogRecord{}
|
||||
mi := &file_sentry_logs_v1_logs_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *LogRecord) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*LogRecord) ProtoMessage() {}
|
||||
|
||||
func (x *LogRecord) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sentry_logs_v1_logs_proto_msgTypes[0]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use LogRecord.ProtoReflect.Descriptor instead.
|
||||
func (*LogRecord) Descriptor() ([]byte, []int) {
|
||||
return file_sentry_logs_v1_logs_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *LogRecord) GetTimestampUnixNano() int64 {
|
||||
if x != nil {
|
||||
return x.TimestampUnixNano
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *LogRecord) GetHost() string {
|
||||
if x != nil {
|
||||
return x.Host
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LogRecord) GetService() string {
|
||||
if x != nil {
|
||||
return x.Service
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LogRecord) GetSeverity() Severity {
|
||||
if x != nil {
|
||||
return x.Severity
|
||||
}
|
||||
return Severity_SEVERITY_UNSPECIFIED
|
||||
}
|
||||
|
||||
func (x *LogRecord) GetMessage() string {
|
||||
if x != nil {
|
||||
return x.Message
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *LogRecord) GetAttributes() map[string]string {
|
||||
if x != nil {
|
||||
return x.Attributes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PushBatchRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Agent-assigned identifier for dedup/idempotency on retry. Ingest may
|
||||
// use this to avoid double-writing a batch if a retry follows a
|
||||
// timeout on an actually-successful push.
|
||||
BatchId string `protobuf:"bytes,1,opt,name=batch_id,json=batchId,proto3" json:"batch_id,omitempty"`
|
||||
Records []*LogRecord `protobuf:"bytes,2,rep,name=records,proto3" json:"records,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *PushBatchRequest) Reset() {
|
||||
*x = PushBatchRequest{}
|
||||
mi := &file_sentry_logs_v1_logs_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PushBatchRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PushBatchRequest) ProtoMessage() {}
|
||||
|
||||
func (x *PushBatchRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sentry_logs_v1_logs_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PushBatchRequest.ProtoReflect.Descriptor instead.
|
||||
func (*PushBatchRequest) Descriptor() ([]byte, []int) {
|
||||
return file_sentry_logs_v1_logs_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *PushBatchRequest) GetBatchId() string {
|
||||
if x != nil {
|
||||
return x.BatchId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *PushBatchRequest) GetRecords() []*LogRecord {
|
||||
if x != nil {
|
||||
return x.Records
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type PushBatchResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Number of records ingest accepted. Phase 0: batches are all-or-nothing,
|
||||
// so this equals len(records) on success. Partial-acceptance semantics
|
||||
// are not implemented yet.
|
||||
Accepted uint32 `protobuf:"varint,1,opt,name=accepted,proto3" json:"accepted,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *PushBatchResponse) Reset() {
|
||||
*x = PushBatchResponse{}
|
||||
mi := &file_sentry_logs_v1_logs_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *PushBatchResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*PushBatchResponse) ProtoMessage() {}
|
||||
|
||||
func (x *PushBatchResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_sentry_logs_v1_logs_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use PushBatchResponse.ProtoReflect.Descriptor instead.
|
||||
func (*PushBatchResponse) Descriptor() ([]byte, []int) {
|
||||
return file_sentry_logs_v1_logs_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *PushBatchResponse) GetAccepted() uint32 {
|
||||
if x != nil {
|
||||
return x.Accepted
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
var File_sentry_logs_v1_logs_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_sentry_logs_v1_logs_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x19sentry/logs/v1/logs.proto\x12\x0esentry.logs.v1\"\xc3\x02\n" +
|
||||
"\tLogRecord\x12.\n" +
|
||||
"\x13timestamp_unix_nano\x18\x01 \x01(\x03R\x11timestampUnixNano\x12\x12\n" +
|
||||
"\x04host\x18\x02 \x01(\tR\x04host\x12\x18\n" +
|
||||
"\aservice\x18\x03 \x01(\tR\aservice\x124\n" +
|
||||
"\bseverity\x18\x04 \x01(\x0e2\x18.sentry.logs.v1.SeverityR\bseverity\x12\x18\n" +
|
||||
"\amessage\x18\x05 \x01(\tR\amessage\x12I\n" +
|
||||
"\n" +
|
||||
"attributes\x18\x06 \x03(\v2).sentry.logs.v1.LogRecord.AttributesEntryR\n" +
|
||||
"attributes\x1a=\n" +
|
||||
"\x0fAttributesEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"b\n" +
|
||||
"\x10PushBatchRequest\x12\x19\n" +
|
||||
"\bbatch_id\x18\x01 \x01(\tR\abatchId\x123\n" +
|
||||
"\arecords\x18\x02 \x03(\v2\x19.sentry.logs.v1.LogRecordR\arecords\"/\n" +
|
||||
"\x11PushBatchResponse\x12\x1a\n" +
|
||||
"\baccepted\x18\x01 \x01(\rR\baccepted*\x9a\x01\n" +
|
||||
"\bSeverity\x12\x18\n" +
|
||||
"\x14SEVERITY_UNSPECIFIED\x10\x00\x12\x12\n" +
|
||||
"\x0eSEVERITY_TRACE\x10\x01\x12\x12\n" +
|
||||
"\x0eSEVERITY_DEBUG\x10\x02\x12\x11\n" +
|
||||
"\rSEVERITY_INFO\x10\x03\x12\x11\n" +
|
||||
"\rSEVERITY_WARN\x10\x04\x12\x12\n" +
|
||||
"\x0eSEVERITY_ERROR\x10\x05\x12\x12\n" +
|
||||
"\x0eSEVERITY_FATAL\x10\x062]\n" +
|
||||
"\tLogIngest\x12P\n" +
|
||||
"\tPushBatch\x12 .sentry.logs.v1.PushBatchRequest\x1a!.sentry.logs.v1.PushBatchResponseB6Z4github.com/sentry/sentry/proto/sentry/logs/v1;logsv1b\x06proto3"
|
||||
|
||||
var (
|
||||
file_sentry_logs_v1_logs_proto_rawDescOnce sync.Once
|
||||
file_sentry_logs_v1_logs_proto_rawDescData []byte
|
||||
)
|
||||
|
||||
func file_sentry_logs_v1_logs_proto_rawDescGZIP() []byte {
|
||||
file_sentry_logs_v1_logs_proto_rawDescOnce.Do(func() {
|
||||
file_sentry_logs_v1_logs_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_sentry_logs_v1_logs_proto_rawDesc), len(file_sentry_logs_v1_logs_proto_rawDesc)))
|
||||
})
|
||||
return file_sentry_logs_v1_logs_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_sentry_logs_v1_logs_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_sentry_logs_v1_logs_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_sentry_logs_v1_logs_proto_goTypes = []any{
|
||||
(Severity)(0), // 0: sentry.logs.v1.Severity
|
||||
(*LogRecord)(nil), // 1: sentry.logs.v1.LogRecord
|
||||
(*PushBatchRequest)(nil), // 2: sentry.logs.v1.PushBatchRequest
|
||||
(*PushBatchResponse)(nil), // 3: sentry.logs.v1.PushBatchResponse
|
||||
nil, // 4: sentry.logs.v1.LogRecord.AttributesEntry
|
||||
}
|
||||
var file_sentry_logs_v1_logs_proto_depIdxs = []int32{
|
||||
0, // 0: sentry.logs.v1.LogRecord.severity:type_name -> sentry.logs.v1.Severity
|
||||
4, // 1: sentry.logs.v1.LogRecord.attributes:type_name -> sentry.logs.v1.LogRecord.AttributesEntry
|
||||
1, // 2: sentry.logs.v1.PushBatchRequest.records:type_name -> sentry.logs.v1.LogRecord
|
||||
2, // 3: sentry.logs.v1.LogIngest.PushBatch:input_type -> sentry.logs.v1.PushBatchRequest
|
||||
3, // 4: sentry.logs.v1.LogIngest.PushBatch:output_type -> sentry.logs.v1.PushBatchResponse
|
||||
4, // [4:5] is the sub-list for method output_type
|
||||
3, // [3:4] is the sub-list for method input_type
|
||||
3, // [3:3] is the sub-list for extension type_name
|
||||
3, // [3:3] is the sub-list for extension extendee
|
||||
0, // [0:3] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_sentry_logs_v1_logs_proto_init() }
|
||||
func file_sentry_logs_v1_logs_proto_init() {
|
||||
if File_sentry_logs_v1_logs_proto != nil {
|
||||
return
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_sentry_logs_v1_logs_proto_rawDesc), len(file_sentry_logs_v1_logs_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_sentry_logs_v1_logs_proto_goTypes,
|
||||
DependencyIndexes: file_sentry_logs_v1_logs_proto_depIdxs,
|
||||
EnumInfos: file_sentry_logs_v1_logs_proto_enumTypes,
|
||||
MessageInfos: file_sentry_logs_v1_logs_proto_msgTypes,
|
||||
}.Build()
|
||||
File_sentry_logs_v1_logs_proto = out.File
|
||||
file_sentry_logs_v1_logs_proto_goTypes = nil
|
||||
file_sentry_logs_v1_logs_proto_depIdxs = nil
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package sentry.logs.v1;
|
||||
|
||||
option go_package = "github.com/sentry/sentry/proto/sentry/logs/v1;logsv1";
|
||||
|
||||
// LogIngest is the service agents use to ship batched log records to the
|
||||
// ingest service over mTLS. Phase 0: single unary batch push. Streaming
|
||||
// (client-streaming for continuous shipping) is a likely Phase 1 upgrade
|
||||
// once backpressure/flow-control behavior is characterized.
|
||||
service LogIngest {
|
||||
rpc PushBatch(PushBatchRequest) returns (PushBatchResponse);
|
||||
}
|
||||
|
||||
// Severity follows OTel's severity number ranges (1-24), collapsed here to
|
||||
// the coarse names agents actually need to set. Numeric value stored
|
||||
// downstream may be a full OTel severity_number computed by ingest.
|
||||
enum Severity {
|
||||
SEVERITY_UNSPECIFIED = 0;
|
||||
SEVERITY_TRACE = 1;
|
||||
SEVERITY_DEBUG = 2;
|
||||
SEVERITY_INFO = 3;
|
||||
SEVERITY_WARN = 4;
|
||||
SEVERITY_ERROR = 5;
|
||||
SEVERITY_FATAL = 6;
|
||||
}
|
||||
|
||||
message LogRecord {
|
||||
// Unix epoch nanoseconds, set by the agent at time of read (not parse or
|
||||
// send time) to preserve original ordering as closely as possible.
|
||||
int64 timestamp_unix_nano = 1;
|
||||
|
||||
// Hostname the agent is running on. Agent fills this from its own config
|
||||
// or system hostname; not trusted as an identity claim (mTLS client cert
|
||||
// is the identity boundary).
|
||||
string host = 2;
|
||||
|
||||
// Logical service/unit name. For journald sources, this is typically the
|
||||
// systemd unit name; for file sources, it comes from agent config.
|
||||
string service = 3;
|
||||
|
||||
Severity severity = 4;
|
||||
|
||||
// Original, unparsed log line. Always populated, even when structured
|
||||
// fields below are also present, per the schema-on-read fallback
|
||||
// requirement in CLAUDE.md.
|
||||
string message = 5;
|
||||
|
||||
// Structured fields extracted by the agent's parser (e.g. RFC 5424
|
||||
// syslog header fields). Empty when the raw-passthrough fallback fires.
|
||||
map<string, string> attributes = 6;
|
||||
}
|
||||
|
||||
message PushBatchRequest {
|
||||
// Agent-assigned identifier for dedup/idempotency on retry. Ingest may
|
||||
// use this to avoid double-writing a batch if a retry follows a
|
||||
// timeout on an actually-successful push.
|
||||
string batch_id = 1;
|
||||
|
||||
repeated LogRecord records = 2;
|
||||
}
|
||||
|
||||
message PushBatchResponse {
|
||||
// Number of records ingest accepted. Phase 0: batches are all-or-nothing,
|
||||
// so this equals len(records) on success. Partial-acceptance semantics
|
||||
// are not implemented yet.
|
||||
uint32 accepted = 1;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.6.2
|
||||
// - protoc v7.35.1
|
||||
// source: sentry/logs/v1/logs.proto
|
||||
|
||||
package logsv1
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.64.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion9
|
||||
|
||||
const (
|
||||
LogIngest_PushBatch_FullMethodName = "/sentry.logs.v1.LogIngest/PushBatch"
|
||||
)
|
||||
|
||||
// LogIngestClient is the client API for LogIngest service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
//
|
||||
// LogIngest is the service agents use to ship batched log records to the
|
||||
// ingest service over mTLS. Phase 0: single unary batch push. Streaming
|
||||
// (client-streaming for continuous shipping) is a likely Phase 1 upgrade
|
||||
// once backpressure/flow-control behavior is characterized.
|
||||
type LogIngestClient interface {
|
||||
PushBatch(ctx context.Context, in *PushBatchRequest, opts ...grpc.CallOption) (*PushBatchResponse, error)
|
||||
}
|
||||
|
||||
type logIngestClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewLogIngestClient(cc grpc.ClientConnInterface) LogIngestClient {
|
||||
return &logIngestClient{cc}
|
||||
}
|
||||
|
||||
func (c *logIngestClient) PushBatch(ctx context.Context, in *PushBatchRequest, opts ...grpc.CallOption) (*PushBatchResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(PushBatchResponse)
|
||||
err := c.cc.Invoke(ctx, LogIngest_PushBatch_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// LogIngestServer is the server API for LogIngest service.
|
||||
// All implementations must embed UnimplementedLogIngestServer
|
||||
// for forward compatibility.
|
||||
//
|
||||
// LogIngest is the service agents use to ship batched log records to the
|
||||
// ingest service over mTLS. Phase 0: single unary batch push. Streaming
|
||||
// (client-streaming for continuous shipping) is a likely Phase 1 upgrade
|
||||
// once backpressure/flow-control behavior is characterized.
|
||||
type LogIngestServer interface {
|
||||
PushBatch(context.Context, *PushBatchRequest) (*PushBatchResponse, error)
|
||||
mustEmbedUnimplementedLogIngestServer()
|
||||
}
|
||||
|
||||
// UnimplementedLogIngestServer must be embedded to have
|
||||
// forward compatible implementations.
|
||||
//
|
||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
||||
// pointer dereference when methods are called.
|
||||
type UnimplementedLogIngestServer struct{}
|
||||
|
||||
func (UnimplementedLogIngestServer) PushBatch(context.Context, *PushBatchRequest) (*PushBatchResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method PushBatch not implemented")
|
||||
}
|
||||
func (UnimplementedLogIngestServer) mustEmbedUnimplementedLogIngestServer() {}
|
||||
func (UnimplementedLogIngestServer) testEmbeddedByValue() {}
|
||||
|
||||
// UnsafeLogIngestServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to LogIngestServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeLogIngestServer interface {
|
||||
mustEmbedUnimplementedLogIngestServer()
|
||||
}
|
||||
|
||||
func RegisterLogIngestServer(s grpc.ServiceRegistrar, srv LogIngestServer) {
|
||||
// If the following call panics, it indicates UnimplementedLogIngestServer was
|
||||
// embedded by pointer and is nil. This will cause panics if an
|
||||
// unimplemented method is ever invoked, so we test this at initialization
|
||||
// time to prevent it from happening at runtime later due to I/O.
|
||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
||||
t.testEmbeddedByValue()
|
||||
}
|
||||
s.RegisterService(&LogIngest_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _LogIngest_PushBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(PushBatchRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(LogIngestServer).PushBatch(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: LogIngest_PushBatch_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(LogIngestServer).PushBatch(ctx, req.(*PushBatchRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// LogIngest_ServiceDesc is the grpc.ServiceDesc for LogIngest service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var LogIngest_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "sentry.logs.v1.LogIngest",
|
||||
HandlerType: (*LogIngestServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "PushBatch",
|
||||
Handler: _LogIngest_PushBatch_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "sentry/logs/v1/logs.proto",
|
||||
}
|
||||
Reference in New Issue
Block a user