@spinajs/log-source-graphana-loki 2.0.480 → 2.0.482
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/lib/cjs/index.d.ts +50 -14
- package/lib/cjs/index.d.ts.map +1 -1
- package/lib/cjs/index.js +164 -64
- package/lib/cjs/index.js.map +1 -1
- package/lib/mjs/index.d.ts +50 -14
- package/lib/mjs/index.d.ts.map +1 -1
- package/lib/mjs/index.js +163 -65
- package/lib/mjs/index.js.map +1 -1
- package/lib/tsconfig.cjs.tsbuildinfo +1 -1
- package/lib/tsconfig.mjs.tsbuildinfo +1 -1
- package/package.json +12 -12
package/lib/cjs/index.d.ts
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
import { IInstanceCheck } from "@spinajs/di";
|
|
2
|
-
import { Log, ILogEntry, LogTarget, ICommonTargetOptions } from "@spinajs/log";
|
|
3
|
-
import {
|
|
2
|
+
import { Log, ILogEntry, LogTarget, ICommonTargetOptions, BatchQueue } from "@spinajs/log";
|
|
3
|
+
import { AxiosInstance } from "axios";
|
|
4
|
+
import { ResiliencePipeline } from "@spinajs/util";
|
|
5
|
+
/**
|
|
6
|
+
* Decide whether a failed Loki push is worth retrying.
|
|
7
|
+
*
|
|
8
|
+
* Retryable: any error without an HTTP response ( network / timeout / DNS, eg.
|
|
9
|
+
* ECONNREFUSED / ETIMEDOUT / ECONNRESET / EAI_AGAIN ), or an HTTP response with
|
|
10
|
+
* status 429 / 502 / 503 / 504. Everything else ( 4xx client errors like 400 /
|
|
11
|
+
* 401 / 403 / 404, and any other status ) is treated as a permanent error that
|
|
12
|
+
* must surface rather than loop forever.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isRetryableLokiError(error: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Read a `Retry-After` header from a Loki error response and return the delay in
|
|
17
|
+
* milliseconds. Supports both the numeric ( delta-seconds ) and HTTP-date forms.
|
|
18
|
+
* Returns `undefined` when there is no usable header.
|
|
19
|
+
*/
|
|
20
|
+
export declare function retryAfterMs(error: unknown): number | undefined;
|
|
4
21
|
export interface IGraphanaOptions extends ICommonTargetOptions {
|
|
5
22
|
interval: number;
|
|
6
23
|
bufferSize: number;
|
|
24
|
+
/**
|
|
25
|
+
* Hard cap on the number of entries retained for retry. When a flush fails and the
|
|
26
|
+
* retained + newly buffered entries exceed this, the oldest are dropped.
|
|
27
|
+
*/
|
|
28
|
+
maxBufferSize: number;
|
|
7
29
|
timeout: number;
|
|
8
30
|
host: string;
|
|
9
31
|
auth: {
|
|
@@ -14,24 +36,38 @@ export interface IGraphanaOptions extends ICommonTargetOptions {
|
|
|
14
36
|
app: string;
|
|
15
37
|
};
|
|
16
38
|
}
|
|
17
|
-
declare enum TargetStatus {
|
|
18
|
-
WRITTING = 0,
|
|
19
|
-
PENDING = 1,
|
|
20
|
-
IDLE = 2
|
|
21
|
-
}
|
|
22
39
|
export declare class GraphanaLokiLogTarget extends LogTarget<IGraphanaOptions> implements IInstanceCheck {
|
|
23
40
|
protected Log: Log;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The single buffered-batch queue that owns accumulation, the flush timer and
|
|
43
|
+
* the maxQueue cap. The actual Loki push happens in `send`, wired as its
|
|
44
|
+
* `onFlush`.
|
|
45
|
+
*/
|
|
46
|
+
protected Queue: BatchQueue<ILogEntry>;
|
|
47
|
+
/**
|
|
48
|
+
* Set true when the queue overflows maxBufferSize, so the drop warning is
|
|
49
|
+
* emitted ONCE per overflow episode and reset when a flush succeeds.
|
|
50
|
+
*/
|
|
51
|
+
protected Overflowed: boolean;
|
|
52
|
+
protected AxiosInstance: AxiosInstance;
|
|
53
|
+
/**
|
|
54
|
+
* Reusable resilience pipeline guarding the Loki push with exponential
|
|
55
|
+
* backoff + jitter, honoring `Retry-After` and only retrying transient errors.
|
|
56
|
+
*/
|
|
57
|
+
protected RetryPipeline: ResiliencePipeline<void>;
|
|
29
58
|
constructor(options: IGraphanaOptions);
|
|
30
59
|
__checkInstance__(creationOptions: IGraphanaOptions[]): boolean;
|
|
31
60
|
resolve(): void;
|
|
32
61
|
write(data: ILogEntry): void;
|
|
62
|
+
forceFlush(): Promise<void>;
|
|
33
63
|
dispose(): Promise<void>;
|
|
34
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Push a single batch to Loki. Wired as the queue's `onFlush`.
|
|
66
|
+
*
|
|
67
|
+
* MUST resolve ( never reject ) so shutdown()/forceFlush() cannot reject: on a
|
|
68
|
+
* retryable-exhausted failure the batch is requeued at the front, on a
|
|
69
|
+
* non-retryable error it is dropped and surfaced via HasError/Error.
|
|
70
|
+
*/
|
|
71
|
+
protected send(batch: ILogEntry[]): Promise<void>;
|
|
35
72
|
}
|
|
36
|
-
export {};
|
|
37
73
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAgC,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,EAAU,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAgC,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,EAAU,UAAU,EAAE,MAAM,cAAc,CAAC;AAGnG,OAAc,EAAc,aAAa,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAAe,kBAAkB,EAAuC,MAAM,eAAe,CAAC;AAQrG;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW5D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAwB/D;AAED,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB;IAE5D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH;AAaD,qBAEa,qBAAsB,SAAQ,SAAS,CAAC,gBAAgB,CAAE,YAAW,cAAc;IAE9F,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAEnB;;;;OAIG;IACH,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAEvC;;;OAGG;IACH,SAAS,CAAC,UAAU,UAAS;IAE7B,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IAEvC;;;OAGG;IACH,SAAS,CAAC,aAAa,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;gBAEtC,OAAO,EAAE,gBAAgB;IAoBrC,iBAAiB,CAAC,eAAe,EAAE,gBAAgB,EAAE,GAAG,OAAO;IAIxD,OAAO,IAAI,IAAI;IA8Df,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAY5B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,OAAO;IAKpB;;;;;;OAMG;cACa,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAkExD"}
|
package/lib/cjs/index.js
CHANGED
|
@@ -13,97 +13,174 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.GraphanaLokiLogTarget = void 0;
|
|
16
|
+
exports.isRetryableLokiError = isRetryableLokiError;
|
|
17
|
+
exports.retryAfterMs = retryAfterMs;
|
|
16
18
|
/* eslint-disable promise/always-return */
|
|
17
19
|
/* eslint-disable security/detect-object-injection */
|
|
18
20
|
const configuration_common_1 = require("@spinajs/configuration-common");
|
|
19
21
|
const di_1 = require("@spinajs/di");
|
|
20
22
|
const log_1 = require("@spinajs/log");
|
|
21
23
|
const axios_1 = __importDefault(require("axios"));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const util_1 = require("@spinajs/util");
|
|
25
|
+
/**
|
|
26
|
+
* HTTP status codes worth retrying against Loki: rate limiting and transient
|
|
27
|
+
* upstream / gateway failures.
|
|
28
|
+
*/
|
|
29
|
+
const RETRYABLE_STATUS = new Set([429, 502, 503, 504]);
|
|
30
|
+
/**
|
|
31
|
+
* Decide whether a failed Loki push is worth retrying.
|
|
32
|
+
*
|
|
33
|
+
* Retryable: any error without an HTTP response ( network / timeout / DNS, eg.
|
|
34
|
+
* ECONNREFUSED / ETIMEDOUT / ECONNRESET / EAI_AGAIN ), or an HTTP response with
|
|
35
|
+
* status 429 / 502 / 503 / 504. Everything else ( 4xx client errors like 400 /
|
|
36
|
+
* 401 / 403 / 404, and any other status ) is treated as a permanent error that
|
|
37
|
+
* must surface rather than loop forever.
|
|
38
|
+
*/
|
|
39
|
+
function isRetryableLokiError(error) {
|
|
40
|
+
const status = error?.response?.status;
|
|
41
|
+
// No HTTP response -> network / timeout / DNS failure ( axios reports these
|
|
42
|
+
// without a `.response`, carrying codes like ECONNREFUSED / ETIMEDOUT /
|
|
43
|
+
// ECONNRESET / EAI_AGAIN ) -> retryable.
|
|
44
|
+
if (status === undefined) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return RETRYABLE_STATUS.has(status);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read a `Retry-After` header from a Loki error response and return the delay in
|
|
51
|
+
* milliseconds. Supports both the numeric ( delta-seconds ) and HTTP-date forms.
|
|
52
|
+
* Returns `undefined` when there is no usable header.
|
|
53
|
+
*/
|
|
54
|
+
function retryAfterMs(error) {
|
|
55
|
+
const err = error;
|
|
56
|
+
const headers = err?.response?.headers;
|
|
57
|
+
const raw = headers?.["retry-after"];
|
|
58
|
+
if (raw === undefined || raw === null) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
62
|
+
// numeric delta-seconds ( either a real number or a numeric string )
|
|
63
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
64
|
+
if (typeof value !== "boolean" && !Number.isNaN(asNumber) && `${value}`.trim() !== "") {
|
|
65
|
+
return Math.max(0, asNumber * 1000);
|
|
66
|
+
}
|
|
67
|
+
// HTTP-date form
|
|
68
|
+
const asDate = Date.parse(String(value));
|
|
69
|
+
if (!Number.isNaN(asDate)) {
|
|
70
|
+
return Math.max(0, asDate - Date.now());
|
|
71
|
+
}
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
28
74
|
// we mark per instance check becouse we can have multiple file targes
|
|
29
75
|
// for different files/paths/logs but we dont want to create every time writer for same.
|
|
30
76
|
let GraphanaLokiLogTarget = class GraphanaLokiLogTarget extends log_1.LogTarget {
|
|
31
77
|
constructor(options) {
|
|
32
78
|
super(options);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Set true when the queue overflows maxBufferSize, so the drop warning is
|
|
81
|
+
* emitted ONCE per overflow episode and reset when a flush succeeds.
|
|
82
|
+
*/
|
|
83
|
+
this.Overflowed = false;
|
|
84
|
+
// Config-driven targets ( resolved through the logger config ) nest their
|
|
85
|
+
// settings under an `options` sub-object ( like FileTarget ), while direct
|
|
86
|
+
// construction passes them flat. Hoist any nested options onto this.Options
|
|
87
|
+
// at highest precedence so both forms resolve the same flat reads below.
|
|
88
|
+
const nested = (options?.options ?? {});
|
|
36
89
|
this.Options = Object.assign({
|
|
37
90
|
interval: 3000,
|
|
38
91
|
bufferSize: 10,
|
|
92
|
+
maxBufferSize: 1000,
|
|
39
93
|
timeout: 1000,
|
|
40
|
-
}, this.Options);
|
|
94
|
+
}, this.Options, nested);
|
|
41
95
|
}
|
|
42
96
|
__checkInstance__(creationOptions) {
|
|
43
97
|
return this.Options.name === creationOptions[0].name;
|
|
44
98
|
}
|
|
45
99
|
resolve() {
|
|
100
|
+
const headers = {
|
|
101
|
+
"Content-Type": "application/json",
|
|
102
|
+
};
|
|
103
|
+
// A Loki endpoint may be unauthenticated: only attach the Basic auth header
|
|
104
|
+
// when credentials are actually configured, so a config without `auth`
|
|
105
|
+
// doesn't throw on `auth.username`.
|
|
106
|
+
if (this.Options.auth?.username) {
|
|
107
|
+
headers.Authorization = `Basic ${Buffer.from(`${this.Options.auth.username}:${this.Options.auth.password}`).toString("base64")}`;
|
|
108
|
+
}
|
|
46
109
|
this.AxiosInstance = axios_1.default.create({
|
|
47
110
|
baseURL: this.Options.host,
|
|
48
|
-
headers
|
|
49
|
-
"Content-Type": "application/json",
|
|
50
|
-
Authorization: `Basic ${Buffer.from(`${this.Options.auth.username}:${this.Options.auth.password}`).toString("base64")}`,
|
|
51
|
-
},
|
|
111
|
+
headers,
|
|
52
112
|
timeout: this.Options.timeout,
|
|
53
113
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
114
|
+
// one reusable pipeline: exponential backoff + jitter, 5 attempts, 1s..5s,
|
|
115
|
+
// honoring Retry-After and retrying only transient errors ( network + 429 /
|
|
116
|
+
// 502 / 503 / 504 ). Non-retryable errors fall straight through to .catch.
|
|
117
|
+
this.RetryPipeline = new util_1.ResiliencePipelineBuilder()
|
|
118
|
+
.addRetry({
|
|
119
|
+
MaxRetryAttempts: 5,
|
|
120
|
+
Delay: util_1.TimeSpan.fromSeconds(1),
|
|
121
|
+
MaxDelay: util_1.TimeSpan.fromSeconds(5),
|
|
122
|
+
BackoffType: util_1.BackoffType.Exponential,
|
|
123
|
+
UseJitter: true,
|
|
124
|
+
ShouldHandle: (o) => o.Error !== undefined && isRetryableLokiError(o.Error),
|
|
125
|
+
DelayGenerator: ({ Outcome }) => {
|
|
126
|
+
const ms = retryAfterMs(Outcome.Error);
|
|
127
|
+
return ms !== undefined ? util_1.TimeSpan.fromMilliseconds(ms) : undefined;
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
.build();
|
|
131
|
+
// one BatchQueue owns accumulation, the periodic flush timer and the
|
|
132
|
+
// maxQueue cap ( dropping the oldest ). The actual push happens in send().
|
|
133
|
+
this.Queue = new log_1.BatchQueue({
|
|
134
|
+
maxBatch: this.Options.bufferSize,
|
|
135
|
+
maxQueue: this.Options.maxBufferSize,
|
|
136
|
+
flushIntervalMs: this.Options.interval ?? 3000,
|
|
137
|
+
onFlush: (batch) => this.send(batch),
|
|
138
|
+
onOverflow: (droppedItems) => {
|
|
139
|
+
// emit the drop warning ONCE per overflow episode; reset on the next
|
|
140
|
+
// successful send() so a later episode warns again.
|
|
141
|
+
if (!this.Overflowed) {
|
|
142
|
+
this.Overflowed = true;
|
|
143
|
+
this.Log.warn(`Graphana loki buffer exceeded ${this.Options.maxBufferSize} entries, dropped ${droppedItems.length} oldest entries.`);
|
|
144
|
+
}
|
|
145
|
+
// spill each dropped entry to the fallback ( if a wrapper wired one ) so
|
|
146
|
+
// a durable target catches exactly what overflow discarded.
|
|
147
|
+
for (const entry of droppedItems) {
|
|
148
|
+
this.OnDropped?.(entry);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
super.resolve();
|
|
65
153
|
}
|
|
66
154
|
write(data) {
|
|
67
155
|
if (!this.Options.enabled) {
|
|
68
156
|
return;
|
|
69
157
|
}
|
|
70
158
|
data.Variables["n_timestamp"] = new Date().getTime() * 1000000;
|
|
71
|
-
|
|
72
|
-
//
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (this.Entries.length >= this.Options.bufferSize) {
|
|
78
|
-
this.Status = TargetStatus.PENDING;
|
|
79
|
-
this.WriteEntries = [...this.WriteEntries, ...this.Entries];
|
|
80
|
-
this.Entries = [];
|
|
81
|
-
// write at end of nodejs event loop all buffered messages at once
|
|
82
|
-
setImmediate(() => {
|
|
83
|
-
this.flush();
|
|
84
|
-
});
|
|
85
|
-
}
|
|
159
|
+
// fire-and-forget: Loki did not await the buffer write before either. A
|
|
160
|
+
// size-triggered flush ( when the batch fills ) runs in the background.
|
|
161
|
+
void this.Queue.enqueue(data);
|
|
162
|
+
}
|
|
163
|
+
forceFlush() {
|
|
164
|
+
return this.Queue ? this.Queue.forceFlush() : Promise.resolve();
|
|
86
165
|
}
|
|
87
166
|
async dispose() {
|
|
88
|
-
// stop flush timer
|
|
89
|
-
|
|
90
|
-
this.WriteEntries = [...this.WriteEntries, ...this.Entries];
|
|
91
|
-
this.Entries = [];
|
|
92
|
-
// write all messages from buffer
|
|
93
|
-
this.flush();
|
|
167
|
+
// stop the flush timer and perform a final best-effort flush.
|
|
168
|
+
await this.Queue.shutdown();
|
|
94
169
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Push a single batch to Loki. Wired as the queue's `onFlush`.
|
|
172
|
+
*
|
|
173
|
+
* MUST resolve ( never reject ) so shutdown()/forceFlush() cannot reject: on a
|
|
174
|
+
* retryable-exhausted failure the batch is requeued at the front, on a
|
|
175
|
+
* non-retryable error it is dropped and surfaced via HasError/Error.
|
|
176
|
+
*/
|
|
177
|
+
async send(batch) {
|
|
100
178
|
const streams = new Map();
|
|
101
179
|
const keyFor = (x) => {
|
|
102
180
|
return [this.Options.labels.app, x.Variables.logger, x.Variables.level, ...Object.values(this.Options.labels)].join("-");
|
|
103
181
|
};
|
|
104
182
|
const valFor = (x) => [x.Variables["n_timestamp"].toString(), (0, configuration_common_1.format)(x.Variables, this.Options.layout)];
|
|
105
|
-
|
|
106
|
-
this.WriteEntries.forEach((x) => {
|
|
183
|
+
batch.forEach((x) => {
|
|
107
184
|
const key = keyFor(x);
|
|
108
185
|
const stream = streams.get(key);
|
|
109
186
|
if (!stream) {
|
|
@@ -119,17 +196,40 @@ let GraphanaLokiLogTarget = class GraphanaLokiLogTarget extends log_1.LogTarget
|
|
|
119
196
|
}
|
|
120
197
|
stream.values.push(valFor(x));
|
|
121
198
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
this.
|
|
127
|
-
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
this.
|
|
132
|
-
|
|
199
|
+
// wrap ONLY the push in the resilience pipeline: transient failures are
|
|
200
|
+
// retried inline with backoff; we reach the catch only after retries are
|
|
201
|
+
// exhausted or immediately on a non-retryable error.
|
|
202
|
+
try {
|
|
203
|
+
await this.RetryPipeline.execute(() => this.AxiosInstance.post("/loki/api/v1/push", { streams: [...streams.values()] }).then(() => undefined));
|
|
204
|
+
// successful write -> clear any previous error state
|
|
205
|
+
this.HasError = false;
|
|
206
|
+
this.Error = null;
|
|
207
|
+
// drained successfully - allow the next overflow episode to warn again
|
|
208
|
+
this.Overflowed = false;
|
|
209
|
+
this.Log.trace(`Wrote buffered messages to graphana target at url ${this.Options.host}, ${batch.length} messages.`);
|
|
210
|
+
}
|
|
211
|
+
catch (err) {
|
|
212
|
+
// reached only after retries are exhausted ( transient ) or immediately
|
|
213
|
+
// for a non-retryable error.
|
|
214
|
+
this.HasError = true;
|
|
215
|
+
this.Error = err;
|
|
216
|
+
if (isRetryableLokiError(err)) {
|
|
217
|
+
// retryable but exhausted: put the batch back at the front so the next
|
|
218
|
+
// flush retries it. The queue enforces the maxQueue cap ( dropping the
|
|
219
|
+
// oldest + onOverflow ) so the retry buffer never grows without bound.
|
|
220
|
+
this.Log.error(err, `Cannot write log messages to graphana target - retries exhausted, retaining entries for next flush.`);
|
|
221
|
+
this.Queue.requeueFront(batch);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// permanent error ( eg. 400 malformed, 401 bad auth ): retrying forever
|
|
225
|
+
// would only hide a config problem. Drop the batch and surface it, but
|
|
226
|
+
// first spill each undelivered entry to the fallback ( if a wrapper wired
|
|
227
|
+
// one ) so a durable target catches exactly what was not delivered.
|
|
228
|
+
for (const entry of batch) {
|
|
229
|
+
this.OnDropped?.(entry);
|
|
230
|
+
}
|
|
231
|
+
this.Log.error(err, `Cannot write log messages to graphana target - dropping ${batch.length} entries because the error is non-retryable.`);
|
|
232
|
+
}
|
|
133
233
|
}
|
|
134
234
|
};
|
|
135
235
|
exports.GraphanaLokiLogTarget = GraphanaLokiLogTarget;
|
package/lib/cjs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AA0BA,oDAWC;AAOD,oCAwBC;AApED,0CAA0C;AAC1C,qDAAqD;AACrD,wEAAuD;AACvD,oCAA2E;AAC3E,sCAAmG;AAGnG,kDAAyD;AAEzD,wCAAqG;AAErG;;;GAGG;AACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAS,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE/D;;;;;;;;GAQG;AACH,SAAgB,oBAAoB,CAAC,KAAc;IACjD,MAAM,MAAM,GAAI,KAAgC,EAAE,QAAQ,EAAE,MAAM,CAAC;IAEnE,4EAA4E;IAC5E,wEAAwE;IACxE,yCAAyC;IACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAgB,YAAY,CAAC,KAAc;IACzC,MAAM,GAAG,GAAG,KAA+B,CAAC;IAC5C,MAAM,OAAO,GAAG,GAAG,EAAE,QAAQ,EAAE,OAA8C,CAAC;IAC9E,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC;IAErC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACtC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAEhD,qEAAqE;IACrE,MAAM,QAAQ,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnE,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACtF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,iBAAiB;IACjB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC1C,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AA+BD,sEAAsE;AACtE,wFAAwF;AAGjF,IAAM,qBAAqB,GAA3B,MAAM,qBAAsB,SAAQ,eAA2B;IAyBpE,YAAY,OAAyB;QACnC,KAAK,CAAC,OAAO,CAAC,CAAC;QAfjB;;;WAGG;QACO,eAAU,GAAG,KAAK,CAAC;QAa3B,0EAA0E;QAC1E,2EAA2E;QAC3E,4EAA4E;QAC5E,yEAAyE;QACzE,MAAM,MAAM,GAAG,CAAE,OAAe,EAAE,OAAO,IAAI,EAAE,CAA8B,CAAC;QAC9E,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAC1B;YACE,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,EAAE;YACd,aAAa,EAAE,IAAI;YACnB,OAAO,EAAE,IAAI;SACd,EACD,IAAI,CAAC,OAAO,EACZ,MAAM,CACP,CAAC;IACJ,CAAC;IAED,iBAAiB,CAAC,eAAmC;QACnD,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,CAAC;IAEM,OAAO;QACZ,MAAM,OAAO,GAA2B;YACtC,cAAc,EAAE,kBAAkB;SACnC,CAAC;QAEF,4EAA4E;QAC5E,uEAAuE;QACvE,oCAAoC;QACpC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;YAChC,OAAO,CAAC,aAAa,GAAG,SAAS,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnI,CAAC;QAED,IAAI,CAAC,aAAa,GAAG,eAAK,CAAC,MAAM,CAAC;YAChC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI;YAC1B,OAAO;YACP,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,OAAO;SAC9B,CAAC,CAAC;QAEH,2EAA2E;QAC3E,4EAA4E;QAC5E,2EAA2E;QAC3E,IAAI,CAAC,aAAa,GAAG,IAAI,gCAAyB,EAAQ;aACvD,QAAQ,CAAC;YACR,gBAAgB,EAAE,CAAC;YACnB,KAAK,EAAE,eAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;YAC9B,QAAQ,EAAE,eAAQ,CAAC,WAAW,CAAC,CAAC,CAAC;YACjC,WAAW,EAAE,kBAAW,CAAC,WAAW;YACpC,SAAS,EAAE,IAAI;YACf,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,IAAI,oBAAoB,CAAC,CAAC,CAAC,KAAK,CAAC;YAC3E,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE;gBAC9B,MAAM,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBACvC,OAAO,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,eAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACtE,CAAC;SACF,CAAC;aACD,KAAK,EAAE,CAAC;QAEX,qEAAqE;QACrE,2EAA2E;QAC3E,IAAI,CAAC,KAAK,GAAG,IAAI,gBAAU,CAAY;YACrC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACjC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACpC,eAAe,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,IAAI,IAAI;YAC9C,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;YACpC,UAAU,EAAE,CAAC,YAAY,EAAE,EAAE;gBAC3B,qEAAqE;gBACrE,oDAAoD;gBACpD,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;oBACrB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;oBACvB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iCAAiC,IAAI,CAAC,OAAO,CAAC,aAAa,qBAAqB,YAAY,CAAC,MAAM,kBAAkB,CAAC,CAAC;gBACvI,CAAC;gBAED,yEAAyE;gBACzE,4DAA4D;gBAC5D,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;oBACjC,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC;SACF,CAAC,CAAC;QAEH,KAAK,CAAC,OAAO,EAAE,CAAC;IAClB,CAAC;IAEM,KAAK,CAAC,IAAe;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC;QAE/D,wEAAwE;QACxE,wEAAwE;QACxE,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAEM,UAAU;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;IAClE,CAAC;IAEM,KAAK,CAAC,OAAO;QAClB,8DAA8D;QAC9D,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;OAMG;IACO,KAAK,CAAC,IAAI,CAAC,KAAkB;QACrC,MAAM,OAAO,GAAwB,IAAI,GAAG,EAAkB,CAAC;QAC/D,MAAM,MAAM,GAAG,CAAC,CAAY,EAAE,EAAE;YAC9B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3H,CAAC,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,CAAY,EAAE,EAAE,CAAC,CAAE,CAAC,CAAC,SAAS,CAAC,aAAa,CAAS,CAAC,QAAQ,EAAE,EAAE,IAAA,6BAAM,EAAC,CAAC,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAE5H,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE;YAClB,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACtB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAEhC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE;oBACf,MAAM,EAAE;wBACN,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,MAAM;wBAC1B,KAAK,EAAE,CAAC,CAAC,SAAS,CAAC,KAAK;wBACxB,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM;qBACvB;oBACD,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;iBACpB,CAAC,CAAC;gBAEH,OAAO;YACT,CAAC;YAED,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;QAEH,wEAAwE;QACxE,yEAAyE;QACzE,qDAAqD;QACrD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,mBAAmB,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;YAE/I,qDAAqD;YACrD,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;YACtB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;YAClB,uEAAuE;YACvE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YAExB,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,qDAAqD,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,MAAM,YAAY,CAAC,CAAC;QACtH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,wEAAwE;YACxE,6BAA6B;YAC7B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;YACrB,IAAI,CAAC,KAAK,GAAG,GAAY,CAAC;YAE1B,IAAI,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,uEAAuE;gBACvE,uEAAuE;gBACvE,uEAAuE;gBACvE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,qGAAqG,CAAC,CAAC;gBAC3H,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;gBAC/B,OAAO;YACT,CAAC;YAED,wEAAwE;YACxE,uEAAuE;YACvE,0EAA0E;YAC1E,oEAAoE;YACpE,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;gBAC1B,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC;YAC1B,CAAC;YAED,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,2DAA2D,KAAK,CAAC,MAAM,8CAA8C,CAAC,CAAC;QAC7I,CAAC;IACH,CAAC;CACF,CAAA;AA7MY,sDAAqB;AAEtB;IADT,IAAA,YAAM,EAAC,eAAe,CAAC;8BACT,SAAG;kDAAC;gCAFR,qBAAqB;IAFjC,IAAA,qBAAgB,GAAE;IAClB,IAAA,eAAU,EAAC,mBAAmB,CAAC;;GACnB,qBAAqB,CA6MjC"}
|
package/lib/mjs/index.d.ts
CHANGED
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
import { IInstanceCheck } from "@spinajs/di";
|
|
2
|
-
import { Log, ILogEntry, LogTarget, ICommonTargetOptions } from "@spinajs/log";
|
|
3
|
-
import {
|
|
2
|
+
import { Log, ILogEntry, LogTarget, ICommonTargetOptions, BatchQueue } from "@spinajs/log";
|
|
3
|
+
import { AxiosInstance } from "axios";
|
|
4
|
+
import { ResiliencePipeline } from "@spinajs/util";
|
|
5
|
+
/**
|
|
6
|
+
* Decide whether a failed Loki push is worth retrying.
|
|
7
|
+
*
|
|
8
|
+
* Retryable: any error without an HTTP response ( network / timeout / DNS, eg.
|
|
9
|
+
* ECONNREFUSED / ETIMEDOUT / ECONNRESET / EAI_AGAIN ), or an HTTP response with
|
|
10
|
+
* status 429 / 502 / 503 / 504. Everything else ( 4xx client errors like 400 /
|
|
11
|
+
* 401 / 403 / 404, and any other status ) is treated as a permanent error that
|
|
12
|
+
* must surface rather than loop forever.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isRetryableLokiError(error: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Read a `Retry-After` header from a Loki error response and return the delay in
|
|
17
|
+
* milliseconds. Supports both the numeric ( delta-seconds ) and HTTP-date forms.
|
|
18
|
+
* Returns `undefined` when there is no usable header.
|
|
19
|
+
*/
|
|
20
|
+
export declare function retryAfterMs(error: unknown): number | undefined;
|
|
4
21
|
export interface IGraphanaOptions extends ICommonTargetOptions {
|
|
5
22
|
interval: number;
|
|
6
23
|
bufferSize: number;
|
|
24
|
+
/**
|
|
25
|
+
* Hard cap on the number of entries retained for retry. When a flush fails and the
|
|
26
|
+
* retained + newly buffered entries exceed this, the oldest are dropped.
|
|
27
|
+
*/
|
|
28
|
+
maxBufferSize: number;
|
|
7
29
|
timeout: number;
|
|
8
30
|
host: string;
|
|
9
31
|
auth: {
|
|
@@ -14,24 +36,38 @@ export interface IGraphanaOptions extends ICommonTargetOptions {
|
|
|
14
36
|
app: string;
|
|
15
37
|
};
|
|
16
38
|
}
|
|
17
|
-
declare enum TargetStatus {
|
|
18
|
-
WRITTING = 0,
|
|
19
|
-
PENDING = 1,
|
|
20
|
-
IDLE = 2
|
|
21
|
-
}
|
|
22
39
|
export declare class GraphanaLokiLogTarget extends LogTarget<IGraphanaOptions> implements IInstanceCheck {
|
|
23
40
|
protected Log: Log;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The single buffered-batch queue that owns accumulation, the flush timer and
|
|
43
|
+
* the maxQueue cap. The actual Loki push happens in `send`, wired as its
|
|
44
|
+
* `onFlush`.
|
|
45
|
+
*/
|
|
46
|
+
protected Queue: BatchQueue<ILogEntry>;
|
|
47
|
+
/**
|
|
48
|
+
* Set true when the queue overflows maxBufferSize, so the drop warning is
|
|
49
|
+
* emitted ONCE per overflow episode and reset when a flush succeeds.
|
|
50
|
+
*/
|
|
51
|
+
protected Overflowed: boolean;
|
|
52
|
+
protected AxiosInstance: AxiosInstance;
|
|
53
|
+
/**
|
|
54
|
+
* Reusable resilience pipeline guarding the Loki push with exponential
|
|
55
|
+
* backoff + jitter, honoring `Retry-After` and only retrying transient errors.
|
|
56
|
+
*/
|
|
57
|
+
protected RetryPipeline: ResiliencePipeline<void>;
|
|
29
58
|
constructor(options: IGraphanaOptions);
|
|
30
59
|
__checkInstance__(creationOptions: IGraphanaOptions[]): boolean;
|
|
31
60
|
resolve(): void;
|
|
32
61
|
write(data: ILogEntry): void;
|
|
62
|
+
forceFlush(): Promise<void>;
|
|
33
63
|
dispose(): Promise<void>;
|
|
34
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Push a single batch to Loki. Wired as the queue's `onFlush`.
|
|
66
|
+
*
|
|
67
|
+
* MUST resolve ( never reject ) so shutdown()/forceFlush() cannot reject: on a
|
|
68
|
+
* retryable-exhausted failure the batch is requeued at the front, on a
|
|
69
|
+
* non-retryable error it is dropped and surfaced via HasError/Error.
|
|
70
|
+
*/
|
|
71
|
+
protected send(batch: ILogEntry[]): Promise<void>;
|
|
35
72
|
}
|
|
36
|
-
export {};
|
|
37
73
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/mjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAgC,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,EAAU,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAgC,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,oBAAoB,EAAU,UAAU,EAAE,MAAM,cAAc,CAAC;AAGnG,OAAc,EAAc,aAAa,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAO,EAAe,kBAAkB,EAAuC,MAAM,eAAe,CAAC;AAQrG;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW5D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAwB/D;AAED,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB;IAE5D,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE;QACJ,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,EAAE;QACN,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;CACH;AAaD,qBAEa,qBAAsB,SAAQ,SAAS,CAAC,gBAAgB,CAAE,YAAW,cAAc;IAE9F,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;IAEnB;;;;OAIG;IACH,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;IAEvC;;;OAGG;IACH,SAAS,CAAC,UAAU,UAAS;IAE7B,SAAS,CAAC,aAAa,EAAE,aAAa,CAAC;IAEvC;;;OAGG;IACH,SAAS,CAAC,aAAa,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC;gBAEtC,OAAO,EAAE,gBAAgB;IAoBrC,iBAAiB,CAAC,eAAe,EAAE,gBAAgB,EAAE,GAAG,OAAO;IAIxD,OAAO,IAAI,IAAI;IA8Df,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAY5B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,OAAO;IAKpB;;;;;;OAMG;cACa,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAkExD"}
|