stuff
This commit is contained in:
+25
@@ -0,0 +1,25 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o /go-otel ./cmd/server
|
||||||
|
|
||||||
|
# Final stage
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
RUN apk --no-cache add ca-certificates
|
||||||
|
|
||||||
|
WORKDIR /root/
|
||||||
|
|
||||||
|
COPY --from=builder /go-otel .
|
||||||
|
COPY --from=builder /app/index.html .
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
CMD ["./go-otel"]
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# Go OTel Debugger
|
||||||
|
|
||||||
|
This is a debug OpenTelemetry receiver that accepts traces, metrics, and logs via OTLP HTTP protocol and broadcasts them over WebSocket connections for real-time visualization.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Receives OTLP traces, metrics, and logs over HTTP
|
||||||
|
- Broadcasts received telemetry data over WebSocket connections
|
||||||
|
- Supports both JSON and Protocol Buffer formats
|
||||||
|
- Real-time debugging and visualization
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.21+
|
||||||
|
- Docker (optional)
|
||||||
|
|
||||||
|
### Running Locally
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```sh
|
||||||
|
git clone https://git.tornberg.me/go-otel.git
|
||||||
|
cd go-otel
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the server:
|
||||||
|
```sh
|
||||||
|
go run ./cmd/server
|
||||||
|
```
|
||||||
|
|
||||||
|
The server will be running on port 8080.
|
||||||
|
|
||||||
|
### Running with Docker
|
||||||
|
|
||||||
|
1. Build the Docker image:
|
||||||
|
```sh
|
||||||
|
docker build -t go-otel .
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Run the Docker container:
|
||||||
|
```sh
|
||||||
|
docker run -p 8080:8080 go-otel
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### OTLP HTTP Endpoints
|
||||||
|
|
||||||
|
- **POST /v1/traces**: Receive OTLP traces
|
||||||
|
- **POST /v1/metrics**: Receive OTLP metrics
|
||||||
|
- **POST /v1/logs**: Receive OTLP logs
|
||||||
|
|
||||||
|
Supported Content-Types:
|
||||||
|
- `application/json`
|
||||||
|
- `application/x-protobuf`
|
||||||
|
|
||||||
|
### OTLP gRPC Endpoints
|
||||||
|
|
||||||
|
- **gRPC service**: `otlptracegrpc.TraceService.Export`
|
||||||
|
- **gRPC service**: `otlptracegrpc.MetricsService.Export`
|
||||||
|
- **gRPC service**: `otlptracegrpc.LogsService.Export`
|
||||||
|
|
||||||
|
gRPC server runs on port 4317 (standard OTLP gRPC port).
|
||||||
|
|
||||||
|
### WebSocket
|
||||||
|
|
||||||
|
- **GET /ws**: Connect to receive real-time telemetry data
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Send OTLP data to the appropriate endpoints. The data will be broadcast to all connected WebSocket clients for visualization.
|
||||||
|
|
||||||
|
Open `http://localhost:8080` in your browser to access the web-based visualizer.
|
||||||
|
|
||||||
|
### HTTP Examples
|
||||||
|
|
||||||
|
Example with curl (JSON):
|
||||||
|
```sh
|
||||||
|
curl -X POST http://localhost:8080/v1/traces \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @trace-data.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### gRPC Examples
|
||||||
|
|
||||||
|
Use OTLP gRPC clients or libraries to send data to `localhost:4317`.
|
||||||
|
|
||||||
|
### WebSocket
|
||||||
|
|
||||||
|
Connect to WebSocket to receive the data:
|
||||||
|
```javascript
|
||||||
|
const ws = new WebSocket('ws://localhost:8080/ws');
|
||||||
|
ws.onmessage = (event) => {
|
||||||
|
console.log('Received:', JSON.parse(event.data));
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web Interface
|
||||||
|
|
||||||
|
The web interface provides real-time visualization of:
|
||||||
|
- Traces
|
||||||
|
- Metrics
|
||||||
|
- Logs
|
||||||
|
|
||||||
|
Features:
|
||||||
|
- Live data streaming via WebSocket
|
||||||
|
- Separate sections for each telemetry type
|
||||||
|
- Message counters
|
||||||
|
- Clear buttons for each section
|
||||||
|
- Connection status indicator
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
A test client is included to verify that the receiver works correctly.
|
||||||
|
|
||||||
|
### Run the Test
|
||||||
|
|
||||||
|
1. Start the receiver:
|
||||||
|
```sh
|
||||||
|
go run ./cmd/server
|
||||||
|
```
|
||||||
|
|
||||||
|
2. In another terminal, run the test client:
|
||||||
|
```sh
|
||||||
|
cd test-client && go run .
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the provided test script:
|
||||||
|
```sh
|
||||||
|
./test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The test client will send sample traces via both HTTP and gRPC protocols.
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
|
||||||
|
#### HTTP Testing
|
||||||
|
```sh
|
||||||
|
curl -X POST http://localhost:8080/v1/traces \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"test-service"}}]},"scopeSpans":[{"scope":{"name":"test-scope"},"spans":[{"traceId":"0102030405060708090a0b0c0d0e0f10","spanId":"0102030405060708","name":"test-span","startTimeUnixNano":"1640995200000000000","endTimeUnixNano":"1640995260000000000","attributes":[{"key":"test.key","value":{"stringValue":"test-value"}}]}]}]}]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Web Interface
|
||||||
|
Open `http://localhost:8080` in your browser to see the real-time visualization.
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.tornberg.me/go-otel/internal/collector"
|
||||||
|
"git.tornberg.me/go-otel/internal/websocket"
|
||||||
|
"go.opentelemetry.io/collector/pdata/plog"
|
||||||
|
"go.opentelemetry.io/collector/pdata/pmetric"
|
||||||
|
"go.opentelemetry.io/collector/pdata/ptrace"
|
||||||
|
logsgrpc "go.opentelemetry.io/proto/otlp/collector/logs/v1"
|
||||||
|
metricsgrpc "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
|
||||||
|
tracegrpc "go.opentelemetry.io/proto/otlp/collector/trace/v1"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type traceService struct {
|
||||||
|
tracegrpc.UnimplementedTraceServiceServer
|
||||||
|
collector *collector.Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *traceService) Export(ctx context.Context, req *tracegrpc.ExportTraceServiceRequest) (*tracegrpc.ExportTraceServiceResponse, error) {
|
||||||
|
log.Printf("Received gRPC traces %d", len(req.ResourceSpans))
|
||||||
|
|
||||||
|
// Convert protobuf to bytes then to pdata
|
||||||
|
data, err := proto.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal trace request: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarshaler := &ptrace.ProtoUnmarshaler{}
|
||||||
|
traces, err := unmarshaler.UnmarshalTraces(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to unmarshal traces: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.collector.ConsumeTraces(ctx, traces); err != nil {
|
||||||
|
log.Printf("failed to consume traces: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tracegrpc.ExportTraceServiceResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type metricsService struct {
|
||||||
|
metricsgrpc.UnimplementedMetricsServiceServer
|
||||||
|
collector *collector.Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *metricsService) Export(ctx context.Context, req *metricsgrpc.ExportMetricsServiceRequest) (*metricsgrpc.ExportMetricsServiceResponse, error) {
|
||||||
|
log.Printf("Received gRPC metrics %d", len(req.ResourceMetrics))
|
||||||
|
|
||||||
|
// Convert protobuf to bytes then to pdata
|
||||||
|
data, err := proto.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal metrics request: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarshaler := &pmetric.ProtoUnmarshaler{}
|
||||||
|
metrics, err := unmarshaler.UnmarshalMetrics(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to unmarshal metrics: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.collector.ConsumeMetrics(ctx, metrics); err != nil {
|
||||||
|
log.Printf("failed to consume metrics: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &metricsgrpc.ExportMetricsServiceResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type logsService struct {
|
||||||
|
logsgrpc.UnimplementedLogsServiceServer
|
||||||
|
collector *collector.Collector
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *logsService) Export(ctx context.Context, req *logsgrpc.ExportLogsServiceRequest) (*logsgrpc.ExportLogsServiceResponse, error) {
|
||||||
|
log.Printf("Received gRPC logs %d", len(req.ResourceLogs))
|
||||||
|
|
||||||
|
// Convert protobuf to bytes then to pdata
|
||||||
|
data, err := proto.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal logs request: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unmarshaler := &plog.ProtoUnmarshaler{}
|
||||||
|
logs, err := unmarshaler.UnmarshalLogs(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to unmarshal logs: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.collector.ConsumeLogs(ctx, logs); err != nil {
|
||||||
|
log.Printf("failed to consume logs: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &logsgrpc.ExportLogsServiceResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// Configure history size (default 100, can be made configurable)
|
||||||
|
historySize := 100
|
||||||
|
coll := collector.New(nil, historySize) // Temporary nil for wsServer
|
||||||
|
wsServer := websocket.New(coll)
|
||||||
|
// Update collector with wsServer
|
||||||
|
coll.SetBroadcaster(wsServer)
|
||||||
|
|
||||||
|
// Start gRPC server
|
||||||
|
go func() {
|
||||||
|
lis, err := net.Listen("tcp", ":4317")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("failed to listen for gRPC", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := grpc.NewServer()
|
||||||
|
tracegrpc.RegisterTraceServiceServer(s, &traceService{collector: coll})
|
||||||
|
metricsgrpc.RegisterMetricsServiceServer(s, &metricsService{collector: coll})
|
||||||
|
logsgrpc.RegisterLogsServiceServer(s, &logsService{collector: coll})
|
||||||
|
|
||||||
|
log.Print("Starting gRPC server on port 4317")
|
||||||
|
if err := s.Serve(lis); err != nil {
|
||||||
|
log.Fatalf("failed to serve gRPC: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// OTLP HTTP endpoints
|
||||||
|
http.HandleFunc("/v1/traces", handleTraces(coll))
|
||||||
|
http.HandleFunc("/v1/metrics", handleMetrics(coll))
|
||||||
|
http.HandleFunc("/v1/logs", handleLogs(coll))
|
||||||
|
|
||||||
|
// History API endpoints
|
||||||
|
http.HandleFunc("/api/history", handleHistory(coll))
|
||||||
|
http.HandleFunc("/api/history/traces", handleHistoryByType(coll, "traces"))
|
||||||
|
http.HandleFunc("/api/history/metrics", handleHistoryByType(coll, "metrics"))
|
||||||
|
http.HandleFunc("/api/history/logs", handleHistoryByType(coll, "logs"))
|
||||||
|
|
||||||
|
// WebSocket server for visualization
|
||||||
|
http.HandleFunc("/ws", wsServer.HandleConnections)
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte("OTel Debug Receiver is running"))
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("Starting HTTP server on port 8080")
|
||||||
|
log.Printf("OTLP HTTP endpoints:")
|
||||||
|
log.Printf(" Traces: http://localhost:8080/v1/traces")
|
||||||
|
log.Printf(" Metrics: http://localhost:8080/v1/metrics")
|
||||||
|
log.Printf(" Logs: http://localhost:8080/v1/logs")
|
||||||
|
log.Printf("OTLP gRPC endpoints:")
|
||||||
|
log.Printf(" Traces: localhost:4317")
|
||||||
|
log.Printf(" Metrics: localhost:4317")
|
||||||
|
log.Printf(" Logs: localhost:4317")
|
||||||
|
log.Printf("WebSocket: ws://localhost:8080/ws")
|
||||||
|
|
||||||
|
if err := http.ListenAndServe(":8080", nil); err != nil {
|
||||||
|
log.Fatalf("failed to start HTTP server: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTraces(coll *collector.Collector) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log.Printf("Received traces request: method=%s, content-type=%s", r.Method, r.Header.Get("Content-Type"))
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to read request body: %v", err)
|
||||||
|
http.Error(w, "failed to read request body", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Received traces body, size=%d", len(body))
|
||||||
|
|
||||||
|
var unmarshaler ptrace.Unmarshaler
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
switch contentType {
|
||||||
|
case "application/x-protobuf":
|
||||||
|
unmarshaler = &ptrace.ProtoUnmarshaler{}
|
||||||
|
case "application/json":
|
||||||
|
unmarshaler = &ptrace.JSONUnmarshaler{}
|
||||||
|
default:
|
||||||
|
unmarshaler = &ptrace.JSONUnmarshaler{} // default to JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
traces, err := unmarshaler.UnmarshalTraces(body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to unmarshal traces: %v", err)
|
||||||
|
http.Error(w, "failed to unmarshal traces", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Successfully unmarshaled traces, span_count=%d", traces.SpanCount())
|
||||||
|
|
||||||
|
if err := coll.ConsumeTraces(context.Background(), traces); err != nil {
|
||||||
|
log.Printf("failed to consume traces: %v", err)
|
||||||
|
http.Error(w, "failed to consume traces", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMetrics(coll *collector.Collector) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to read request body: %v", err)
|
||||||
|
http.Error(w, "failed to read request body", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshaler pmetric.Unmarshaler
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
switch contentType {
|
||||||
|
case "application/x-protobuf":
|
||||||
|
unmarshaler = &pmetric.ProtoUnmarshaler{}
|
||||||
|
case "application/json":
|
||||||
|
unmarshaler = &pmetric.JSONUnmarshaler{}
|
||||||
|
default:
|
||||||
|
unmarshaler = &pmetric.JSONUnmarshaler{} // default to JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
metrics, err := unmarshaler.UnmarshalMetrics(body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to unmarshal metrics: %v", err)
|
||||||
|
http.Error(w, "failed to unmarshal metrics", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := coll.ConsumeMetrics(context.Background(), metrics); err != nil {
|
||||||
|
log.Printf("failed to consume metrics: %v", err)
|
||||||
|
http.Error(w, "failed to consume metrics", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLogs(coll *collector.Collector) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to read request body: %v", err)
|
||||||
|
http.Error(w, "failed to read request body", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshaler plog.Unmarshaler
|
||||||
|
contentType := r.Header.Get("Content-Type")
|
||||||
|
switch contentType {
|
||||||
|
case "application/x-protobuf":
|
||||||
|
unmarshaler = &plog.ProtoUnmarshaler{}
|
||||||
|
case "application/json":
|
||||||
|
unmarshaler = &plog.JSONUnmarshaler{}
|
||||||
|
default:
|
||||||
|
unmarshaler = &plog.JSONUnmarshaler{} // default to JSON
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, err := unmarshaler.UnmarshalLogs(body)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to unmarshal logs: %v", err)
|
||||||
|
http.Error(w, "failed to unmarshal logs", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := coll.ConsumeLogs(context.Background(), logs); err != nil {
|
||||||
|
log.Printf("failed to consume logs: %v", err)
|
||||||
|
http.Error(w, "failed to consume logs", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHistory(coll *collector.Collector) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
history := coll.GetHistory()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(history); err != nil {
|
||||||
|
log.Printf("failed to encode history: %v", err)
|
||||||
|
http.Error(w, "failed to encode history", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHistoryByType(coll *collector.Collector, dataType string) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
history := coll.GetHistory()
|
||||||
|
|
||||||
|
// Filter by type
|
||||||
|
var filteredHistory []websocket.HistoryEntry
|
||||||
|
for _, entry := range history {
|
||||||
|
if string(entry.Type) == dataType {
|
||||||
|
filteredHistory = append(filteredHistory, entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(filteredHistory); err != nil {
|
||||||
|
log.Printf("failed to encode filtered history: %v", err)
|
||||||
|
http.Error(w, "failed to encode history", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
module git.tornberg.me/go-otel
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/websocket v1.5.3
|
||||||
|
go.opentelemetry.io/collector/consumer v1.45.0
|
||||||
|
go.opentelemetry.io/collector/pdata v1.45.0
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0
|
||||||
|
google.golang.org/grpc v1.76.0
|
||||||
|
google.golang.org/protobuf v1.36.10
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
|
go.opentelemetry.io/collector/featuregate v1.45.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/net v0.46.0 // indirect
|
||||||
|
golang.org/x/sys v0.37.0 // indirect
|
||||||
|
golang.org/x/text v0.30.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
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=
|
||||||
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||||
|
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||||
|
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/collector/consumer v1.45.0 h1:TtqXxgW+1GSCwdoohq0fzqnfqrZBKbfo++1XRj8mrEA=
|
||||||
|
go.opentelemetry.io/collector/consumer v1.45.0/go.mod h1:pJzqTWBubwLt8mVou+G4/Hs23b3m425rVmld3LqOYpY=
|
||||||
|
go.opentelemetry.io/collector/featuregate v1.45.0 h1:D06hpf1F2KzKC+qXLmVv5e8IZpgCyZVeVVC8iOQxVmw=
|
||||||
|
go.opentelemetry.io/collector/featuregate v1.45.0/go.mod h1:d0tiRzVYrytB6LkcYgz2ESFTv7OktRPQe0QEQcPt1L4=
|
||||||
|
go.opentelemetry.io/collector/pdata v1.45.0 h1:q4XaISpeX640BcwXwb2mKOVw/gb67r22HjGWl8sbWsk=
|
||||||
|
go.opentelemetry.io/collector/pdata v1.45.0/go.mod h1:5q2f001YhwMQO8QvpFhCOa4Cq/vtwX9W4HRMsXkU/nE=
|
||||||
|
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp v1.8.0 h1:afcLwp2XOeCbGrjufT1qWyruFt+6C9g5SOuymrSPUXQ=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp v1.8.0/go.mod h1:Yaa5fjYm1SMCq0hG0x/87wV1MP9H5xDuG/1+AhvBcsI=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0 h1:Uc+elixz922LHx5colXGi1ORbsW8DTIGM+gg+D9V7HE=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0/go.mod h1:VyU6dTWBWv6h9w/+DYgSZAPMabWbPTFTuxp25sM8+s0=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0 h1:i8YpvWGm/Uq1koL//bnbJ/26eV3OrKWm09+rDYo7keU=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0/go.mod h1:pQ70xHY/ZVxNUBPn+qUWPl8nwai87eWdqL3M37lNi9A=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
|
||||||
|
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
|
||||||
|
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
|
||||||
|
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
|
||||||
|
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
|
||||||
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:vk5TfqZHNn0obhPIYeS+cxIFKFQgser/M2jnI+9c6MM=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||||
|
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||||
|
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
+232
@@ -0,0 +1,232 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>OTel Debug Visualizer</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.connected {
|
||||||
|
background-color: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
.disconnected {
|
||||||
|
background-color: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
.data-section {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.data-section h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #007bff;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.data-content {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
.clear-btn {
|
||||||
|
background: #dc3545;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.clear-btn:hover {
|
||||||
|
background: #c82333;
|
||||||
|
}
|
||||||
|
.stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.stat {
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
flex: 1;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
.stat h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
.stat p {
|
||||||
|
margin: 5px 0 0 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>OpenTelemetry Debug Visualizer</h1>
|
||||||
|
|
||||||
|
<div id="status" class="status disconnected">
|
||||||
|
Disconnected from server
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat">
|
||||||
|
<h3>Traces</h3>
|
||||||
|
<p id="traces-count">0</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<h3>Metrics</h3>
|
||||||
|
<p id="metrics-count">0</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<h3>Logs</h3>
|
||||||
|
<p id="logs-count">0</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-section">
|
||||||
|
<h2>Traces</h2>
|
||||||
|
<div id="traces" class="data-content"></div>
|
||||||
|
<button class="clear-btn" onclick="clearData('traces')">
|
||||||
|
Clear Traces
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-section">
|
||||||
|
<h2>Metrics</h2>
|
||||||
|
<div id="metrics" class="data-content"></div>
|
||||||
|
<button class="clear-btn" onclick="clearData('metrics')">
|
||||||
|
Clear Metrics
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="data-section">
|
||||||
|
<h2>Logs</h2>
|
||||||
|
<div id="logs" class="data-content"></div>
|
||||||
|
<button class="clear-btn" onclick="clearData('logs')">
|
||||||
|
Clear Logs
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let ws;
|
||||||
|
let tracesCount = 0;
|
||||||
|
let metricsCount = 0;
|
||||||
|
let logsCount = 0;
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
ws = new WebSocket("ws://localhost:8080/ws");
|
||||||
|
|
||||||
|
ws.onopen = function () {
|
||||||
|
document.getElementById("status").className = "status connected";
|
||||||
|
document.getElementById("status").textContent = "Connected to server";
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onmessage = function (event) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
displayData(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse data:", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = function () {
|
||||||
|
document.getElementById("status").className = "status disconnected";
|
||||||
|
document.getElementById("status").textContent =
|
||||||
|
"Disconnected from server - reconnecting...";
|
||||||
|
setTimeout(connect, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onerror = function (error) {
|
||||||
|
console.error("WebSocket error:", error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayData(data) {
|
||||||
|
let container;
|
||||||
|
let countElement;
|
||||||
|
|
||||||
|
if (data.resourceSpans) {
|
||||||
|
// Traces
|
||||||
|
container = document.getElementById("traces");
|
||||||
|
countElement = document.getElementById("traces-count");
|
||||||
|
tracesCount++;
|
||||||
|
countElement.textContent = tracesCount;
|
||||||
|
} else if (data.resourceMetrics) {
|
||||||
|
// Metrics
|
||||||
|
container = document.getElementById("metrics");
|
||||||
|
countElement = document.getElementById("metrics-count");
|
||||||
|
metricsCount++;
|
||||||
|
countElement.textContent = metricsCount;
|
||||||
|
} else if (data.resourceLogs) {
|
||||||
|
// Logs
|
||||||
|
container = document.getElementById("logs");
|
||||||
|
countElement = document.getElementById("logs-count");
|
||||||
|
logsCount++;
|
||||||
|
countElement.textContent = logsCount;
|
||||||
|
} else {
|
||||||
|
return; // Unknown data type
|
||||||
|
}
|
||||||
|
|
||||||
|
const timestamp = new Date().toLocaleTimeString();
|
||||||
|
const dataDiv = document.createElement("div");
|
||||||
|
dataDiv.innerHTML = `<strong>[${timestamp}]</strong> ${JSON.stringify(
|
||||||
|
data,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}<hr>`;
|
||||||
|
container.appendChild(dataDiv);
|
||||||
|
container.scrollTop = container.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearData(type) {
|
||||||
|
const container = document.getElementById(type);
|
||||||
|
container.innerHTML = "";
|
||||||
|
const countElement = document.getElementById(type + "-count");
|
||||||
|
if (type === "traces") tracesCount = 0;
|
||||||
|
else if (type === "metrics") metricsCount = 0;
|
||||||
|
else if (type === "logs") logsCount = 0;
|
||||||
|
countElement.textContent = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect on page load
|
||||||
|
connect();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/ring"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.tornberg.me/go-otel/internal/websocket"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/collector/consumer"
|
||||||
|
"go.opentelemetry.io/collector/pdata/plog"
|
||||||
|
"go.opentelemetry.io/collector/pdata/pmetric"
|
||||||
|
"go.opentelemetry.io/collector/pdata/ptrace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Collector struct {
|
||||||
|
broadcaster websocket.Broadcaster
|
||||||
|
historySize int
|
||||||
|
history *ring.Ring
|
||||||
|
historyMutex sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(broadcaster websocket.Broadcaster, historySize int) *Collector {
|
||||||
|
return &Collector{
|
||||||
|
broadcaster: broadcaster,
|
||||||
|
historySize: historySize,
|
||||||
|
history: ring.New(historySize),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) SetBroadcaster(broadcaster websocket.Broadcaster) {
|
||||||
|
c.broadcaster = broadcaster
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) addToHistory(dataType websocket.DataType, data interface{}) {
|
||||||
|
c.historyMutex.Lock()
|
||||||
|
defer c.historyMutex.Unlock()
|
||||||
|
|
||||||
|
c.history.Value = websocket.HistoryEntry{
|
||||||
|
Type: string(dataType),
|
||||||
|
Timestamp: time.Now().UnixNano(),
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
c.history = c.history.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) GetHistory() []websocket.HistoryEntry {
|
||||||
|
c.historyMutex.RLock()
|
||||||
|
defer c.historyMutex.RUnlock()
|
||||||
|
|
||||||
|
var history []websocket.HistoryEntry
|
||||||
|
c.history.Do(func(v interface{}) {
|
||||||
|
if entry, ok := v.(websocket.HistoryEntry); ok && entry.Type != "" {
|
||||||
|
history = append(history, entry)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return history
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
|
||||||
|
marshaler := &ptrace.JSONMarshaler{}
|
||||||
|
buf, err := marshaler.MarshalTraces(td)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal traces: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse to interface{} for history storage
|
||||||
|
var data interface{}
|
||||||
|
if err := json.Unmarshal(buf, &data); err != nil {
|
||||||
|
log.Printf("failed to unmarshal traces for history: %v", err)
|
||||||
|
} else {
|
||||||
|
c.addToHistory(websocket.DataTypeTraces, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := websocket.Entry{
|
||||||
|
Type: string(websocket.DataTypeTraces),
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.broadcaster.Broadcast(&entry)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) ConsumeMetrics(ctx context.Context, md pmetric.Metrics) error {
|
||||||
|
marshaler := &pmetric.JSONMarshaler{}
|
||||||
|
buf, err := marshaler.MarshalMetrics(md)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal metrics: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse to interface{} for history storage
|
||||||
|
var data interface{}
|
||||||
|
if err := json.Unmarshal(buf, &data); err != nil {
|
||||||
|
log.Printf("failed to unmarshal metrics for history: %v", err)
|
||||||
|
} else {
|
||||||
|
c.addToHistory(websocket.DataTypeMetrics, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := websocket.Entry{
|
||||||
|
Type: string(websocket.DataTypeMetrics),
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.broadcaster.Broadcast(&entry)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Collector) ConsumeLogs(ctx context.Context, ld plog.Logs) error {
|
||||||
|
marshaler := &plog.JSONMarshaler{}
|
||||||
|
buf, err := marshaler.MarshalLogs(ld)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal logs: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse to interface{} for history storage
|
||||||
|
var data interface{}
|
||||||
|
if err := json.Unmarshal(buf, &data); err != nil {
|
||||||
|
log.Printf("failed to unmarshal logs for history: %v", err)
|
||||||
|
} else {
|
||||||
|
c.addToHistory(websocket.DataTypeLogs, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := websocket.Entry{
|
||||||
|
Type: string(websocket.DataTypeLogs),
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.broadcaster.Broadcast(&entry)
|
||||||
|
|
||||||
|
//c.broadcaster.Broadcast(buf)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement consumer interfaces
|
||||||
|
func (c *Collector) Capabilities() consumer.Capabilities {
|
||||||
|
return consumer.Capabilities{MutatesData: false}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
DataTypeTraces DataType = "traces"
|
||||||
|
DataTypeMetrics DataType = "metrics"
|
||||||
|
DataTypeLogs DataType = "logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HistoryProvider interface {
|
||||||
|
GetHistory() []HistoryEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
type HistoryEntry struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entry struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Data interface{} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Broadcaster interface {
|
||||||
|
Broadcast(data *Entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
clients map[*websocket.Conn]bool
|
||||||
|
mutex sync.Mutex
|
||||||
|
historyProvider HistoryProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(historyProvider HistoryProvider) *Server {
|
||||||
|
return &Server{
|
||||||
|
|
||||||
|
clients: make(map[*websocket.Conn]bool),
|
||||||
|
historyProvider: historyProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleConnections(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ws, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to upgrade connection: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer ws.Close()
|
||||||
|
|
||||||
|
s.mutex.Lock()
|
||||||
|
s.clients[ws] = true
|
||||||
|
s.mutex.Unlock()
|
||||||
|
|
||||||
|
log.Printf("client connected")
|
||||||
|
|
||||||
|
// // Send history to new client
|
||||||
|
// if s.historyProvider != nil {
|
||||||
|
// history := s.historyProvider.GetHistory()
|
||||||
|
// for _, entry := range history {
|
||||||
|
// if data, err := json.Marshal(entry); err == nil {
|
||||||
|
// if err := ws.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||||
|
// log.Printf("failed to send history to client: %v", err)
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Read message from browser
|
||||||
|
_, _, err := ws.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("read error: %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mutex.Lock()
|
||||||
|
delete(s.clients, ws)
|
||||||
|
s.mutex.Unlock()
|
||||||
|
log.Printf("client disconnected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Broadcast(entry *Entry) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
msg, err := json.Marshal(entry)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal log entry: %v", err)
|
||||||
|
} else {
|
||||||
|
for client := range s.clients {
|
||||||
|
if err := client.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||||
|
log.Printf("write error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SendHistory(ws *websocket.Conn) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
history := s.historyProvider.GetHistory() // Assuming GetHistory() returns the history data
|
||||||
|
for _, entry := range history {
|
||||||
|
data, err := json.Marshal(entry)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal history entry: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := ws.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||||
|
log.Printf("write error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) HandleMessage(ws *websocket.Conn, msg []byte) {
|
||||||
|
var message map[string]interface{}
|
||||||
|
if err := json.Unmarshal(msg, &message); err != nil {
|
||||||
|
log.Printf("error unmarshaling message: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the message (this is just an example, adjust according to your needs)
|
||||||
|
switch message["type"] {
|
||||||
|
case "ping":
|
||||||
|
log.Printf("received ping")
|
||||||
|
s.SendPong(ws)
|
||||||
|
default:
|
||||||
|
log.Printf("unknown message type : %v", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) SendPong(ws *websocket.Conn) {
|
||||||
|
if err := ws.WriteMessage(websocket.PongMessage, nil); err != nil {
|
||||||
|
log.Printf("error sending pong", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: otel-debug-receiver
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: otel-debug-receiver
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: otel-debug-receiver
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: otel-debug
|
||||||
|
image: matst80/otel-debug:latest
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
- containerPort: 4317
|
||||||
|
name: grpc
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "500m"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: otel-debug-ingress
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx # Assuming nginx ingress controller
|
||||||
|
tls:
|
||||||
|
- hosts:
|
||||||
|
- otel.tornberg.me
|
||||||
|
secretName: otel-tls
|
||||||
|
rules:
|
||||||
|
- host: otel.tornberg.me
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: otel-debug-service
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: otel-debug-service
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: otel-debug-receiver
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
- name: grpc
|
||||||
|
port: 4317
|
||||||
|
targetPort: 4317
|
||||||
|
protocol: TCP
|
||||||
|
type: ClusterIP
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
module test-client
|
||||||
|
|
||||||
|
go 1.25.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
go.opentelemetry.io/collector/pdata v1.45.0
|
||||||
|
go.opentelemetry.io/otel v1.38.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
|
go.opentelemetry.io/collector/featuregate v1.45.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/net v0.43.0 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
|
||||||
|
google.golang.org/grpc v1.76.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.10 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
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=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
||||||
|
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||||
|
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
|
||||||
|
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
|
go.opentelemetry.io/collector/featuregate v1.45.0 h1:D06hpf1F2KzKC+qXLmVv5e8IZpgCyZVeVVC8iOQxVmw=
|
||||||
|
go.opentelemetry.io/collector/featuregate v1.45.0/go.mod h1:d0tiRzVYrytB6LkcYgz2ESFTv7OktRPQe0QEQcPt1L4=
|
||||||
|
go.opentelemetry.io/collector/pdata v1.45.0 h1:q4XaISpeX640BcwXwb2mKOVw/gb67r22HjGWl8sbWsk=
|
||||||
|
go.opentelemetry.io/collector/pdata v1.45.0/go.mod h1:5q2f001YhwMQO8QvpFhCOa4Cq/vtwX9W4HRMsXkU/nE=
|
||||||
|
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||||
|
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||||
|
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||||
|
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp v1.8.0 h1:afcLwp2XOeCbGrjufT1qWyruFt+6C9g5SOuymrSPUXQ=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp v1.8.0/go.mod h1:Yaa5fjYm1SMCq0hG0x/87wV1MP9H5xDuG/1+AhvBcsI=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0 h1:Uc+elixz922LHx5colXGi1ORbsW8DTIGM+gg+D9V7HE=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.1.0/go.mod h1:VyU6dTWBWv6h9w/+DYgSZAPMabWbPTFTuxp25sM8+s0=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0 h1:i8YpvWGm/Uq1koL//bnbJ/26eV3OrKWm09+rDYo7keU=
|
||||||
|
go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.1.0/go.mod h1:pQ70xHY/ZVxNUBPn+qUWPl8nwai87eWdqL3M37lNi9A=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||||
|
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc=
|
||||||
|
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
|
||||||
|
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
|
||||||
|
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
||||||
|
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.opentelemetry.io/collector/pdata/pcommon"
|
||||||
|
"go.opentelemetry.io/collector/pdata/plog"
|
||||||
|
"go.opentelemetry.io/collector/pdata/pmetric"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
fmt.Println("Testing OTLP Debug Receiver...")
|
||||||
|
fmt.Println("Make sure the receiver is running on ports 8080 (HTTP) and 4317 (gRPC)")
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
// Test HTTP endpoint
|
||||||
|
fmt.Println("Testing HTTP endpoint...")
|
||||||
|
testHTTPTraces(ctx)
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// Test gRPC endpoint
|
||||||
|
fmt.Println("Testing gRPC endpoint...")
|
||||||
|
testGRPCTraces(ctx)
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// Test HTTP metrics
|
||||||
|
fmt.Println("Testing HTTP metrics...")
|
||||||
|
testHTTPMetrics()
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
// Test HTTP logs
|
||||||
|
fmt.Println("Testing HTTP logs...")
|
||||||
|
testHTTPLogs()
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Test completed! Check the receiver logs and web interface at http://localhost:8080")
|
||||||
|
fmt.Println("You should see traces, metrics, and logs from both HTTP and gRPC clients.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHTTPTraces(ctx context.Context) {
|
||||||
|
// Create OTLP HTTP exporter
|
||||||
|
exporter, err := otlptracehttp.New(ctx,
|
||||||
|
otlptracehttp.WithEndpoint("localhost:8080"),
|
||||||
|
otlptracehttp.WithInsecure(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create HTTP exporter: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tracer provider
|
||||||
|
tp := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithBatcher(exporter),
|
||||||
|
sdktrace.WithResource(resource.NewWithAttributes(
|
||||||
|
semconv.SchemaURL,
|
||||||
|
semconv.ServiceNameKey.String("test-client-http"),
|
||||||
|
semconv.ServiceVersionKey.String("1.0.0"),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err := tp.Shutdown(ctx); err != nil {
|
||||||
|
log.Printf("failed to shutdown tracer provider: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
|
||||||
|
// Create tracer
|
||||||
|
tracer := tp.Tracer("test-tracer")
|
||||||
|
|
||||||
|
// Create some sample spans
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
_, span := tracer.Start(ctx, fmt.Sprintf("http-test-span-%d", i),
|
||||||
|
trace.WithAttributes(
|
||||||
|
attribute.String("test.key", fmt.Sprintf("test-value-%d", i)),
|
||||||
|
attribute.Int("test.number", i),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add an event
|
||||||
|
span.AddEvent("test event", trace.WithAttributes(
|
||||||
|
attribute.String("event.key", "event.value"),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Simulate some work
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force flush
|
||||||
|
if err := tp.ForceFlush(ctx); err != nil {
|
||||||
|
log.Printf("failed to flush: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("HTTP traces sent successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGRPCTraces(ctx context.Context) {
|
||||||
|
// Create OTLP gRPC exporter
|
||||||
|
exporter, err := otlptracegrpc.New(ctx,
|
||||||
|
otlptracegrpc.WithEndpoint("localhost:4317"),
|
||||||
|
otlptracegrpc.WithInsecure(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create gRPC exporter: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tracer provider
|
||||||
|
tp := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithBatcher(exporter),
|
||||||
|
sdktrace.WithResource(resource.NewWithAttributes(
|
||||||
|
semconv.SchemaURL,
|
||||||
|
semconv.ServiceNameKey.String("test-client-grpc"),
|
||||||
|
semconv.ServiceVersionKey.String("1.0.0"),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if err := tp.Shutdown(ctx); err != nil {
|
||||||
|
log.Printf("failed to shutdown tracer provider: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
|
||||||
|
// Create tracer
|
||||||
|
tracer := tp.Tracer("test-tracer-grpc")
|
||||||
|
|
||||||
|
// Create some sample spans
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
_, span := tracer.Start(ctx, fmt.Sprintf("grpc-test-span-%d", i),
|
||||||
|
trace.WithAttributes(
|
||||||
|
attribute.String("test.key", fmt.Sprintf("grpc-test-value-%d", i)),
|
||||||
|
attribute.Int("test.number", i*10),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add an event
|
||||||
|
span.AddEvent("grpc test event", trace.WithAttributes(
|
||||||
|
attribute.String("grpc.event.key", "grpc.event.value"),
|
||||||
|
))
|
||||||
|
|
||||||
|
// Simulate some work
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
|
||||||
|
span.End()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force flush
|
||||||
|
if err := tp.ForceFlush(ctx); err != nil {
|
||||||
|
log.Printf("failed to flush: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("gRPC traces sent successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHTTPMetrics() {
|
||||||
|
md := pmetric.NewMetrics()
|
||||||
|
resourceMetrics := md.ResourceMetrics().AppendEmpty()
|
||||||
|
resourceMetrics.Resource().Attributes().PutStr("service.name", "test-client-metric")
|
||||||
|
scopeMetrics := resourceMetrics.ScopeMetrics().AppendEmpty()
|
||||||
|
scopeMetrics.Scope().SetName("test-meter")
|
||||||
|
metric := scopeMetrics.Metrics().AppendEmpty()
|
||||||
|
metric.SetName("test_counter")
|
||||||
|
gauge := metric.SetEmptyGauge()
|
||||||
|
dataPoint := gauge.DataPoints().AppendEmpty()
|
||||||
|
dataPoint.SetIntValue(42)
|
||||||
|
dataPoint.Attributes().PutStr("test.key", "test-value")
|
||||||
|
marshaler := &pmetric.ProtoMarshaler{}
|
||||||
|
buf, err := marshaler.MarshalMetrics(md)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal metrics: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := http.Post("http://localhost:8080/v1/metrics", "application/x-protobuf", bytes.NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to post metrics: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
fmt.Println("HTTP metrics sent successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHTTPLogs() {
|
||||||
|
ld := plog.NewLogs()
|
||||||
|
resourceLogs := ld.ResourceLogs().AppendEmpty()
|
||||||
|
resourceLogs.Resource().Attributes().PutStr("service.name", "test-client-log")
|
||||||
|
scopeLogs := resourceLogs.ScopeLogs().AppendEmpty()
|
||||||
|
scopeLogs.Scope().SetName("test-logger")
|
||||||
|
logRecord := scopeLogs.LogRecords().AppendEmpty()
|
||||||
|
logRecord.Body().SetStr("test log message")
|
||||||
|
logRecord.SetTimestamp(pcommon.NewTimestampFromTime(time.Now()))
|
||||||
|
logRecord.Attributes().PutStr("test.key", "test-value")
|
||||||
|
marshaler := &plog.ProtoMarshaler{}
|
||||||
|
buf, err := marshaler.MarshalLogs(ld)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to marshal logs: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, err := http.Post("http://localhost:8080/v1/logs", "application/x-protobuf", bytes.NewReader(buf))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to post logs: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Body.Close()
|
||||||
|
fmt.Println("HTTP logs sent successfully")
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "=== OTLP Debug Receiver Test ==="
|
||||||
|
echo "This script will start the receiver and run the test client."
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "Building receiver..."
|
||||||
|
cd /Users/mats/git.tornberg.me/go-otel
|
||||||
|
go build -o server ./cmd/server
|
||||||
|
|
||||||
|
echo "Building test client..."
|
||||||
|
cd test-client
|
||||||
|
go build -o test-client .
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Starting OTLP Debug Receiver..."
|
||||||
|
cd ..
|
||||||
|
./server &
|
||||||
|
SERVER_PID=$!
|
||||||
|
|
||||||
|
echo "Waiting for server to start..."
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Running test client..."
|
||||||
|
cd test-client
|
||||||
|
./test-client
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Test completed. Stopping server..."
|
||||||
|
kill $SERVER_PID
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "=== Test Complete ==="
|
||||||
|
echo "Check the output above for any errors."
|
||||||
|
echo "Open http://localhost:8080 in your browser to see the web interface."
|
||||||
Reference in New Issue
Block a user