gogolanghttpintegrationopentelemetry

Go Integration

Send logs and traces from Go applications to ScryWatch using the standard library — no SDK required.

Go Integration

ScryWatch accepts logs over HTTP. Use Go’s standard net/http package — no SDK or extra dependencies required. For distributed traces, send OTLP via the OpenTelemetry Go SDK.

What you’ll need

Log ingest endpoint

POST https://ingest.scrywatch.com/v1/ingest
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

Step 1: Minimal log helper

package scrywatch

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"time"
)

// LogEvent is a single log entry sent to ScryWatch.
type LogEvent struct {
	Timestamp   int64          `json:"timestamp"`
	Level       string         `json:"level"`
	Type        string         `json:"type"`
	Message     string         `json:"message"`
	Service     string         `json:"service,omitempty"`
	Environment string         `json:"environment,omitempty"`
	UserID      string         `json:"user_id,omitempty"`
	SessionID   string         `json:"session_id,omitempty"`
	Metadata    map[string]any `json:"metadata,omitempty"`
}

// Client sends log events to ScryWatch.
type Client struct {
	endpoint string
	apiKey   string
	http     *http.Client
}

// NewClient creates a new ScryWatch client.
func NewClient(endpoint, apiKey string) *Client {
	return &Client{
		endpoint: endpoint,
		apiKey:   apiKey,
		http:     &http.Client{Timeout: 5 * time.Second},
	}
}

// Log sends a single log event.
func (c *Client) Log(ctx context.Context, level, message string, metadata map[string]any) error {
	return c.LogBatch(ctx, []LogEvent{{
		Timestamp: time.Now().UnixMilli(),
		Level:     level,
		Type:      "custom",
		Message:   message,
		Metadata:  metadata,
	}})
}

// LogBatch sends multiple log events in one request (up to 250).
func (c *Client) LogBatch(ctx context.Context, events []LogEvent) error {
	payload, err := json.Marshal(map[string]any{"events": events})
	if err != nil {
		return err
	}

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint+"/v1/ingest", bytes.NewReader(payload))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.apiKey)
	req.Header.Set("Content-Type", "application/json")

	resp, err := c.http.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	return nil
}

Step 2: Usage

package main

import (
	"context"
	"log"
)

func main() {
	sw := scrywatch.NewClient("https://ingest.scrywatch.com", "YOUR_API_KEY")

	if err := sw.Log(context.Background(), "info", "Server started", map[string]any{
		"port": 8080,
	}); err != nil {
		log.Printf("scrywatch: %v", err)
	}
}

Batch events

Use LogBatch to send up to 250 events in a single request, reducing HTTP overhead for high-volume services:

events := []scrywatch.LogEvent{
	{Timestamp: time.Now().UnixMilli(), Level: "info", Type: "custom", Message: "Request received", Service: "api"},
	{Timestamp: time.Now().UnixMilli(), Level: "error", Type: "custom", Message: "Upstream timeout", Metadata: map[string]any{"target": "payments"}},
}
if err := sw.LogBatch(context.Background(), events); err != nil {
	log.Printf("scrywatch batch: %v", err)
}

Step 3: Traces via OpenTelemetry

ScryWatch accepts OTLP/HTTP traces from the standard OpenTelemetry Go SDK.

⚠️ OTLP support is traces-only today. OTLP log ingestion is not yet supported by ScryWatch — use the HTTP ingest above for logs. Also note: the OpenTelemetry Go Logs Bridge API is still experimental as of early 2026; even when ScryWatch adds support, you should evaluate stability before production use.

go get go.opentelemetry.io/otel \
       go.opentelemetry.io/otel/sdk/trace \
       go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
       go.opentelemetry.io/otel/semconv/v1.21.0
import (
	"context"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/sdk/resource"
	"go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)

func initTracer(ctx context.Context) (*trace.TracerProvider, error) {
	exporter, err := otlptracehttp.New(ctx,
		otlptracehttp.WithEndpoint("api.scrywatch.com"),
		otlptracehttp.WithURLPath("/api/traces/otlp"),
		otlptracehttp.WithHeaders(map[string]string{
			"Authorization": "Bearer YOUR_API_KEY",
		}),
	)
	if err != nil {
		return nil, err
	}

	res, _ := resource.New(ctx,
		resource.WithAttributes(semconv.ServiceName("my-go-app")),
	)

	tp := trace.NewTracerProvider(
		trace.WithBatcher(exporter),
		trace.WithResource(res),
	)
	otel.SetTracerProvider(tp)
	return tp, nil
}

// Call at startup
func main() {
	ctx := context.Background()
	tp, err := initTracer(ctx)
	if err != nil {
		log.Fatal(err)
	}
	defer tp.Shutdown(ctx)

	// Now use otel.Tracer("my-service") as normal
}

NDJSON format (alternative)

ScryWatch also accepts NDJSON (one JSON object per line):

req.Header.Set("Content-Type", "application/x-ndjson")
// Body: one JSON log object per line, no wrapping array needed

Production tip: OpenTelemetry Collector

Rather than exporting directly from each service, deploy an OpenTelemetry Collector as a sidecar or DaemonSet and forward to ScryWatch from there. This decouples your services from ScryWatch’s endpoint — if you change backends, only the Collector config changes. See the Kubernetes guide.

Event fields reference

FieldTypeRequiredDescription
timestampnumberUnix milliseconds — use time.Now().UnixMilli()
levelstringinfo | warn | error | debug
typestringcustom | crash | session | navigation | api_call
messagestringLog message
servicestringService name
environmentstringe.g. production, staging
user_idstringUser identifier
metadataobjectAny additional key/value pairs

See also

← All guides