@spinajs/log-otlp 2.0.482 → 2.0.485
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 +134 -0
- package/lib/cjs/index.d.ts.map +1 -0
- package/lib/cjs/index.js +332 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/cjs/package.json +1 -0
- package/lib/mjs/index.d.ts +134 -0
- package/lib/mjs/index.d.ts.map +1 -0
- package/lib/mjs/index.js +320 -0
- package/lib/mjs/index.js.map +1 -0
- package/lib/mjs/package.json +1 -0
- package/lib/tsconfig.cjs.tsbuildinfo +1 -0
- package/lib/tsconfig.mjs.tsbuildinfo +1 -0
- package/package.json +6 -6
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { IInstanceCheck } from "@spinajs/di";
|
|
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 OTLP export 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 isRetryableOtlpError(error: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Read a `Retry-After` header from an OTLP error response and return the delay
|
|
17
|
+
* in milliseconds. Supports both the numeric ( delta-seconds ) and HTTP-date
|
|
18
|
+
* forms. Returns `undefined` when there is no usable header.
|
|
19
|
+
*/
|
|
20
|
+
export declare function retryAfterMs(error: unknown): number | undefined;
|
|
21
|
+
export interface IOtlpTargetOptions extends ICommonTargetOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Base URL of the OTLP/HTTP receiver, eg. `http://localhost:4318`. The target
|
|
24
|
+
* POSTs the encoded logs to `${endpoint}/v1/logs`.
|
|
25
|
+
*/
|
|
26
|
+
endpoint: string;
|
|
27
|
+
/**
|
|
28
|
+
* Extra HTTP headers sent with every export ( eg. auth tokens / API keys ).
|
|
29
|
+
*/
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Resource attributes describing the emitting service ( eg.
|
|
33
|
+
* `{ "service.name": "my-service" }` ). Emitted on `resourceLogs[].resource`.
|
|
34
|
+
*/
|
|
35
|
+
resource?: Record<string, string | number | boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Instrumentation scope name reported on each `scopeLogs[].scope`. Default
|
|
38
|
+
* `@spinajs/log`.
|
|
39
|
+
*/
|
|
40
|
+
scopeName?: string;
|
|
41
|
+
/** Periodic flush tick in ms. Default 3000. */
|
|
42
|
+
interval: number;
|
|
43
|
+
/** Flush automatically once this many records are buffered ( maxBatch ). Default 10. */
|
|
44
|
+
bufferSize: number;
|
|
45
|
+
/**
|
|
46
|
+
* Hard cap on the number of records retained for retry ( maxQueue ). When a
|
|
47
|
+
* flush fails and retained + newly buffered records exceed this, the oldest
|
|
48
|
+
* are dropped. Default 1000.
|
|
49
|
+
*/
|
|
50
|
+
maxBufferSize: number;
|
|
51
|
+
/** Per-request HTTP timeout in ms. Default 5000. */
|
|
52
|
+
timeout: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Internal buffered record: the log entry plus the wall-clock event time
|
|
56
|
+
* captured at `write()` time, as an OTLP nanosecond string.
|
|
57
|
+
*/
|
|
58
|
+
export interface IOtlpRecord {
|
|
59
|
+
entry: ILogEntry;
|
|
60
|
+
timeUnixNano: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* An OTLP `AnyValue`. Only one field is set at a time.
|
|
64
|
+
*/
|
|
65
|
+
interface IOtlpAnyValue {
|
|
66
|
+
stringValue?: string;
|
|
67
|
+
boolValue?: boolean;
|
|
68
|
+
intValue?: string;
|
|
69
|
+
doubleValue?: number;
|
|
70
|
+
}
|
|
71
|
+
interface IOtlpKeyValue {
|
|
72
|
+
key: string;
|
|
73
|
+
value: IOtlpAnyValue;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Encode a single scalar / structured value as an OTLP `AnyValue`. Never throws:
|
|
77
|
+
* objects / arrays fall back to a JSON string via `safeStringify`.
|
|
78
|
+
*
|
|
79
|
+
* - string -> `{ stringValue }`
|
|
80
|
+
* - boolean -> `{ boolValue }`
|
|
81
|
+
* - integer number -> `{ intValue: String(v) }`
|
|
82
|
+
* - non-integer number -> `{ doubleValue: v }`
|
|
83
|
+
* - anything else ( object / array / bigint / etc. ) -> `{ stringValue: safeStringify(v) }`
|
|
84
|
+
*/
|
|
85
|
+
export declare function anyValue(v: unknown): IOtlpAnyValue;
|
|
86
|
+
/**
|
|
87
|
+
* Encode a plain record as an array of OTLP `KeyValue`s. Missing / empty input
|
|
88
|
+
* yields an empty array.
|
|
89
|
+
*/
|
|
90
|
+
export declare function kv(obj?: Record<string, unknown>): IOtlpKeyValue[];
|
|
91
|
+
/**
|
|
92
|
+
* Map one buffered record to an OTLP `LogRecord`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function toLogRecord(record: IOtlpRecord): Record<string, unknown>;
|
|
95
|
+
/**
|
|
96
|
+
* Build the full OTLP Logs JSON payload for a batch of records.
|
|
97
|
+
*/
|
|
98
|
+
export declare function toOtlp(batch: IOtlpRecord[], resource?: Record<string, string | number | boolean>, scopeName?: string): Record<string, unknown>;
|
|
99
|
+
export declare class OtlpLogTarget extends LogTarget<IOtlpTargetOptions> implements IInstanceCheck {
|
|
100
|
+
protected Log: Log;
|
|
101
|
+
/**
|
|
102
|
+
* The single buffered-batch queue that owns accumulation, the flush timer and
|
|
103
|
+
* the maxQueue cap. The actual OTLP export happens in `send`, wired as its
|
|
104
|
+
* `onFlush`.
|
|
105
|
+
*/
|
|
106
|
+
protected Queue: BatchQueue<IOtlpRecord>;
|
|
107
|
+
/**
|
|
108
|
+
* Set true when the queue overflows maxBufferSize, so the drop warning is
|
|
109
|
+
* emitted ONCE per overflow episode and reset when a flush succeeds.
|
|
110
|
+
*/
|
|
111
|
+
protected Overflowed: boolean;
|
|
112
|
+
protected AxiosInstance: AxiosInstance;
|
|
113
|
+
/**
|
|
114
|
+
* Reusable resilience pipeline guarding the OTLP export with exponential
|
|
115
|
+
* backoff + jitter, honoring `Retry-After` and only retrying transient errors.
|
|
116
|
+
*/
|
|
117
|
+
protected RetryPipeline: ResiliencePipeline<void>;
|
|
118
|
+
constructor(options: IOtlpTargetOptions);
|
|
119
|
+
__checkInstance__(creationOptions: IOtlpTargetOptions[]): boolean;
|
|
120
|
+
resolve(): void;
|
|
121
|
+
write(data: ILogEntry): void;
|
|
122
|
+
forceFlush(): Promise<void>;
|
|
123
|
+
dispose(): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Export a single batch to the OTLP endpoint. Wired as the queue's `onFlush`.
|
|
126
|
+
*
|
|
127
|
+
* MUST resolve ( never reject ) so shutdown()/forceFlush() cannot reject: on a
|
|
128
|
+
* retryable-exhausted failure the batch is requeued at the front, on a
|
|
129
|
+
* non-retryable error it is dropped and surfaced via HasError/Error.
|
|
130
|
+
*/
|
|
131
|
+
protected send(batch: IOtlpRecord[]): Promise<void>;
|
|
132
|
+
}
|
|
133
|
+
export {};
|
|
134
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/cjs/index.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.OtlpLogTarget = void 0;
|
|
16
|
+
exports.isRetryableOtlpError = isRetryableOtlpError;
|
|
17
|
+
exports.retryAfterMs = retryAfterMs;
|
|
18
|
+
exports.anyValue = anyValue;
|
|
19
|
+
exports.kv = kv;
|
|
20
|
+
exports.toLogRecord = toLogRecord;
|
|
21
|
+
exports.toOtlp = toOtlp;
|
|
22
|
+
/* eslint-disable promise/always-return */
|
|
23
|
+
/* eslint-disable security/detect-object-injection */
|
|
24
|
+
const di_1 = require("@spinajs/di");
|
|
25
|
+
const log_1 = require("@spinajs/log");
|
|
26
|
+
const axios_1 = __importDefault(require("axios"));
|
|
27
|
+
const util_1 = require("@spinajs/util");
|
|
28
|
+
/**
|
|
29
|
+
* HTTP status codes worth retrying against an OTLP endpoint: rate limiting and
|
|
30
|
+
* transient upstream / gateway failures.
|
|
31
|
+
*/
|
|
32
|
+
const RETRYABLE_STATUS = new Set([429, 502, 503, 504]);
|
|
33
|
+
/**
|
|
34
|
+
* Decide whether a failed OTLP export is worth retrying.
|
|
35
|
+
*
|
|
36
|
+
* Retryable: any error without an HTTP response ( network / timeout / DNS, eg.
|
|
37
|
+
* ECONNREFUSED / ETIMEDOUT / ECONNRESET / EAI_AGAIN ), or an HTTP response with
|
|
38
|
+
* status 429 / 502 / 503 / 504. Everything else ( 4xx client errors like 400 /
|
|
39
|
+
* 401 / 403 / 404, and any other status ) is treated as a permanent error that
|
|
40
|
+
* must surface rather than loop forever.
|
|
41
|
+
*/
|
|
42
|
+
function isRetryableOtlpError(error) {
|
|
43
|
+
const status = error?.response?.status;
|
|
44
|
+
// No HTTP response -> network / timeout / DNS failure ( axios reports these
|
|
45
|
+
// without a `.response`, carrying codes like ECONNREFUSED / ETIMEDOUT /
|
|
46
|
+
// ECONNRESET / EAI_AGAIN ) -> retryable.
|
|
47
|
+
if (status === undefined) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return RETRYABLE_STATUS.has(status);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read a `Retry-After` header from an OTLP error response and return the delay
|
|
54
|
+
* in milliseconds. Supports both the numeric ( delta-seconds ) and HTTP-date
|
|
55
|
+
* forms. Returns `undefined` when there is no usable header.
|
|
56
|
+
*/
|
|
57
|
+
function retryAfterMs(error) {
|
|
58
|
+
const err = error;
|
|
59
|
+
const headers = err?.response?.headers;
|
|
60
|
+
const raw = headers?.["retry-after"];
|
|
61
|
+
if (raw === undefined || raw === null) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
65
|
+
// numeric delta-seconds ( either a real number or a numeric string )
|
|
66
|
+
const asNumber = typeof value === "number" ? value : Number(value);
|
|
67
|
+
if (typeof value !== "boolean" && !Number.isNaN(asNumber) && `${value}`.trim() !== "") {
|
|
68
|
+
return Math.max(0, asNumber * 1000);
|
|
69
|
+
}
|
|
70
|
+
// HTTP-date form
|
|
71
|
+
const asDate = Date.parse(String(value));
|
|
72
|
+
if (!Number.isNaN(asDate)) {
|
|
73
|
+
return Math.max(0, asDate - Date.now());
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Encode a single scalar / structured value as an OTLP `AnyValue`. Never throws:
|
|
79
|
+
* objects / arrays fall back to a JSON string via `safeStringify`.
|
|
80
|
+
*
|
|
81
|
+
* - string -> `{ stringValue }`
|
|
82
|
+
* - boolean -> `{ boolValue }`
|
|
83
|
+
* - integer number -> `{ intValue: String(v) }`
|
|
84
|
+
* - non-integer number -> `{ doubleValue: v }`
|
|
85
|
+
* - anything else ( object / array / bigint / etc. ) -> `{ stringValue: safeStringify(v) }`
|
|
86
|
+
*/
|
|
87
|
+
function anyValue(v) {
|
|
88
|
+
if (typeof v === "string") {
|
|
89
|
+
return { stringValue: v };
|
|
90
|
+
}
|
|
91
|
+
if (typeof v === "boolean") {
|
|
92
|
+
return { boolValue: v };
|
|
93
|
+
}
|
|
94
|
+
if (typeof v === "number") {
|
|
95
|
+
return Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v };
|
|
96
|
+
}
|
|
97
|
+
return { stringValue: (0, log_1.safeStringify)(v) };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Encode a plain record as an array of OTLP `KeyValue`s. Missing / empty input
|
|
101
|
+
* yields an empty array.
|
|
102
|
+
*/
|
|
103
|
+
function kv(obj) {
|
|
104
|
+
if (!obj) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return Object.keys(obj).map((key) => ({ key, value: anyValue(obj[key]) }));
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Variable keys that get dedicated OTLP mapping ( body / severity / trace ) and
|
|
111
|
+
* must therefore be excluded from the generic attribute set.
|
|
112
|
+
*/
|
|
113
|
+
const RESERVED_VARIABLE_KEYS = new Set(["message", "level", "traceId", "spanId"]);
|
|
114
|
+
/**
|
|
115
|
+
* Map one buffered record to an OTLP `LogRecord`.
|
|
116
|
+
*/
|
|
117
|
+
function toLogRecord(record) {
|
|
118
|
+
const { entry, timeUnixNano } = record;
|
|
119
|
+
const vars = entry.Variables ?? {};
|
|
120
|
+
const attributes = [];
|
|
121
|
+
for (const key of Object.keys(vars)) {
|
|
122
|
+
if (RESERVED_VARIABLE_KEYS.has(key)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const value = vars[key];
|
|
126
|
+
// The serializer registry turns a logged `Error` into a plain
|
|
127
|
+
// `{ name, message, stack, ... }` record. Emit the OTel exception.* semantic
|
|
128
|
+
// convention attributes instead of a raw serialized object.
|
|
129
|
+
if (key === "error" && value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
130
|
+
const err = value;
|
|
131
|
+
if (err.name !== undefined) {
|
|
132
|
+
attributes.push({ key: "exception.type", value: anyValue(err.name) });
|
|
133
|
+
}
|
|
134
|
+
if (err.message !== undefined) {
|
|
135
|
+
attributes.push({ key: "exception.message", value: anyValue(err.message) });
|
|
136
|
+
}
|
|
137
|
+
if (err.stack !== undefined) {
|
|
138
|
+
attributes.push({ key: "exception.stacktrace", value: anyValue(err.stack) });
|
|
139
|
+
}
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
attributes.push({ key, value: anyValue(value) });
|
|
143
|
+
}
|
|
144
|
+
const logRecord = {
|
|
145
|
+
timeUnixNano,
|
|
146
|
+
observedTimeUnixNano: timeUnixNano,
|
|
147
|
+
severityNumber: log_1.LogLevelToSeverityNumber[entry.Level],
|
|
148
|
+
severityText: vars.level,
|
|
149
|
+
body: { stringValue: String(vars.message ?? "") },
|
|
150
|
+
attributes,
|
|
151
|
+
};
|
|
152
|
+
// traceId / spanId ( hex strings from the http traceparent feature ) become
|
|
153
|
+
// top-level fields on the record, NOT attributes.
|
|
154
|
+
if (vars.traceId !== undefined && vars.traceId !== null) {
|
|
155
|
+
logRecord.traceId = String(vars.traceId);
|
|
156
|
+
}
|
|
157
|
+
if (vars.spanId !== undefined && vars.spanId !== null) {
|
|
158
|
+
logRecord.spanId = String(vars.spanId);
|
|
159
|
+
}
|
|
160
|
+
return logRecord;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Build the full OTLP Logs JSON payload for a batch of records.
|
|
164
|
+
*/
|
|
165
|
+
function toOtlp(batch, resource, scopeName = "@spinajs/log") {
|
|
166
|
+
return {
|
|
167
|
+
resourceLogs: [
|
|
168
|
+
{
|
|
169
|
+
resource: { attributes: kv(resource) },
|
|
170
|
+
scopeLogs: [
|
|
171
|
+
{
|
|
172
|
+
scope: { name: scopeName },
|
|
173
|
+
logRecords: batch.map(toLogRecord),
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// we mark per instance check because we can have multiple otlp targets for
|
|
181
|
+
// different endpoints/logs but we dont want to create the writer twice for the
|
|
182
|
+
// same one.
|
|
183
|
+
let OtlpLogTarget = class OtlpLogTarget extends log_1.LogTarget {
|
|
184
|
+
constructor(options) {
|
|
185
|
+
super(options);
|
|
186
|
+
/**
|
|
187
|
+
* Set true when the queue overflows maxBufferSize, so the drop warning is
|
|
188
|
+
* emitted ONCE per overflow episode and reset when a flush succeeds.
|
|
189
|
+
*/
|
|
190
|
+
this.Overflowed = false;
|
|
191
|
+
// Config-driven targets ( resolved through the logger config ) nest their
|
|
192
|
+
// settings under an `options` sub-object ( like FileTarget ), while direct
|
|
193
|
+
// construction passes them flat. Hoist any nested options onto this.Options
|
|
194
|
+
// at highest precedence so both forms resolve the same flat reads below.
|
|
195
|
+
const nested = (options?.options ?? {});
|
|
196
|
+
this.Options = Object.assign({
|
|
197
|
+
interval: 3000,
|
|
198
|
+
bufferSize: 10,
|
|
199
|
+
maxBufferSize: 1000,
|
|
200
|
+
timeout: 5000,
|
|
201
|
+
scopeName: "@spinajs/log",
|
|
202
|
+
}, this.Options, nested);
|
|
203
|
+
}
|
|
204
|
+
__checkInstance__(creationOptions) {
|
|
205
|
+
return this.Options.name === creationOptions[0].name;
|
|
206
|
+
}
|
|
207
|
+
resolve() {
|
|
208
|
+
this.AxiosInstance = axios_1.default.create({
|
|
209
|
+
baseURL: this.Options.endpoint,
|
|
210
|
+
headers: {
|
|
211
|
+
"Content-Type": "application/json",
|
|
212
|
+
...this.Options.headers,
|
|
213
|
+
},
|
|
214
|
+
timeout: this.Options.timeout,
|
|
215
|
+
});
|
|
216
|
+
// one reusable pipeline: exponential backoff + jitter, 5 attempts, 1s..5s,
|
|
217
|
+
// honoring Retry-After and retrying only transient errors ( network + 429 /
|
|
218
|
+
// 502 / 503 / 504 ). Non-retryable errors fall straight through to .catch.
|
|
219
|
+
this.RetryPipeline = new util_1.ResiliencePipelineBuilder()
|
|
220
|
+
.addRetry({
|
|
221
|
+
MaxRetryAttempts: 5,
|
|
222
|
+
Delay: util_1.TimeSpan.fromSeconds(1),
|
|
223
|
+
MaxDelay: util_1.TimeSpan.fromSeconds(5),
|
|
224
|
+
BackoffType: util_1.BackoffType.Exponential,
|
|
225
|
+
UseJitter: true,
|
|
226
|
+
ShouldHandle: (o) => o.Error !== undefined && isRetryableOtlpError(o.Error),
|
|
227
|
+
DelayGenerator: ({ Outcome }) => {
|
|
228
|
+
const ms = retryAfterMs(Outcome.Error);
|
|
229
|
+
return ms !== undefined ? util_1.TimeSpan.fromMilliseconds(ms) : undefined;
|
|
230
|
+
},
|
|
231
|
+
})
|
|
232
|
+
.build();
|
|
233
|
+
// one BatchQueue owns accumulation, the periodic flush timer and the
|
|
234
|
+
// maxQueue cap ( dropping the oldest ). The actual export happens in send().
|
|
235
|
+
this.Queue = new log_1.BatchQueue({
|
|
236
|
+
maxBatch: this.Options.bufferSize,
|
|
237
|
+
maxQueue: this.Options.maxBufferSize,
|
|
238
|
+
flushIntervalMs: this.Options.interval ?? 3000,
|
|
239
|
+
onFlush: (batch) => this.send(batch),
|
|
240
|
+
onOverflow: (droppedItems) => {
|
|
241
|
+
// emit the drop warning ONCE per overflow episode; reset on the next
|
|
242
|
+
// successful send() so a later episode warns again.
|
|
243
|
+
if (!this.Overflowed) {
|
|
244
|
+
this.Overflowed = true;
|
|
245
|
+
this.Log.warn(`OTLP buffer exceeded ${this.Options.maxBufferSize} records, dropped ${droppedItems.length} oldest records.`);
|
|
246
|
+
}
|
|
247
|
+
// spill each dropped entry to the fallback ( if a wrapper wired one ) so
|
|
248
|
+
// a durable target catches exactly what overflow discarded. Records wrap
|
|
249
|
+
// the entry, so unwrap to the ILogEntry.
|
|
250
|
+
for (const rec of droppedItems) {
|
|
251
|
+
this.OnDropped?.(rec.entry);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
super.resolve();
|
|
256
|
+
}
|
|
257
|
+
write(data) {
|
|
258
|
+
if (!this.Options.enabled) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
// capture the event time now ( wall-clock ms -> OTLP nanosecond string )
|
|
262
|
+
const record = {
|
|
263
|
+
entry: data,
|
|
264
|
+
timeUnixNano: (BigInt(Date.now()) * 1000000n).toString(),
|
|
265
|
+
};
|
|
266
|
+
// fire-and-forget: a size-triggered flush ( when the batch fills ) runs in
|
|
267
|
+
// the background.
|
|
268
|
+
void this.Queue.enqueue(record);
|
|
269
|
+
}
|
|
270
|
+
forceFlush() {
|
|
271
|
+
return this.Queue ? this.Queue.forceFlush() : Promise.resolve();
|
|
272
|
+
}
|
|
273
|
+
async dispose() {
|
|
274
|
+
// stop the flush timer and perform a final best-effort flush.
|
|
275
|
+
await this.Queue.shutdown();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Export a single batch to the OTLP endpoint. Wired as the queue's `onFlush`.
|
|
279
|
+
*
|
|
280
|
+
* MUST resolve ( never reject ) so shutdown()/forceFlush() cannot reject: on a
|
|
281
|
+
* retryable-exhausted failure the batch is requeued at the front, on a
|
|
282
|
+
* non-retryable error it is dropped and surfaced via HasError/Error.
|
|
283
|
+
*/
|
|
284
|
+
async send(batch) {
|
|
285
|
+
const payload = toOtlp(batch, this.Options.resource, this.Options.scopeName);
|
|
286
|
+
// wrap ONLY the push in the resilience pipeline: transient failures are
|
|
287
|
+
// retried inline with backoff; we reach the catch only after retries are
|
|
288
|
+
// exhausted or immediately on a non-retryable error.
|
|
289
|
+
try {
|
|
290
|
+
await this.RetryPipeline.execute(() => this.AxiosInstance.post("/v1/logs", payload).then(() => undefined));
|
|
291
|
+
// successful export -> clear any previous error state
|
|
292
|
+
this.HasError = false;
|
|
293
|
+
this.Error = null;
|
|
294
|
+
// drained successfully - allow the next overflow episode to warn again
|
|
295
|
+
this.Overflowed = false;
|
|
296
|
+
this.Log.trace(`Wrote buffered messages to OTLP target at ${this.Options.endpoint}, ${batch.length} records.`);
|
|
297
|
+
}
|
|
298
|
+
catch (err) {
|
|
299
|
+
// reached only after retries are exhausted ( transient ) or immediately
|
|
300
|
+
// for a non-retryable error.
|
|
301
|
+
this.HasError = true;
|
|
302
|
+
this.Error = err;
|
|
303
|
+
if (isRetryableOtlpError(err)) {
|
|
304
|
+
// retryable but exhausted: put the batch back at the front so the next
|
|
305
|
+
// flush retries it. The queue enforces the maxQueue cap ( dropping the
|
|
306
|
+
// oldest + onOverflow ) so the retry buffer never grows without bound.
|
|
307
|
+
this.Log.error(err, `Cannot export log records to OTLP target - retries exhausted, retaining records for next flush.`);
|
|
308
|
+
this.Queue.requeueFront(batch);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
// permanent error ( eg. 400 malformed, 401 bad auth ): retrying forever
|
|
312
|
+
// would only hide a config problem. Drop the batch and surface it, but
|
|
313
|
+
// first spill each undelivered entry to the fallback ( if a wrapper wired
|
|
314
|
+
// one ) so a durable target catches exactly what was not delivered.
|
|
315
|
+
for (const rec of batch) {
|
|
316
|
+
this.OnDropped?.(rec.entry);
|
|
317
|
+
}
|
|
318
|
+
this.Log.error(err, `Cannot export log records to OTLP target - dropping ${batch.length} records because the error is non-retryable.`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
exports.OtlpLogTarget = OtlpLogTarget;
|
|
323
|
+
__decorate([
|
|
324
|
+
(0, log_1.Logger)("LogOtlpTarget"),
|
|
325
|
+
__metadata("design:type", log_1.Log)
|
|
326
|
+
], OtlpLogTarget.prototype, "Log", void 0);
|
|
327
|
+
exports.OtlpLogTarget = OtlpLogTarget = __decorate([
|
|
328
|
+
(0, di_1.PerInstanceCheck)(),
|
|
329
|
+
(0, di_1.Injectable)("OtlpLogTarget"),
|
|
330
|
+
__metadata("design:paramtypes", [Object])
|
|
331
|
+
], OtlpLogTarget);
|
|
332
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { IInstanceCheck } from "@spinajs/di";
|
|
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 OTLP export 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 isRetryableOtlpError(error: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Read a `Retry-After` header from an OTLP error response and return the delay
|
|
17
|
+
* in milliseconds. Supports both the numeric ( delta-seconds ) and HTTP-date
|
|
18
|
+
* forms. Returns `undefined` when there is no usable header.
|
|
19
|
+
*/
|
|
20
|
+
export declare function retryAfterMs(error: unknown): number | undefined;
|
|
21
|
+
export interface IOtlpTargetOptions extends ICommonTargetOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Base URL of the OTLP/HTTP receiver, eg. `http://localhost:4318`. The target
|
|
24
|
+
* POSTs the encoded logs to `${endpoint}/v1/logs`.
|
|
25
|
+
*/
|
|
26
|
+
endpoint: string;
|
|
27
|
+
/**
|
|
28
|
+
* Extra HTTP headers sent with every export ( eg. auth tokens / API keys ).
|
|
29
|
+
*/
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* Resource attributes describing the emitting service ( eg.
|
|
33
|
+
* `{ "service.name": "my-service" }` ). Emitted on `resourceLogs[].resource`.
|
|
34
|
+
*/
|
|
35
|
+
resource?: Record<string, string | number | boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Instrumentation scope name reported on each `scopeLogs[].scope`. Default
|
|
38
|
+
* `@spinajs/log`.
|
|
39
|
+
*/
|
|
40
|
+
scopeName?: string;
|
|
41
|
+
/** Periodic flush tick in ms. Default 3000. */
|
|
42
|
+
interval: number;
|
|
43
|
+
/** Flush automatically once this many records are buffered ( maxBatch ). Default 10. */
|
|
44
|
+
bufferSize: number;
|
|
45
|
+
/**
|
|
46
|
+
* Hard cap on the number of records retained for retry ( maxQueue ). When a
|
|
47
|
+
* flush fails and retained + newly buffered records exceed this, the oldest
|
|
48
|
+
* are dropped. Default 1000.
|
|
49
|
+
*/
|
|
50
|
+
maxBufferSize: number;
|
|
51
|
+
/** Per-request HTTP timeout in ms. Default 5000. */
|
|
52
|
+
timeout: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Internal buffered record: the log entry plus the wall-clock event time
|
|
56
|
+
* captured at `write()` time, as an OTLP nanosecond string.
|
|
57
|
+
*/
|
|
58
|
+
export interface IOtlpRecord {
|
|
59
|
+
entry: ILogEntry;
|
|
60
|
+
timeUnixNano: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* An OTLP `AnyValue`. Only one field is set at a time.
|
|
64
|
+
*/
|
|
65
|
+
interface IOtlpAnyValue {
|
|
66
|
+
stringValue?: string;
|
|
67
|
+
boolValue?: boolean;
|
|
68
|
+
intValue?: string;
|
|
69
|
+
doubleValue?: number;
|
|
70
|
+
}
|
|
71
|
+
interface IOtlpKeyValue {
|
|
72
|
+
key: string;
|
|
73
|
+
value: IOtlpAnyValue;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Encode a single scalar / structured value as an OTLP `AnyValue`. Never throws:
|
|
77
|
+
* objects / arrays fall back to a JSON string via `safeStringify`.
|
|
78
|
+
*
|
|
79
|
+
* - string -> `{ stringValue }`
|
|
80
|
+
* - boolean -> `{ boolValue }`
|
|
81
|
+
* - integer number -> `{ intValue: String(v) }`
|
|
82
|
+
* - non-integer number -> `{ doubleValue: v }`
|
|
83
|
+
* - anything else ( object / array / bigint / etc. ) -> `{ stringValue: safeStringify(v) }`
|
|
84
|
+
*/
|
|
85
|
+
export declare function anyValue(v: unknown): IOtlpAnyValue;
|
|
86
|
+
/**
|
|
87
|
+
* Encode a plain record as an array of OTLP `KeyValue`s. Missing / empty input
|
|
88
|
+
* yields an empty array.
|
|
89
|
+
*/
|
|
90
|
+
export declare function kv(obj?: Record<string, unknown>): IOtlpKeyValue[];
|
|
91
|
+
/**
|
|
92
|
+
* Map one buffered record to an OTLP `LogRecord`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function toLogRecord(record: IOtlpRecord): Record<string, unknown>;
|
|
95
|
+
/**
|
|
96
|
+
* Build the full OTLP Logs JSON payload for a batch of records.
|
|
97
|
+
*/
|
|
98
|
+
export declare function toOtlp(batch: IOtlpRecord[], resource?: Record<string, string | number | boolean>, scopeName?: string): Record<string, unknown>;
|
|
99
|
+
export declare class OtlpLogTarget extends LogTarget<IOtlpTargetOptions> implements IInstanceCheck {
|
|
100
|
+
protected Log: Log;
|
|
101
|
+
/**
|
|
102
|
+
* The single buffered-batch queue that owns accumulation, the flush timer and
|
|
103
|
+
* the maxQueue cap. The actual OTLP export happens in `send`, wired as its
|
|
104
|
+
* `onFlush`.
|
|
105
|
+
*/
|
|
106
|
+
protected Queue: BatchQueue<IOtlpRecord>;
|
|
107
|
+
/**
|
|
108
|
+
* Set true when the queue overflows maxBufferSize, so the drop warning is
|
|
109
|
+
* emitted ONCE per overflow episode and reset when a flush succeeds.
|
|
110
|
+
*/
|
|
111
|
+
protected Overflowed: boolean;
|
|
112
|
+
protected AxiosInstance: AxiosInstance;
|
|
113
|
+
/**
|
|
114
|
+
* Reusable resilience pipeline guarding the OTLP export with exponential
|
|
115
|
+
* backoff + jitter, honoring `Retry-After` and only retrying transient errors.
|
|
116
|
+
*/
|
|
117
|
+
protected RetryPipeline: ResiliencePipeline<void>;
|
|
118
|
+
constructor(options: IOtlpTargetOptions);
|
|
119
|
+
__checkInstance__(creationOptions: IOtlpTargetOptions[]): boolean;
|
|
120
|
+
resolve(): void;
|
|
121
|
+
write(data: ILogEntry): void;
|
|
122
|
+
forceFlush(): Promise<void>;
|
|
123
|
+
dispose(): Promise<void>;
|
|
124
|
+
/**
|
|
125
|
+
* Export a single batch to the OTLP endpoint. Wired as the queue's `onFlush`.
|
|
126
|
+
*
|
|
127
|
+
* MUST resolve ( never reject ) so shutdown()/forceFlush() cannot reject: on a
|
|
128
|
+
* retryable-exhausted failure the batch is requeued at the front, on a
|
|
129
|
+
* non-retryable error it is dropped and surfaced via HasError/Error.
|
|
130
|
+
*/
|
|
131
|
+
protected send(batch: IOtlpRecord[]): Promise<void>;
|
|
132
|
+
}
|
|
133
|
+
export {};
|
|
134
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|