jskelet 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -6
- package/docs/06-cache.md +202 -9
- package/docs/07-yapilandirma.md +70 -2
- package/docs/en/06-caching.md +209 -10
- package/docs/en/07-configuration.md +72 -2
- package/package.json +1 -1
- package/src/client/cache-panel/login.html +63 -0
- package/src/client/cache-panel/panel.css +448 -0
- package/src/client/cache-panel/panel.html +133 -0
- package/src/client/cache-panel/panel.js +339 -0
- package/src/client/devtools/overlay.js +85 -0
- package/src/client/devtools/report.js +13 -0
- package/src/config/defaults.js +67 -2
- package/src/config/index.js +98 -1
- package/src/index.js +5 -0
- package/src/server/cache-panel.js +505 -0
- package/src/server/create-app.js +16 -0
- package/src/server/data-cache.js +92 -2
- package/src/server/dev/report.js +8 -1
- package/src/server/html-cache.js +36 -0
- package/src/server/prewarm.js +128 -24
- package/src/server/render.js +24 -14
- package/src/server/upstream-limiter.js +376 -0
- package/src/server/upstream-tracking.js +25 -0
package/src/server/dev/report.js
CHANGED
|
@@ -13,8 +13,9 @@ import fs from "node:fs";
|
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import zlib from "node:zlib";
|
|
15
15
|
import { getHtmlCacheEntries, getHtmlCacheSize } from "../html-cache.js";
|
|
16
|
-
import { getDataCacheSize } from "../data-cache.js";
|
|
16
|
+
import { getDataCacheSize, getDataCacheStats } from "../data-cache.js";
|
|
17
17
|
import { getRedisStatus } from "../redis.js";
|
|
18
|
+
import { getUpstreamLimiterStatus } from "../upstream-limiter.js";
|
|
18
19
|
import { prewarmProgress } from "../prewarm.js";
|
|
19
20
|
import { getConfig } from "../../config/index.js";
|
|
20
21
|
|
|
@@ -351,10 +352,16 @@ export function buildReport(devtools) {
|
|
|
351
352
|
// Veri önbelleğinden yalnızca sayaç: uzun kuyruklu bir sitede on
|
|
352
353
|
// binlerce anahtar oluyor ve dökümü rapora koymak faydasız bir yük.
|
|
353
354
|
data: getDataCacheSize(),
|
|
355
|
+
// Sayaçlar dökümün yerine geçmiyor, onu tamamlıyor: "kaç girdi var"
|
|
356
|
+
// sorusundan çok "kaç okuma upstream'e gitti" sorusu karar veriyor.
|
|
357
|
+
dataStats: getDataCacheStats(),
|
|
354
358
|
// Paylaşımlı kademe kapalıyken de basılır: "Redis'i açtım ama neden
|
|
355
359
|
// çalışmıyor" sorusunun cevabı en çok burada aranıyor.
|
|
356
360
|
redis: getRedisStatus(),
|
|
357
361
|
},
|
|
362
|
+
// Hız freninin o anki durumu. 429 fırtınasında "şu an saniyede kaça
|
|
363
|
+
// indi" bilgisi olmadan ayar yapmak körlemesine oluyor.
|
|
364
|
+
upstream: getUpstreamLimiterStatus(),
|
|
358
365
|
prewarm: { ...prewarmProgress },
|
|
359
366
|
requests: devtools.requests,
|
|
360
367
|
errors: devtools.errors,
|
package/src/server/html-cache.js
CHANGED
|
@@ -686,6 +686,11 @@ onCacheEvent((event) => {
|
|
|
686
686
|
return;
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
+
if (event.type === "html:drop") {
|
|
690
|
+
if (typeof event.key === "string") dropLocalKey(event.key);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
689
694
|
if (event.type !== "html:invalidate") return;
|
|
690
695
|
|
|
691
696
|
const targets = /** @type {(string | RegExp)[]} */ (
|
|
@@ -742,6 +747,37 @@ export function invalidateHtmlByDependency(dataKeys) {
|
|
|
742
747
|
return keys.size;
|
|
743
748
|
}
|
|
744
749
|
|
|
750
|
+
/**
|
|
751
|
+
* Tek bir önbellek **anahtarını** düşürür.
|
|
752
|
+
*
|
|
753
|
+
* `invalidateHtmlCache()` yol deseniyle çalışıyor ve bir yolun bütün query
|
|
754
|
+
* varyantlarını birlikte düşürüyor. Yönetim paneli listedeki tek satırı
|
|
755
|
+
* silebilmek istiyor: `/liste?sayfa=2` düşerken `/liste?sayfa=3` sıcak
|
|
756
|
+
* kalmalı. Desen sözdiziminde `?` kaçırılamadığı için ayrı bir yüzey.
|
|
757
|
+
*
|
|
758
|
+
* @param {string} key `yol?query` biçiminde tam anahtar.
|
|
759
|
+
* @returns {boolean} Girdi var mıydı.
|
|
760
|
+
*/
|
|
761
|
+
export function dropHtmlCacheKey(key) {
|
|
762
|
+
const existed = dropLocalKey(key);
|
|
763
|
+
|
|
764
|
+
if (redisShares("html")) redisDrop([cacheKey("html", key)]);
|
|
765
|
+
publishCacheEvent({ type: "html:drop", key });
|
|
766
|
+
|
|
767
|
+
return existed;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* @param {string} key
|
|
772
|
+
* @returns {boolean}
|
|
773
|
+
*/
|
|
774
|
+
function dropLocalKey(key) {
|
|
775
|
+
// Uçuştaki tazeleme de geçersiz: silinen girdiyi geri yazmamalı.
|
|
776
|
+
tokens.delete(key);
|
|
777
|
+
invalidated.delete(key);
|
|
778
|
+
return drop(key);
|
|
779
|
+
}
|
|
780
|
+
|
|
745
781
|
/**
|
|
746
782
|
* Invalidate edilmiş ve henüz kimsenin istemediği yolları döner ve kuyruğu
|
|
747
783
|
* boşaltır. Isıtma turu bunları başa alır; iki tur aynı yolu tekrar
|
package/src/server/prewarm.js
CHANGED
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import process from "node:process";
|
|
22
22
|
import { getConfig, hook } from "../config/index.js";
|
|
23
|
+
import { getRequestContext } from "../http/request-context.js";
|
|
23
24
|
import { takeInvalidatedPaths } from "./html-cache.js";
|
|
25
|
+
import { getDataCacheStats } from "./data-cache.js";
|
|
26
|
+
import { isTransientStatus } from "./upstream-tracking.js";
|
|
27
|
+
import { upstreamCooldownMs } from "./upstream-limiter.js";
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* Isıtmanın canlı durumu. Dev araçları bunu okuyup ilerlemeyi gösterir;
|
|
@@ -45,11 +49,13 @@ export const prewarmProgress = {
|
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
/**
|
|
48
|
-
* Isıtma turu sırasında bastırılan
|
|
52
|
+
* Isıtma turu sırasında bastırılan uyarılar: mesaj → kaç kez görüldü.
|
|
49
53
|
*
|
|
50
54
|
* Yüzlerce yolu tarayan bir tur, upstream bir an için tıksırdığında yüzlerce
|
|
51
|
-
*
|
|
52
|
-
*
|
|
55
|
+
* satır loga döküyor ve asıl bilgi (kaç sayfa ısındı) kayboluyor. Tur boyunca
|
|
56
|
+
* mesajlar burada toplanır, tur bitince tek satırda özetlenir. Yol adı
|
|
57
|
+
* anahtara girmez: gruplanabilmesi için mesajın kendisi yeterli, tek bir yolun
|
|
58
|
+
* ayrıntısı zaten `entries` üzerinden dev panelinde görünüyor.
|
|
53
59
|
* @type {Map<string, number>}
|
|
54
60
|
*/
|
|
55
61
|
const suppressed = new Map();
|
|
@@ -59,19 +65,41 @@ const suppressed = new Map();
|
|
|
59
65
|
* ısıtmanın user-agent'ıyla geldiğinde doğru; gerçek trafiğin hataları her
|
|
60
66
|
* zaman loglanmaya devam eder.
|
|
61
67
|
*
|
|
62
|
-
* @param {{ get?: (name: string) => string | undefined }} req
|
|
68
|
+
* @param {{ get?: (name: string) => string | undefined } | null | undefined} req
|
|
63
69
|
* @returns {boolean}
|
|
64
70
|
*/
|
|
65
71
|
export function isPrewarmRequest(req) {
|
|
66
|
-
if (!prewarmProgress.active) return false;
|
|
72
|
+
if (!prewarmProgress.active || !req) return false;
|
|
67
73
|
const ua = req.get?.("user-agent");
|
|
68
74
|
return Boolean(ua) && ua === getConfig().brand.prewarmUserAgent;
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
78
|
+
* Şu an işlenen istek ısıtma turuna mı ait? `req` elde olmayan derin
|
|
79
|
+
* katmanlar (render, cache) için: yanıt nesnesi istek bağlamında taşınıyor.
|
|
80
|
+
*
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
function inPrewarmRequest() {
|
|
84
|
+
if (!prewarmProgress.active) return false;
|
|
85
|
+
return isPrewarmRequest(getRequestContext()?.res?.req);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Isıtma turuna ait bir uyarıyı loglamak yerine sayar.
|
|
90
|
+
*
|
|
91
|
+
* @param {string} message Gruplama anahtarı; yol adı içermemeli.
|
|
92
|
+
* @returns {boolean} `true` ise sayıldı, çağıran taraf loglamamalı.
|
|
93
|
+
*/
|
|
94
|
+
export function suppressForPrewarm(message) {
|
|
95
|
+
if (!inPrewarmRequest()) return false;
|
|
96
|
+
suppressed.set(message, (suppressed.get(message) ?? 0) + 1);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Bastırılan bir istek hatasını sayaca ekler. Yığın izi saklanmaz: özet
|
|
102
|
+
* satırının amacı "neyin bozulduğunu" göstermek, hatayı ayıklamak değil.
|
|
75
103
|
*
|
|
76
104
|
* @param {number} status
|
|
77
105
|
* @param {unknown} error
|
|
@@ -241,7 +269,10 @@ function devGateHeader() {
|
|
|
241
269
|
* Tur ilerlemesini `prewarmProgress`'e yazar. Tekrar turunda sayaçların
|
|
242
270
|
* anlamı değiştiği için çağıran taraf kendi formülünü verir.
|
|
243
271
|
* @param {() => Promise<void>} [pace] İstek başına beklenen hız freni.
|
|
244
|
-
* @returns {Promise<{ ok: number, failed: number,
|
|
272
|
+
* @returns {Promise<{ ok: number, failed: number,
|
|
273
|
+
* failures: { path: string, status: number }[] }>}
|
|
274
|
+
* `failures` durum koduyla birlikte döner: tekrar turuna yalnızca geçici
|
|
275
|
+
* hatalar alınıyor, kalıcı olanı yeniden denemek boşa çağrı.
|
|
245
276
|
*/
|
|
246
277
|
async function crawl(origin, paths, concurrency, report = undefined, pace = undefined) {
|
|
247
278
|
const { brand } = getConfig();
|
|
@@ -250,8 +281,8 @@ async function crawl(origin, paths, concurrency, report = undefined, pace = unde
|
|
|
250
281
|
let index = 0;
|
|
251
282
|
let ok = 0;
|
|
252
283
|
let failed = 0;
|
|
253
|
-
/** @type {string[]} */
|
|
254
|
-
const
|
|
284
|
+
/** @type {{ path: string, status: number }[]} */
|
|
285
|
+
const failures = [];
|
|
255
286
|
|
|
256
287
|
async function worker() {
|
|
257
288
|
while (index < paths.length) {
|
|
@@ -276,7 +307,7 @@ async function crawl(origin, paths, concurrency, report = undefined, pace = unde
|
|
|
276
307
|
if (response.ok) ok += 1;
|
|
277
308
|
else {
|
|
278
309
|
failed += 1;
|
|
279
|
-
|
|
310
|
+
failures.push({ path: target, status: response.status });
|
|
280
311
|
}
|
|
281
312
|
|
|
282
313
|
prewarmProgress.entries.push({
|
|
@@ -289,7 +320,8 @@ async function crawl(origin, paths, concurrency, report = undefined, pace = unde
|
|
|
289
320
|
});
|
|
290
321
|
} catch (error) {
|
|
291
322
|
failed += 1;
|
|
292
|
-
|
|
323
|
+
// Yanıt hiç gelmedi: ağ hatası her zaman geçici, tekrar denenir.
|
|
324
|
+
failures.push({ path: target, status: 0 });
|
|
293
325
|
prewarmProgress.entries.push({
|
|
294
326
|
path: target,
|
|
295
327
|
status: 0,
|
|
@@ -314,7 +346,66 @@ async function crawl(origin, paths, concurrency, report = undefined, pace = unde
|
|
|
314
346
|
Array.from({ length: Math.min(concurrency, paths.length) }, worker),
|
|
315
347
|
);
|
|
316
348
|
|
|
317
|
-
return { ok, failed,
|
|
349
|
+
return { ok, failed, failures };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Tekrar turuna girecek yollar. Yalnızca geçici hatalar: `400`/`403`/`404` gibi
|
|
354
|
+
* deterministik cevaplar tekrar denemekle düzelmez ve o çağrılar kotadan
|
|
355
|
+
* karşılıksız yiyor. Sınıflandırma render tarafıyla aynı listeden
|
|
356
|
+
* (`isTransientStatus`), yoksa iki yer birbirinden kayar.
|
|
357
|
+
*
|
|
358
|
+
* @param {{ path: string, status: number }[]} failures
|
|
359
|
+
* @returns {string[]}
|
|
360
|
+
*/
|
|
361
|
+
function retryablePaths(failures) {
|
|
362
|
+
return failures
|
|
363
|
+
.filter((failure) => isTransientStatus(failure.status))
|
|
364
|
+
.map((failure) => failure.path);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Tekrar turundan önce beklenecek süre.
|
|
369
|
+
*
|
|
370
|
+
* Sabit bekleme yanlış soruyu cevaplıyordu: doğru süreyi upstream biliyor.
|
|
371
|
+
* Hız freni açıkken `Retry-After` ya da devre kesicinin soğuma süresi zaten
|
|
372
|
+
* elimizde; 10 saniye kapalı kalacak bir kesiciden 2 saniye sonra tekrar
|
|
373
|
+
* denemek, aynı 429'u peşin peşin almak demek.
|
|
374
|
+
*
|
|
375
|
+
* @returns {number} ms
|
|
376
|
+
*/
|
|
377
|
+
function retryDelayMs() {
|
|
378
|
+
const configured = setting("PREWARM_RETRY_DELAY_MS", "retryDelayMs", 2000);
|
|
379
|
+
const cooldown = upstreamCooldownMs();
|
|
380
|
+
|
|
381
|
+
// Fren kapalıysa `cooldown` 0 olur ve davranış eskisi gibi kalır. Üst sınır
|
|
382
|
+
// turun sonsuza kadar açık kalmasını engelliyor.
|
|
383
|
+
return Math.min(Math.max(configured, cooldown), 60_000);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Turun veri önbelleği üzerinden upstream'e ne kadar dokunduğunu özetler.
|
|
388
|
+
*
|
|
389
|
+
* @param {ReturnType<typeof getDataCacheStats>} before Tur başındaki sayaçlar.
|
|
390
|
+
* @returns {string} Okunacak bir şey yoksa boş dize.
|
|
391
|
+
*/
|
|
392
|
+
function upstreamUsage(before) {
|
|
393
|
+
const after = getDataCacheStats();
|
|
394
|
+
const reads = after.reads - before.reads;
|
|
395
|
+
if (reads <= 0) return "";
|
|
396
|
+
|
|
397
|
+
const produced = after.produced - before.produced;
|
|
398
|
+
const coalesced = after.coalesced - before.coalesced;
|
|
399
|
+
const shared = after.shared - before.shared;
|
|
400
|
+
const served = reads - produced;
|
|
401
|
+
const ratio = Math.round((served / reads) * 100);
|
|
402
|
+
|
|
403
|
+
return (
|
|
404
|
+
`${produced} upstream call${produced === 1 ? "" : "s"} for ${reads} data read${reads === 1 ? "" : "s"} ` +
|
|
405
|
+
`(${ratio}% from the data cache` +
|
|
406
|
+
`${coalesced ? `, ${coalesced} coalesced` : ""}` +
|
|
407
|
+
`${shared ? `, ${shared} from the shared tier` : ""})`
|
|
408
|
+
);
|
|
318
409
|
}
|
|
319
410
|
|
|
320
411
|
/**
|
|
@@ -367,13 +458,18 @@ export async function prewarm({ origin, quiet = false, paths: only }) {
|
|
|
367
458
|
});
|
|
368
459
|
suppressed.clear();
|
|
369
460
|
|
|
461
|
+
// Kotanın gerçekten ne kadar harcandığı ancak veri önbelleğinden görülür:
|
|
462
|
+
// 400 sayfalık bir tur, ortak bir uç için tek çağrı da yapabilir dört yüz de.
|
|
463
|
+
const dataBefore = getDataCacheStats();
|
|
464
|
+
|
|
370
465
|
let ok = 0;
|
|
371
466
|
let failed = 0;
|
|
372
467
|
let recovered = 0;
|
|
468
|
+
let skippedRetry = 0;
|
|
373
469
|
try {
|
|
374
|
-
/** @type {string[]} */
|
|
375
|
-
let
|
|
376
|
-
({ ok, failed,
|
|
470
|
+
/** @type {{ path: string, status: number }[]} */
|
|
471
|
+
let failures;
|
|
472
|
+
({ ok, failed, failures } = await crawl(
|
|
377
473
|
origin,
|
|
378
474
|
paths,
|
|
379
475
|
concurrency,
|
|
@@ -384,18 +480,18 @@ export async function prewarm({ origin, quiet = false, paths: only }) {
|
|
|
384
480
|
// Hatalar çoğunlukla upstream rate limit'i (429): ilk tur yüzlerce sayfayı
|
|
385
481
|
// aynı anda çekerken API'yi zorluyor. Tek seri tekrar turu bu sayfaların
|
|
386
482
|
// önbelleğe girmesini sağlıyor; aksi hâlde ziyaretçi soğuk render'ı öder.
|
|
387
|
-
|
|
483
|
+
const retryPaths = retryablePaths(failures);
|
|
484
|
+
skippedRetry = failures.length - retryPaths.length;
|
|
485
|
+
|
|
486
|
+
if (retryPaths.length) {
|
|
388
487
|
const firstOk = ok;
|
|
389
488
|
const firstFailed = failed;
|
|
390
489
|
|
|
391
|
-
|
|
392
|
-
// 429'u almak demek.
|
|
393
|
-
const retryDelay = setting("PREWARM_RETRY_DELAY_MS", "retryDelayMs", 2000);
|
|
394
|
-
await sleep(retryDelay);
|
|
490
|
+
await sleep(retryDelayMs());
|
|
395
491
|
|
|
396
492
|
const retry = await crawl(
|
|
397
493
|
origin,
|
|
398
|
-
|
|
494
|
+
retryPaths,
|
|
399
495
|
1,
|
|
400
496
|
(retriedOk) => {
|
|
401
497
|
// Tekrar turunda her başarı bir hatayı başarıya çevirir.
|
|
@@ -425,10 +521,18 @@ export async function prewarm({ origin, quiet = false, paths: only }) {
|
|
|
425
521
|
`${pending.length ? `, ${pending.length} invalidated` : ""}` +
|
|
426
522
|
`${failed ? `, ${failed} failed` : ""}` +
|
|
427
523
|
`${recovered ? `, ${recovered} recovered on the retry pass` : ""}` +
|
|
524
|
+
`${skippedRetry ? `, ${skippedRetry} not retried (permanent)` : ""}` +
|
|
428
525
|
`${skipped > 0 ? `, ${skipped} ${rotate ? "deferred to the next pass" : "over the limit"}` : ""}` +
|
|
429
526
|
` (${(elapsed / 1000).toFixed(1)}s)`,
|
|
430
527
|
);
|
|
431
528
|
|
|
529
|
+
// Turun upstream'e ne kadar dokunduğu. Asıl karar bu satıra bakılarak
|
|
530
|
+
// veriliyor: oran düşükse çözüm hız freni değil, `withDataCache` TTL'ini
|
|
531
|
+
// tur aralığından uzun tutmak — fren çağrıları yavaşlatır, sayısını
|
|
532
|
+
// azaltmaz.
|
|
533
|
+
const upstreamCalls = upstreamUsage(dataBefore);
|
|
534
|
+
if (upstreamCalls) console.log(`[prewarm] ${upstreamCalls}`);
|
|
535
|
+
|
|
432
536
|
// Tur boyunca bastırılan hatalar: en sık görülenler önce, liste uzarsa
|
|
433
537
|
// kalanı tek satırda toplanır. Amaç, logu şişirmeden "ne bozuldu"yu
|
|
434
538
|
// görünür tutmak.
|
|
@@ -439,7 +543,7 @@ export async function prewarm({ origin, quiet = false, paths: only }) {
|
|
|
439
543
|
const total = ranked.reduce((sum, [, n]) => sum + n, 0);
|
|
440
544
|
|
|
441
545
|
console.warn(
|
|
442
|
-
`[prewarm] ${total}
|
|
546
|
+
`[prewarm] ${total} problem${total === 1 ? " was" : "s were"} not logged individually:\n` +
|
|
443
547
|
shown.map(([message, n]) => ` ${n}× ${message}`).join("\n") +
|
|
444
548
|
(rest ? `\n … ${rest} more in ${ranked.length - shown.length} other kinds` : ""),
|
|
445
549
|
);
|
package/src/server/render.js
CHANGED
|
@@ -39,6 +39,7 @@ import * as html from "../views/helpers/html.js";
|
|
|
39
39
|
import * as tags from "../views/helpers/tags.js";
|
|
40
40
|
import { loadComponents } from "../views/components/loader.js";
|
|
41
41
|
import { renderStatusPage } from "./status-page.js";
|
|
42
|
+
import { suppressForPrewarm } from "./prewarm.js";
|
|
42
43
|
|
|
43
44
|
const isDev = process.env.NODE_ENV === "development";
|
|
44
45
|
|
|
@@ -530,10 +531,12 @@ async function produce(controller, ctx) {
|
|
|
530
531
|
let failures = first.transient;
|
|
531
532
|
|
|
532
533
|
for (let round = 1; round <= attempts; round += 1) {
|
|
533
|
-
|
|
534
|
-
`
|
|
535
|
-
|
|
536
|
-
)
|
|
534
|
+
const retryDetail =
|
|
535
|
+
`returned notFound() while upstream is failing ` +
|
|
536
|
+
`(${summarizeFailures(failures)}), retrying (${round}/${attempts})`;
|
|
537
|
+
if (!suppressForPrewarm(retryDetail)) {
|
|
538
|
+
console.warn(`[render] ${ctx.pathname} ${retryDetail}`);
|
|
539
|
+
}
|
|
537
540
|
|
|
538
541
|
// Beklemeden tekrar denemek rate limit'e girmiş bir API'de aynı 429'u
|
|
539
542
|
// getirir; kısa bekleme hem pencerenin dönmesine şans verir hem de
|
|
@@ -558,10 +561,12 @@ async function produce(controller, ctx) {
|
|
|
558
561
|
|
|
559
562
|
// Denemeler tükendi. 404 yerine 503: önbelleğe girmez, `Retry-After` taşır
|
|
560
563
|
// ve bir sonraki istek gerçek içeriği üretebilir.
|
|
561
|
-
|
|
562
|
-
`
|
|
563
|
-
|
|
564
|
-
)
|
|
564
|
+
const outageDetail =
|
|
565
|
+
`could not be produced, upstream is still failing ` +
|
|
566
|
+
`(${summarizeFailures(failures)}), serving an uncached 503 instead of a 404`;
|
|
567
|
+
if (!suppressForPrewarm(outageDetail)) {
|
|
568
|
+
console.warn(`[render] ${ctx.pathname} ${outageDetail}`);
|
|
569
|
+
}
|
|
565
570
|
|
|
566
571
|
return {
|
|
567
572
|
html: await renderStatusPage(503),
|
|
@@ -624,17 +629,22 @@ function hasUpstreamFailures(pathname) {
|
|
|
624
629
|
const transient = failures.filter((failure) => isTransientStatus(failure.status));
|
|
625
630
|
const permanent = failures.filter((failure) => !isTransientStatus(failure.status));
|
|
626
631
|
|
|
632
|
+
// Isıtma turu yüzlerce yolu tarıyor; aynı upstream arızası her yol için bir
|
|
633
|
+
// satır basınca tur özeti kayboluyor. Turun uyarıları sayılıp sonunda
|
|
634
|
+
// toplanır (bkz. `suppressForPrewarm`).
|
|
627
635
|
if (permanent.length) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
636
|
+
const detail = `missing data, upstream is failing permanently (${summarizeFailures(permanent)})`;
|
|
637
|
+
if (!suppressForPrewarm(detail)) {
|
|
638
|
+
console.warn(`[render] ${pathname} was produced with ${detail}`);
|
|
639
|
+
}
|
|
631
640
|
}
|
|
632
641
|
|
|
633
642
|
if (!transient.length) return false;
|
|
634
643
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
644
|
+
const detail = `missing data, not caching it (${summarizeFailures(transient)})`;
|
|
645
|
+
if (!suppressForPrewarm(detail)) {
|
|
646
|
+
console.warn(`[render] ${pathname} was produced with ${detail}`);
|
|
647
|
+
}
|
|
638
648
|
|
|
639
649
|
return true;
|
|
640
650
|
}
|