Node.js-Logs an UptimeEye senden (pino, winston, OpenTelemetry)

In Containern bleibt die Best Practice dieselbe: Logge JSON mit pino oder winston nach stdout und lass den Agent der Plattform den Versand übernehmen – die Standardausgabe von pino ist genau das, was die Anleitungen für Kubernetes und Docker auswerten. Gibt es keinen Agent (Serverless, eine einzelne VM, eine CLI), schickst du Batches selbst über HTTP; beide Varianten findest du unten.

Endpunkt: https://logs.uptimeeye.com/v1/ingest/jsonline

1.Container: pino nach stdout

pino schreibt ein JSON-Objekt pro Zeile mit msg, level (als Zahl) und time (Epoch in ms). Ergänz deine Service-Felder einmal als Base-Bindings.

logger.js
import pino from "pino";

export const log = pino({
  base: { service: "checkout", env: process.env.NODE_ENV ?? "prod" },
  formatters: { level: (label) => ({ level: label }) },   // "info" instead of 30
  timestamp: pino.stdTimeFunctions.isoTime,               // "time":"2026-09-04T11:42:08.877Z"
});

log.info({ orderId: "4711", durationMs: 812 }, "payment authorized");
Note: Ergänz bei den Vector-Beispielen _time_field: time (schon von parse_json ausgelesen) und _msg_field: msg in der Query des Sinks.

2.Ohne Agent: ein kleiner Batching-Transport (ohne Abhängigkeiten)

Puffert Zeilen und schickt sie alle zwei Sekunden oder alle 200 Zeilen ab; nutzt den JSON-Lines-Endpoint. Funktioniert als pino-Destination oder für sich allein.

uptimeeye-logs.js
const ENDPOINT = "https://logs.uptimeeye.com/v1/ingest/jsonline?_msg_field=msg&_time_field=time&_stream_fields=service,env";
const KEY = process.env.UPTIMEEYE_INGEST_KEY;

let buffer = [];
let timer = null;

export function send(entry) {
  buffer.push(JSON.stringify({ time: new Date().toISOString(), service: "checkout", env: "prod", ...entry }));
  if (buffer.length >= 200) return flush();
  timer ??= setTimeout(flush, 2000);
}

export async function flush() {
  clearTimeout(timer); timer = null;
  if (buffer.length === 0) return;
  const body = buffer.join("\n") + "\n";
  buffer = [];
  try {
    const res = await fetch(ENDPOINT, {
      method: "POST",
      headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/x-ndjson" },
      body,
    });
    if (!res.ok) console.error("uptimeeye logs:", res.status, await res.text());
  } catch (err) {
    console.error("uptimeeye logs:", err);
  }
}

process.on("beforeExit", flush);

// usage
send({ level: "info", msg: "payment authorized", orderId: "4711", durationMs: 812 });

3.OpenTelemetry Logs SDK

otel.js
import { logs } from "@opentelemetry/api-logs";
import { LoggerProvider, BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";

const provider = new LoggerProvider({
  resource: resourceFromAttributes({ "service.name": "checkout", "deployment.environment": "prod" }),
  processors: [
    new BatchLogRecordProcessor(
      new OTLPLogExporter({
        url: "https://logs.uptimeeye.com/v1/ingest/otlp/v1/logs",
        headers: { Authorization: `Bearer ${process.env.UPTIMEEYE_INGEST_KEY}` },
      }),
    ),
  ],
});
logs.setGlobalLoggerProvider(provider);

logs.getLogger("checkout").emit({ severityText: "INFO", body: "payment authorized", attributes: { orderId: "4711" } });
Note: Wer pino nutzt, kann pino behalten und pino-opentelemetry-transport ergänzen, um denselben Exporter zu füttern.

Felder, die du bekommst

Diese Felder erscheinen im Panel „Felder“ und funktionieren in jedem Filter:

  • service, env
  • level
  • msg als Nachricht
  • pid, hostname (Standard bei pino)
  • jeder Key der Objekte, die du loggst

Tipps

  • Logge Objekte statt verketteter Strings: log.info({ orderId }, "…") gibt dir ein filterbares Feld orderId.
  • winston: format.combine(format.timestamp(), format.json()) erzeugt dieselbe Form; setz _msg_field=message&_time_field=timestamp.
  • Serverless: Schick den Puffer im Shutdown-Hook der Plattform ab (Lambda: am Ende des Handlers) – auf beforeExit kannst du dich dort nicht verlassen.

FAQ

Soll ich aus dem Prozess senden oder über einen Agent?

Über einen Agent, wann immer es einen gibt: Er übersteht Abstürze, bündelt über Prozesse hinweg und braucht keinen Key in der Anwendung. Sende nur dort direkt, wo kein Agent laufen kann.

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.