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.
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");_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.
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
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" } });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,envlevelmsgals Nachrichtpid,hostname(Standard bei pino)- jeder Key der Objekte, die du loggst
Tipps
- Logge Objekte statt verketteter Strings:
log.info({ orderId }, "…")gibt dir ein filterbares FeldorderId. - 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
beforeExitkannst du dich dort nicht verlassen.
FAQ
Soll ich aus dem Prozess senden oder über einen Agent?
Wo bekomme ich den Ingest-Key?
ue_ingest_ und wird nur einmal angezeigt. Management-Keys (ue_live_) lehnt der Ingest-Endpoint ab.Wie sehe ich, ob Logs ankommen?
service:=<your service>. Neue Zeilen sind nach etwa einer Sekunde durchsuchbar; Live-Tail zeigt sie mit etwa 6 s Verzögerung.