1.Container: slog-JSON nach stdout
main.go
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)).With(
"service", "checkout",
"env", os.Getenv("ENV"),
)
slog.SetDefault(logger)
slog.Info("payment authorized", "order_id", "4711", "duration_ms", 812)
// {"time":"2026-09-04T11:42:08.877Z","level":"INFO","msg":"payment authorized","service":"checkout","env":"prod","order_id":"4711","duration_ms":812}Note: Setz bei den Vector-Beispielen
_msg_field: msg und _time_field: time in der Query des Sinks.2.Ohne Agent: ein bündelnder Writer für slog oder zap
uptimeeye.go
package uptimeeye
import (
"bytes"
"net/http"
"os"
"sync"
"time"
)
const endpoint = "https://logs.uptimeeye.com/v1/ingest/jsonline?_msg_field=msg&_time_field=time&_stream_fields=service,env"
// Writer buffers JSON lines and posts them every 2s or at 256 KiB.
type Writer struct {
mu sync.Mutex
buf bytes.Buffer
key string
}
func New() *Writer {
w := &Writer{key: os.Getenv("UPTIMEEYE_INGEST_KEY")}
go func() {
for range time.Tick(2 * time.Second) {
w.Flush()
}
}()
return w
}
func (w *Writer) Write(p []byte) (int, error) {
w.mu.Lock()
w.buf.Write(p)
full := w.buf.Len() >= 256<<10
w.mu.Unlock()
if full {
w.Flush()
}
return len(p), nil
}
func (w *Writer) Flush() {
w.mu.Lock()
if w.buf.Len() == 0 {
w.mu.Unlock()
return
}
body := make([]byte, w.buf.Len())
copy(body, w.buf.Bytes())
w.buf.Reset()
w.mu.Unlock()
req, _ := http.NewRequest(http.MethodPost, endpoint, bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+w.key)
req.Header.Set("Content-Type", "application/x-ndjson")
if res, err := http.DefaultClient.Do(req); err == nil {
res.Body.Close()
}
}
// usage:
// w := uptimeeye.New(); defer w.Flush()
// slog.SetDefault(slog.New(slog.NewJSONHandler(io.MultiWriter(os.Stdout, w), nil)).With("service", "checkout", "env", "prod"))
// zap: zapcore.NewCore(zapcore.NewJSONEncoder(cfg), zapcore.AddSync(w), zap.InfoLevel)Felder, die du bekommst
Diese Felder erscheinen im Panel „Felder“ und funktionieren in jedem Filter:
service,envlevelmsgals Nachricht- jedes slog-Attribut / zap-Feld
Tipps
- Nimm
io.MultiWriter(os.Stdout, w), dann behältst du die lokale Ausgabe und verschickst trotzdem. - zap:
zapcore.EncoderConfig{TimeKey: "time", LevelKey: "level", MessageKey: "msg", EncodeTime: zapcore.RFC3339NanoTimeEncoder}passt zu den Feldnamen oben. - OpenTelemetry für Go:
go.opentelemetry.io/contrib/bridges/otelslogverbindet slog mit dem OTLP-Exporter; Endpoint und Header wie auf der OpenTelemetry-Seite.
FAQ
Was passiert mit Zeilen, solange UptimeEye nicht erreichbar ist?
Der Writer oben verwirft sie nach einem fehlgeschlagenen Versuch – in Ordnung für eine Demo ohne Sidecar, nicht für die Produktion. Ein Agent mit Plattenpuffer (Vector, Fluent Bit) versucht es für dich erneut.
Wo bekomme ich den Ingest-Key?
In der App unter API-Keys → Neuer API-Key → Typ „Log-Ingest“. Der Key beginnt mit
ue_ingest_ und wird nur einmal angezeigt. Management-Keys (ue_live_) lehnt der Ingest-Endpoint ab.Wie sehe ich, ob Logs ankommen?
Öffne Logs in der App, wähl den Zeitraum „15 Min.“ und such nach
service:=<your service>. Neue Zeilen sind nach etwa einer Sekunde durchsuchbar; Live-Tail zeigt sie mit etwa 6 s Verzögerung.Verwandte Seiten
HTTP API / curlDie Ingest-API von UptimeEye Logs: JSON-Lines per curl oder eigenem Code senden, Stream-Felder wählen, mit gzip komprimieren und Antwortcodes verstehen..KubernetesSende stdout/stderr aller Pods per Vector-DaemonSet mit Namespace, Container und Node an UptimeEye Logs.OpenTelemetryRichte einen OpenTelemetry Collector oder ein beliebiges OTel-SDK per OTLP/HTTP auf UptimeEye Logs.Logs durchsuchenJetzt, wo Zeilen ankommen: Filter, Felder, Zeiträume und Live-Tail.