File size: 6,693 Bytes
95b671e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | import { strict as assert } from "node:assert";
import { test } from "node:test";
import {
PoisonMessageError,
describeCollectorFailure,
encodeIngestMessage,
getIngestQueueConfig,
isPermanentCollectorFailure,
isPoisonMessageError,
parseIngestMessage,
queueDeliveryMetricFromParsedMessage,
} from "./ingest-queue.js";
const baseInput = {
path: "/v1/logs",
projectId: "project-1",
contentType: "application/x-protobuf",
body: Buffer.from("hello"),
};
test("getIngestQueueConfig leaves direct forwarding enabled by default", () => {
assert.equal(getIngestQueueConfig({}), null);
});
test("getIngestQueueConfig reads bounded consumer concurrency", () => {
const config = getIngestQueueConfig({
INGEST_QUEUE_URL: "https://sqs.us-west-2.amazonaws.com/123/superlog-test-ingest",
INGEST_QUEUE_CONSUMER_CONCURRENCY: "99",
});
assert.equal(config?.consumerConcurrency, 32);
});
test("encodeIngestMessage stores small payloads inline", () => {
const encoded = encodeIngestMessage(
baseInput,
{
maxMessageBytes: 240_000,
oversizePrefix: "otlp-oversize",
},
new Date("2026-05-24T00:00:00.000Z"),
"payload-1",
);
assert.equal(encoded.storage, "inline");
assert.equal(encoded.s3Object, undefined);
const message = JSON.parse(encoded.messageBody);
assert.equal(message.body.storage, "inline");
assert.equal(Buffer.from(message.body.base64, "base64").toString("utf8"), "hello");
});
test("encodeIngestMessage offloads payloads that would exceed the SQS envelope budget", () => {
const encoded = encodeIngestMessage(
{ ...baseInput, body: Buffer.alloc(32, 7), contentEncoding: "gzip" },
{
maxMessageBytes: 10,
oversizeBucket: "superlog-ingest-oversize",
oversizePrefix: "/otlp-oversize/",
},
new Date("2026-05-24T12:34:56.000Z"),
"payload-2",
);
assert.equal(encoded.storage, "s3");
assert.equal(encoded.s3Object?.bucket, "superlog-ingest-oversize");
assert.equal(encoded.s3Object?.key, "otlp-oversize/2026/05/24/payload-2.otlp");
assert.equal(encoded.s3Object?.body.byteLength, 32);
const message = JSON.parse(encoded.messageBody);
assert.equal(message.body.storage, "s3");
assert.equal(message.body.bucket, "superlog-ingest-oversize");
assert.equal(message.body.key, "otlp-oversize/2026/05/24/payload-2.otlp");
assert.equal(message.contentEncoding, "gzip");
});
test("describeCollectorFailure includes bounded collector response body", () => {
const failure = describeCollectorFailure(400, "bad request: invalid OTLP payload");
assert.equal(failure.message, "collector returned 400: bad request: invalid OTLP payload");
assert.equal(failure.status, 400);
assert.equal(failure.body, "bad request: invalid OTLP payload");
});
test("isPermanentCollectorFailure drops 4xx the collector can never accept on retry", () => {
// A real prod incident: a client sent http.response_content_length above uint64 max,
// the collector 400s the whole batch, and the consumer cycled the same message every
// 900s (visibility timeout) for ~12.5h before the DLQ — pinning oldest-message-age into
// a sawtooth. Re-sending identical bytes can never succeed, so 4xx must be dropped.
assert.equal(isPermanentCollectorFailure(400), true);
assert.equal(isPermanentCollectorFailure(401), true);
assert.equal(isPermanentCollectorFailure(403), true);
assert.equal(isPermanentCollectorFailure(404), true);
assert.equal(isPermanentCollectorFailure(413), true);
assert.equal(isPermanentCollectorFailure(422), true);
});
test("isPermanentCollectorFailure keeps retryable statuses on the queue", () => {
// 408/429 are backpressure/timeout signals and 5xx are transient — the same payload can
// succeed once the collector recovers, so these stay in the queue for redelivery.
assert.equal(isPermanentCollectorFailure(408), false);
assert.equal(isPermanentCollectorFailure(429), false);
assert.equal(isPermanentCollectorFailure(500), false);
assert.equal(isPermanentCollectorFailure(502), false);
assert.equal(isPermanentCollectorFailure(503), false);
assert.equal(isPermanentCollectorFailure(504), false);
// Not a failure status at all — defensively never treated as a permanent drop.
assert.equal(isPermanentCollectorFailure(200), false);
});
test("parseIngestMessage round-trips a well-formed inline message", () => {
const encoded = encodeIngestMessage(
baseInput,
{ maxMessageBytes: 240_000, oversizePrefix: "otlp-oversize" },
new Date("2026-05-24T00:00:00.000Z"),
"payload-ok",
);
const parsed = parseIngestMessage(encoded.messageBody);
assert.equal(parsed.path, "/v1/logs");
assert.equal(parsed.body.storage, "inline");
});
test("parseIngestMessage flags an empty inline body as a poison message", () => {
// An empty OTLP HTTP body base64-encodes to "" — there is nothing to forward and
// no retry can ever succeed, so it must be classified as poison (delete, not cycle).
const emptyBody = JSON.stringify({
version: 1,
kind: "otlp",
path: "/v1/logs",
projectId: "project-1",
contentType: "application/json",
receivedAt: "2026-05-29T00:00:00.000Z",
body: { storage: "inline", base64: "" },
});
assert.throws(() => parseIngestMessage(emptyBody), PoisonMessageError);
try {
parseIngestMessage(emptyBody);
} catch (err) {
assert.equal(isPoisonMessageError(err), true);
}
});
test("parseIngestMessage flags non-JSON message bodies as poison", () => {
assert.throws(() => parseIngestMessage("not json at all"), PoisonMessageError);
});
test("parseIngestMessage flags valid JSON with malformed envelope fields as poison", () => {
// Valid JSON, but `path` is a number — assertIngestMessage's `.startsWith` would throw a
// TypeError. A structurally-broken envelope can never succeed on retry, so it must be poison.
const malformed = JSON.stringify({
version: 1,
kind: "otlp",
path: 123,
projectId: "project-1",
contentType: "application/json",
body: { storage: "inline", base64: "aGVsbG8=" },
});
assert.throws(() => parseIngestMessage(malformed), PoisonMessageError);
});
test("isPoisonMessageError discriminates transient errors", () => {
assert.equal(isPoisonMessageError(new Error("collector returned 503")), false);
assert.equal(isPoisonMessageError(new PoisonMessageError("bad")), true);
});
test("queueDeliveryMetricFromParsedMessage ignores malformed parsed payloads", () => {
assert.equal(
queueDeliveryMetricFromParsedMessage(
{ path: "/v1/logs", projectId: "project-1", body: {} },
"delivery_error",
undefined,
12,
34,
),
null,
);
});
|