@lark-apaas/observable 1.0.0-alpha.1

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/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @lark-apaas/observable(服务端观测 SDK)
2
+
3
+ 基于 OpenTelemetry Logs 的服务端观测封装,提供轻量的启动、结构化日志记录与刷新接口。适合在 NestJS 或任意 Node 服务中快速接入统一的日志输出格式。
4
+
5
+ ## 安装与引入
6
+
7
+ ```ts
8
+ import { appSdk, AppOTelLoggerSDK } from '@lark-apaas/observable';
9
+ ```
10
+
11
+ ## 快速开始
12
+
13
+ ```ts
14
+ import { appSdk } from '@lark-apaas/observable';
15
+
16
+ // 应用启动时
17
+ appSdk.start();
18
+
19
+ // 记录日志(会附带基础属性)
20
+ appSdk.log('info', '用户创建成功', { user_id: '123', module: 'user_service' });
21
+
22
+ // 在 FaaS/请求结束场景手动刷新
23
+ await appSdk.flush();
24
+ ```
25
+
26
+ ## 架构与实现
27
+
28
+ - `src/core/sdk.ts`
29
+ - 启动流程:资源探测(`PlatformDetector`)、创建 `CustomExporter`、注册 `BatchLogRecordProcessor`、启动 `NodeSDK`
30
+ - 写日志:通过 OTel `logs.getLogger()` 记录并映射 `severityNumber` 与 `severityText`
31
+ - `src/core/exporter.ts`
32
+ - 自定义导出器 `CustomExporter`:将 OTel `ReadableLogRecord[]` 转换为 OTLP-like 结构,统一字符串化属性,附加 `uuid` 与环境标识,并输出到 stdout(带前后缀)
33
+ - `src/core/resource-detector.ts`
34
+ - 平台资源探测器 `PlatformDetector`:预留从环境变量提取平台级元数据(当前为空占位)
35
+ - `src/utils/*`
36
+ - `convertAttributesToString`:属性安全字符串化
37
+ - `generateUUID`:生成 TraceId/SpanId(符合 W3C Trace Context 长度)
38
+ - `hrTimeToNanoNumber`:`HrTime` → 纳秒 Number(注意超大值的精度问题)
39
+ - `src/type.ts`
40
+ - 定义日志结构类型(`LogRecord`、`LogsData`、`Resource`)与 `LogAttributes` 等
41
+
42
+ ## API 详解
43
+
44
+ - `appSdk.start()`
45
+ - 初始化资源、日志处理器与 NodeSDK。建议在应用启动早期调用一次。
46
+ - `appSdk.log(level, message, extra?)`
47
+ - 记录一条日志;`level` 支持 `trace|debug|info|warn|error|fatal`
48
+ - 自动合并基础属性(如 `pid`),`extra` 用于业务自定义属性
49
+ - `appSdk.flush()`
50
+ - 强制刷新批处理器,确保日志尽快落地
51
+
52
+ ## 日志输出格式(示例)
53
+
54
+ 每条日志以一行 JSON 输出并带前后缀,便于采集侧按行解析:
55
+
56
+ ```
57
+ force-log-prefix{"resource":{"attributes":{}},"logRecords":[{
58
+ "timeUnixNano":173...,"observedTimeUnixNano":173...,
59
+ "severityNumber":9,"severityText":"INFO",
60
+ "body":"hello observable",
61
+ "attributes":{
62
+ "uuid":"...",
63
+ "app_env":"Dev",
64
+ "level":"INFO",
65
+ "module":"bootstrap"
66
+ },
67
+ "traceId":"...","spanId":"..."
68
+ }]}force-log-suffix
69
+ ```
70
+
71
+ ## 注意事项
72
+
73
+ - `start()` 仅需调用一次;重复调用会造成资源/处理器重复注册
74
+ - 导出器当前使用 stdout 输出,生产环境可替换为文件/网络传输实现
75
+ - Number 精度限制:纳秒时间可能超出 `2^53`,如需绝对精度可改为字符串或 `BigInt` 序列化
76
+ - 资源属性与请求上下文:当前示例未直接从请求上下文读取;可在上层中间件设置属性或扩展导出器实现
package/dist/index.cjs ADDED
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ AppOTelLoggerSDK: () => AppOTelLoggerSDK,
25
+ appSdk: () => appSdk
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+
29
+ // src/core/sdk.ts
30
+ var import_sdk_node = require("@opentelemetry/sdk-node");
31
+ var import_sdk_logs = require("@opentelemetry/sdk-logs");
32
+ var import_resources = require("@opentelemetry/resources");
33
+ var import_api_logs = require("@opentelemetry/api-logs");
34
+
35
+ // src/core/resource-detector.ts
36
+ var PlatformDetector = class {
37
+ static {
38
+ __name(this, "PlatformDetector");
39
+ }
40
+ /**
41
+ * 实现 detect 接口
42
+ * @param config 探测配置 (可选)
43
+ */
44
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
45
+ detect(config) {
46
+ const attributes = {};
47
+ const cleanAttributes = Object.entries(attributes).reduce((acc, [key, value]) => {
48
+ if (value !== void 0 && value !== null && value !== "") {
49
+ acc[key] = value;
50
+ }
51
+ return acc;
52
+ }, {});
53
+ return {
54
+ attributes: cleanAttributes
55
+ };
56
+ }
57
+ };
58
+ var lowCodeDetector = new PlatformDetector();
59
+
60
+ // src/core/exporter.ts
61
+ var import_core = require("@opentelemetry/core");
62
+ var import_common = require("@nestjs/common");
63
+
64
+ // src/utils/generateUUID.ts
65
+ var import_crypto = require("crypto");
66
+ var idGenerator = {
67
+ /**
68
+ * 生成 TraceId (128-bit / 32-char hex)
69
+ * 符合 W3C Trace Context 标准
70
+ * 示例: "5b8efff798038103d269b633813fc60c"
71
+ */
72
+ generateTraceId() {
73
+ return (0, import_crypto.randomBytes)(16).toString("hex");
74
+ },
75
+ /**
76
+ * 生成 SpanId (64-bit / 16-char hex)
77
+ * 示例: "eee19b7ec3c1b174"
78
+ */
79
+ generateSpanId() {
80
+ return (0, import_crypto.randomBytes)(8).toString("hex");
81
+ }
82
+ };
83
+
84
+ // src/utils/hrTimeToNanoNumber.ts
85
+ function hrTimeToNanosNumber(hrTime) {
86
+ const seconds = BigInt(hrTime[0]);
87
+ const nanos = BigInt(hrTime[1]);
88
+ const totalNanos = seconds * 1000000000n + nanos;
89
+ return Number(totalNanos);
90
+ }
91
+ __name(hrTimeToNanosNumber, "hrTimeToNanosNumber");
92
+
93
+ // src/const.ts
94
+ var AppEnv = /* @__PURE__ */ (function(AppEnv2) {
95
+ AppEnv2["Dev"] = "preview";
96
+ AppEnv2["Prod"] = "runtime";
97
+ return AppEnv2;
98
+ })({});
99
+
100
+ // src/utils/convertAllAttrIntoString.ts
101
+ var safeStringify = /* @__PURE__ */ __name((obj) => {
102
+ try {
103
+ return JSON.stringify(obj);
104
+ } catch (error) {
105
+ return "";
106
+ }
107
+ }, "safeStringify");
108
+ function convertAttributesToString(attributes) {
109
+ const result = {};
110
+ for (const [key, value] of Object.entries(attributes)) {
111
+ result[key] = value !== void 0 && value !== null ? typeof value === "object" ? safeStringify(value) : String(value) : "";
112
+ }
113
+ return result;
114
+ }
115
+ __name(convertAttributesToString, "convertAttributesToString");
116
+
117
+ // src/core/exporter.ts
118
+ function _ts_decorate(decorators, target, key, desc) {
119
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
120
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
121
+ 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;
122
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
123
+ }
124
+ __name(_ts_decorate, "_ts_decorate");
125
+ function _ts_metadata(k, v) {
126
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
127
+ }
128
+ __name(_ts_metadata, "_ts_metadata");
129
+ var CustomExporter = class {
130
+ static {
131
+ __name(this, "CustomExporter");
132
+ }
133
+ logPrefix = "force-log-prefix";
134
+ logSuffix = "force-log-suffix";
135
+ constructor() {
136
+ }
137
+ export(logs2, resultCallback) {
138
+ const isDev = process.env.NODE_ENV === "development";
139
+ const defaultAttributes = {
140
+ uuid: idGenerator.generateTraceId(),
141
+ app_env: isDev ? AppEnv.Dev : AppEnv.Prod
142
+ };
143
+ const otlpLikeStructure = {
144
+ resource: {
145
+ // 合并 OTel Resource(此处暂留空,真实场景可由 sdk.resource 提供)
146
+ attributes: {}
147
+ },
148
+ logRecords: logs2.map((log) => {
149
+ const rawAttributes = {
150
+ ...defaultAttributes,
151
+ ...log.attributes ?? {}
152
+ };
153
+ const attributes = convertAttributesToString(rawAttributes);
154
+ return {
155
+ timeUnixNano: hrTimeToNanosNumber(log.hrTime),
156
+ observedTimeUnixNano: hrTimeToNanosNumber(log.hrTimeObserved),
157
+ severityNumber: log.severityNumber,
158
+ severityText: log.severityText,
159
+ body: log.body,
160
+ attributes,
161
+ traceId: log.spanContext?.traceId,
162
+ spanId: log.spanContext?.spanId
163
+ };
164
+ })
165
+ };
166
+ console.log(this.logPrefix + JSON.stringify(otlpLikeStructure) + this.logSuffix);
167
+ resultCallback({
168
+ code: import_core.ExportResultCode.SUCCESS
169
+ });
170
+ }
171
+ async shutdown() {
172
+ }
173
+ };
174
+ CustomExporter = _ts_decorate([
175
+ (0, import_common.Injectable)(),
176
+ _ts_metadata("design:type", Function),
177
+ _ts_metadata("design:paramtypes", [])
178
+ ], CustomExporter);
179
+
180
+ // src/core/sdk.ts
181
+ var AppOTelLoggerSDK = class {
182
+ static {
183
+ __name(this, "AppOTelLoggerSDK");
184
+ }
185
+ sdk = null;
186
+ logProcessor = null;
187
+ start() {
188
+ const resource = (0, import_resources.detectResources)({
189
+ detectors: [
190
+ new PlatformDetector()
191
+ ]
192
+ });
193
+ const logExporter = new CustomExporter();
194
+ this.logProcessor = new import_sdk_logs.BatchLogRecordProcessor(logExporter, {
195
+ scheduledDelayMillis: 2e3,
196
+ maxExportBatchSize: 10
197
+ });
198
+ this.sdk = new import_sdk_node.NodeSDK({
199
+ resource,
200
+ logRecordProcessor: this.logProcessor,
201
+ traceExporter: void 0
202
+ });
203
+ this.sdk.start();
204
+ }
205
+ /**
206
+ * 用户使用的日志接口
207
+ * 自动合并请求级上下文(由 ContextProvider 提供)
208
+ */
209
+ log(level, message, extra = {}) {
210
+ const logger = import_api_logs.logs.getLogger("app-logger");
211
+ const severityNumber = this.mapSeverity(level);
212
+ const severityText = this.mapSeverityText(level);
213
+ const mergedAttributes = {
214
+ ...extra || {},
215
+ pid: process.pid
216
+ };
217
+ logger.emit({
218
+ body: message,
219
+ severityNumber,
220
+ severityText,
221
+ attributes: mergedAttributes,
222
+ timestamp: /* @__PURE__ */ new Date()
223
+ });
224
+ }
225
+ async flush() {
226
+ if (this.logProcessor) {
227
+ await this.logProcessor.forceFlush();
228
+ }
229
+ }
230
+ mapSeverity(level) {
231
+ switch (level.toLowerCase()) {
232
+ case "trace":
233
+ return import_api_logs.SeverityNumber.TRACE;
234
+ case "debug":
235
+ return import_api_logs.SeverityNumber.DEBUG;
236
+ case "info":
237
+ return import_api_logs.SeverityNumber.INFO;
238
+ case "warn":
239
+ return import_api_logs.SeverityNumber.WARN;
240
+ case "error":
241
+ return import_api_logs.SeverityNumber.ERROR;
242
+ case "fatal":
243
+ return import_api_logs.SeverityNumber.FATAL;
244
+ default:
245
+ return import_api_logs.SeverityNumber.INFO;
246
+ }
247
+ }
248
+ mapSeverityText(level) {
249
+ switch (level.toLowerCase()) {
250
+ case "trace":
251
+ return "INFO";
252
+ case "debug":
253
+ return "DEBUG";
254
+ case "info":
255
+ return "INFO";
256
+ case "warn":
257
+ return "WARN";
258
+ case "error":
259
+ return "ERROR";
260
+ case "fatal":
261
+ return "ERROR";
262
+ default:
263
+ return "INFO";
264
+ }
265
+ }
266
+ };
267
+ var appSdk = new AppOTelLoggerSDK();
268
+ // Annotate the CommonJS export names for ESM import in node:
269
+ 0 && (module.exports = {
270
+ AppOTelLoggerSDK,
271
+ appSdk
272
+ });
273
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/sdk.ts","../src/core/resource-detector.ts","../src/core/exporter.ts","../src/utils/generateUUID.ts","../src/utils/hrTimeToNanoNumber.ts","../src/const.ts","../src/utils/convertAllAttrIntoString.ts"],"sourcesContent":["export { AppOTelLoggerSDK, appSdk } from './core/sdk';\nexport type { ContextProvider } from './type';\n","import { NodeSDK } from '@opentelemetry/sdk-node';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { detectResources } from '@opentelemetry/resources';\nimport { logs, SeverityNumber } from '@opentelemetry/api-logs';\nimport { PlatformDetector } from './resource-detector';\nimport type { LogAttributes } from '../type';\nimport { CustomExporter } from './exporter';\n\nexport class AppOTelLoggerSDK {\n private sdk: NodeSDK | null = null;\n private logProcessor: BatchLogRecordProcessor | null = null;\n\n\n start() {\n const resource = detectResources({\n detectors: [new PlatformDetector()],\n });\n\n // 将获取上下文的函数传入 Exporter,保持 SDK 对框架无感\n const logExporter = new CustomExporter();\n \n this.logProcessor = new BatchLogRecordProcessor(logExporter, {\n scheduledDelayMillis: 2000,\n maxExportBatchSize: 10,\n });\n\n this.sdk = new NodeSDK({\n resource,\n logRecordProcessor: this.logProcessor,\n traceExporter: undefined,\n });\n\n this.sdk.start();\n }\n\n /**\n * 用户使用的日志接口\n * 自动合并请求级上下文(由 ContextProvider 提供)\n */\n public log(level: string, message: string, extra: Record<string, unknown> = {}) {\n const logger = logs.getLogger('app-logger');\n const severityNumber = this.mapSeverity(level);\n const severityText = this.mapSeverityText(level);\n\n const mergedAttributes: LogAttributes = {\n ...(extra || {}),\n pid: process.pid,\n };\n\n logger.emit({\n body: message,\n severityNumber,\n severityText,\n attributes: mergedAttributes,\n timestamp: new Date(),\n });\n }\n\n async flush() {\n if (this.logProcessor) {\n await this.logProcessor.forceFlush();\n }\n }\n\n private mapSeverity(level: string): SeverityNumber {\n switch (level.toLowerCase()) {\n case 'trace': return SeverityNumber.TRACE;\n case 'debug': return SeverityNumber.DEBUG;\n case 'info': return SeverityNumber.INFO;\n case 'warn': return SeverityNumber.WARN;\n case 'error': return SeverityNumber.ERROR;\n case 'fatal': return SeverityNumber.FATAL;\n default: return SeverityNumber.INFO;\n }\n }\n\n private mapSeverityText(level: string): string {\n switch (level.toLowerCase()) {\n case 'trace': return 'INFO';\n case 'debug': return 'DEBUG';\n case 'info': return 'INFO';\n case 'warn': return 'WARN';\n case 'error': return 'ERROR';\n case 'fatal': return 'ERROR';\n default: return 'INFO';\n }\n }\n}\n\nexport const appSdk = new AppOTelLoggerSDK();\nexport type { LogAttributes };\n","import { ResourceDetector, ResourceDetectionConfig, DetectedResource } from '@opentelemetry/resources';\n\n/**\n * 业务专属资源探测器\n * 负责从环境变量中提取低代码平台特有的元数据 (Tenant, App, Env)\n */\nexport class PlatformDetector implements ResourceDetector {\n \n /**\n * 实现 detect 接口\n * @param config 探测配置 (可选)\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\n detect(config?: ResourceDetectionConfig): DetectedResource {\n // 1. 从环境变量读取平台级资源字段\n const attributes: Record<string, string | number | boolean> = {};\n\n // 2. 清洗数据:移除 undefined/null/空字符串\n const cleanAttributes = Object.entries(attributes).reduce((acc, [key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n acc[key] = value;\n }\n return acc;\n }, {} as Record<string, string | number | boolean>);\n\n // 3. 返回 Resource 对象\n return {\n attributes: cleanAttributes,\n }\n }\n}\n\nexport const lowCodeDetector = new PlatformDetector();","import { LogRecordExporter, ReadableLogRecord, } from '@opentelemetry/sdk-logs';\nimport { ExportResult, ExportResultCode } from '@opentelemetry/core';\nimport { Attributes } from '@opentelemetry/api';\nimport { Injectable } from '@nestjs/common';\n\nimport { idGenerator } from '../utils/generateUUID';\nimport { hrTimeToNanosNumber } from '../utils/hrTimeToNanoNumber';\nimport { AppEnv } from '../const';\nimport { convertAttributesToString } from '../utils/convertAllAttrIntoString';\n\n@Injectable()\nexport class CustomExporter implements LogRecordExporter {\n private logPrefix: string = \"force-log-prefix\"\n private logSuffix: string = \"force-log-suffix\"\n constructor() {}\n\n export(logs: ReadableLogRecord[], resultCallback: (result: ExportResult) => void) {\n const isDev = process.env.NODE_ENV === \"development\";\n const defaultAttributes: Attributes = {\n uuid: idGenerator.generateTraceId(),\n app_env: isDev ? AppEnv.Dev : AppEnv.Prod,\n };\n\n // 模拟 OTel 的结构化过程\n const otlpLikeStructure = {\n resource: {\n // 合并 OTel Resource(此处暂留空,真实场景可由 sdk.resource 提供)\n attributes: {}\n },\n logRecords: logs.map(log => {\n const rawAttributes = {\n ...defaultAttributes,\n ...(log.attributes ?? {}),\n }\n\n // 将所有属性转换为字符串\n const attributes = convertAttributesToString(rawAttributes);\n\n return {\n timeUnixNano: hrTimeToNanosNumber(log.hrTime),\n observedTimeUnixNano: hrTimeToNanosNumber(log.hrTimeObserved),\n severityNumber: log.severityNumber,\n severityText: log.severityText,\n body: log.body,\n attributes: attributes,\n traceId: log.spanContext?.traceId,\n spanId: log.spanContext?.spanId\n }\n })\n }\n\n console.log(this.logPrefix + JSON.stringify(otlpLikeStructure) + this.logSuffix);\n resultCallback({ code: ExportResultCode.SUCCESS });\n }\n\n async shutdown() { }\n}","import { randomBytes } from 'crypto';\n\nexport const idGenerator = {\n /**\n * 生成 TraceId (128-bit / 32-char hex)\n * 符合 W3C Trace Context 标准\n * 示例: \"5b8efff798038103d269b633813fc60c\"\n */\n generateTraceId(): string {\n // 16 字节 = 128 位,转为 hex 就是 32 个字符\n return randomBytes(16).toString('hex');\n },\n\n /**\n * 生成 SpanId (64-bit / 16-char hex)\n * 示例: \"eee19b7ec3c1b174\"\n */\n generateSpanId(): string {\n // 8 字节 = 64 位,转为 hex 就是 16 个字符\n return randomBytes(8).toString('hex');\n }\n};","import type { HrTime } from '@opentelemetry/api';\n/**\n * 将 HrTime 转换为纳秒数字\n * 注意:JavaScript Number 类型只能精确表示整数到 2^53,对于超过此范围的纳秒值会丢失精度\n*/\nexport function hrTimeToNanosNumber(hrTime: HrTime): number {\n // 将秒转换为 BigInt\n const seconds = BigInt(hrTime[0]);\n // 将纳秒转换为 BigInt\n const nanos = BigInt(hrTime[1]);\n \n // 计算总纳秒数: seconds * 10^9 + nanos\n // 注意:数字后面加 'n' 表示 BigInt 字面量,或者使用 BigInt() 构造函数\n const totalNanos = (seconds * 1_000_000_000n) + nanos;\n \n // 将 BigInt 转换为 Number\n return Number(totalNanos);\n}","export enum AppEnv {\n Dev = \"preview\",\n Prod = \"runtime\",\n}\n","\nconst safeStringify = (obj: unknown) => {\n try {\n return JSON.stringify(obj);\n } catch (error) {\n return '';\n }\n};\n// 将对象的所有属性值转换为字符串\nexport function convertAttributesToString(attributes: Record<string, unknown>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(attributes)) {\n result[key] = value !== undefined && value !== null ? typeof value === 'object' ? safeStringify(value) : String(value) : '';\n }\n return result;\n}"],"mappings":"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","names":["PlatformDetector","detect","config","attributes","cleanAttributes","Object","entries","reduce","acc","key","value","undefined","lowCodeDetector","idGenerator","generateTraceId","randomBytes","toString","generateSpanId","hrTimeToNanosNumber","hrTime","seconds","BigInt","nanos","totalNanos","Number","AppEnv","safeStringify","obj","JSON","stringify","error","convertAttributesToString","attributes","result","key","value","Object","entries","undefined","String","CustomExporter","logPrefix","logSuffix","export","logs","resultCallback","isDev","process","env","NODE_ENV","defaultAttributes","uuid","idGenerator","generateTraceId","app_env","AppEnv","Dev","Prod","otlpLikeStructure","resource","attributes","logRecords","map","log","rawAttributes","convertAttributesToString","timeUnixNano","hrTimeToNanosNumber","hrTime","observedTimeUnixNano","hrTimeObserved","severityNumber","severityText","body","traceId","spanContext","spanId","console","JSON","stringify","code","ExportResultCode","SUCCESS","shutdown","AppOTelLoggerSDK","sdk","logProcessor","start","resource","detectResources","detectors","PlatformDetector","logExporter","CustomExporter","BatchLogRecordProcessor","scheduledDelayMillis","maxExportBatchSize","NodeSDK","logRecordProcessor","traceExporter","undefined","log","level","message","extra","logger","logs","getLogger","severityNumber","mapSeverity","severityText","mapSeverityText","mergedAttributes","pid","process","emit","body","attributes","timestamp","Date","flush","forceFlush","toLowerCase","SeverityNumber","TRACE","DEBUG","INFO","WARN","ERROR","FATAL","appSdk"]}
@@ -0,0 +1,20 @@
1
+ interface ContextProvider {
2
+ getContext(): Record<string, unknown> | undefined;
3
+ }
4
+
5
+ declare class AppOTelLoggerSDK {
6
+ private sdk;
7
+ private logProcessor;
8
+ start(): void;
9
+ /**
10
+ * 用户使用的日志接口
11
+ * 自动合并请求级上下文(由 ContextProvider 提供)
12
+ */
13
+ log(level: string, message: string, extra?: Record<string, unknown>): void;
14
+ flush(): Promise<void>;
15
+ private mapSeverity;
16
+ private mapSeverityText;
17
+ }
18
+ declare const appSdk: AppOTelLoggerSDK;
19
+
20
+ export { AppOTelLoggerSDK, type ContextProvider, appSdk };
@@ -0,0 +1,20 @@
1
+ interface ContextProvider {
2
+ getContext(): Record<string, unknown> | undefined;
3
+ }
4
+
5
+ declare class AppOTelLoggerSDK {
6
+ private sdk;
7
+ private logProcessor;
8
+ start(): void;
9
+ /**
10
+ * 用户使用的日志接口
11
+ * 自动合并请求级上下文(由 ContextProvider 提供)
12
+ */
13
+ log(level: string, message: string, extra?: Record<string, unknown>): void;
14
+ flush(): Promise<void>;
15
+ private mapSeverity;
16
+ private mapSeverityText;
17
+ }
18
+ declare const appSdk: AppOTelLoggerSDK;
19
+
20
+ export { AppOTelLoggerSDK, type ContextProvider, appSdk };
package/dist/index.js ADDED
@@ -0,0 +1,247 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/core/sdk.ts
5
+ import { NodeSDK } from "@opentelemetry/sdk-node";
6
+ import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
7
+ import { detectResources } from "@opentelemetry/resources";
8
+ import { logs, SeverityNumber } from "@opentelemetry/api-logs";
9
+
10
+ // src/core/resource-detector.ts
11
+ var PlatformDetector = class {
12
+ static {
13
+ __name(this, "PlatformDetector");
14
+ }
15
+ /**
16
+ * 实现 detect 接口
17
+ * @param config 探测配置 (可选)
18
+ */
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ detect(config) {
21
+ const attributes = {};
22
+ const cleanAttributes = Object.entries(attributes).reduce((acc, [key, value]) => {
23
+ if (value !== void 0 && value !== null && value !== "") {
24
+ acc[key] = value;
25
+ }
26
+ return acc;
27
+ }, {});
28
+ return {
29
+ attributes: cleanAttributes
30
+ };
31
+ }
32
+ };
33
+ var lowCodeDetector = new PlatformDetector();
34
+
35
+ // src/core/exporter.ts
36
+ import { ExportResultCode } from "@opentelemetry/core";
37
+ import { Injectable } from "@nestjs/common";
38
+
39
+ // src/utils/generateUUID.ts
40
+ import { randomBytes } from "crypto";
41
+ var idGenerator = {
42
+ /**
43
+ * 生成 TraceId (128-bit / 32-char hex)
44
+ * 符合 W3C Trace Context 标准
45
+ * 示例: "5b8efff798038103d269b633813fc60c"
46
+ */
47
+ generateTraceId() {
48
+ return randomBytes(16).toString("hex");
49
+ },
50
+ /**
51
+ * 生成 SpanId (64-bit / 16-char hex)
52
+ * 示例: "eee19b7ec3c1b174"
53
+ */
54
+ generateSpanId() {
55
+ return randomBytes(8).toString("hex");
56
+ }
57
+ };
58
+
59
+ // src/utils/hrTimeToNanoNumber.ts
60
+ function hrTimeToNanosNumber(hrTime) {
61
+ const seconds = BigInt(hrTime[0]);
62
+ const nanos = BigInt(hrTime[1]);
63
+ const totalNanos = seconds * 1000000000n + nanos;
64
+ return Number(totalNanos);
65
+ }
66
+ __name(hrTimeToNanosNumber, "hrTimeToNanosNumber");
67
+
68
+ // src/const.ts
69
+ var AppEnv = /* @__PURE__ */ (function(AppEnv2) {
70
+ AppEnv2["Dev"] = "preview";
71
+ AppEnv2["Prod"] = "runtime";
72
+ return AppEnv2;
73
+ })({});
74
+
75
+ // src/utils/convertAllAttrIntoString.ts
76
+ var safeStringify = /* @__PURE__ */ __name((obj) => {
77
+ try {
78
+ return JSON.stringify(obj);
79
+ } catch (error) {
80
+ return "";
81
+ }
82
+ }, "safeStringify");
83
+ function convertAttributesToString(attributes) {
84
+ const result = {};
85
+ for (const [key, value] of Object.entries(attributes)) {
86
+ result[key] = value !== void 0 && value !== null ? typeof value === "object" ? safeStringify(value) : String(value) : "";
87
+ }
88
+ return result;
89
+ }
90
+ __name(convertAttributesToString, "convertAttributesToString");
91
+
92
+ // src/core/exporter.ts
93
+ function _ts_decorate(decorators, target, key, desc) {
94
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
95
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
96
+ 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;
97
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
98
+ }
99
+ __name(_ts_decorate, "_ts_decorate");
100
+ function _ts_metadata(k, v) {
101
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
102
+ }
103
+ __name(_ts_metadata, "_ts_metadata");
104
+ var CustomExporter = class {
105
+ static {
106
+ __name(this, "CustomExporter");
107
+ }
108
+ logPrefix = "force-log-prefix";
109
+ logSuffix = "force-log-suffix";
110
+ constructor() {
111
+ }
112
+ export(logs2, resultCallback) {
113
+ const isDev = process.env.NODE_ENV === "development";
114
+ const defaultAttributes = {
115
+ uuid: idGenerator.generateTraceId(),
116
+ app_env: isDev ? AppEnv.Dev : AppEnv.Prod
117
+ };
118
+ const otlpLikeStructure = {
119
+ resource: {
120
+ // 合并 OTel Resource(此处暂留空,真实场景可由 sdk.resource 提供)
121
+ attributes: {}
122
+ },
123
+ logRecords: logs2.map((log) => {
124
+ const rawAttributes = {
125
+ ...defaultAttributes,
126
+ ...log.attributes ?? {}
127
+ };
128
+ const attributes = convertAttributesToString(rawAttributes);
129
+ return {
130
+ timeUnixNano: hrTimeToNanosNumber(log.hrTime),
131
+ observedTimeUnixNano: hrTimeToNanosNumber(log.hrTimeObserved),
132
+ severityNumber: log.severityNumber,
133
+ severityText: log.severityText,
134
+ body: log.body,
135
+ attributes,
136
+ traceId: log.spanContext?.traceId,
137
+ spanId: log.spanContext?.spanId
138
+ };
139
+ })
140
+ };
141
+ console.log(this.logPrefix + JSON.stringify(otlpLikeStructure) + this.logSuffix);
142
+ resultCallback({
143
+ code: ExportResultCode.SUCCESS
144
+ });
145
+ }
146
+ async shutdown() {
147
+ }
148
+ };
149
+ CustomExporter = _ts_decorate([
150
+ Injectable(),
151
+ _ts_metadata("design:type", Function),
152
+ _ts_metadata("design:paramtypes", [])
153
+ ], CustomExporter);
154
+
155
+ // src/core/sdk.ts
156
+ var AppOTelLoggerSDK = class {
157
+ static {
158
+ __name(this, "AppOTelLoggerSDK");
159
+ }
160
+ sdk = null;
161
+ logProcessor = null;
162
+ start() {
163
+ const resource = detectResources({
164
+ detectors: [
165
+ new PlatformDetector()
166
+ ]
167
+ });
168
+ const logExporter = new CustomExporter();
169
+ this.logProcessor = new BatchLogRecordProcessor(logExporter, {
170
+ scheduledDelayMillis: 2e3,
171
+ maxExportBatchSize: 10
172
+ });
173
+ this.sdk = new NodeSDK({
174
+ resource,
175
+ logRecordProcessor: this.logProcessor,
176
+ traceExporter: void 0
177
+ });
178
+ this.sdk.start();
179
+ }
180
+ /**
181
+ * 用户使用的日志接口
182
+ * 自动合并请求级上下文(由 ContextProvider 提供)
183
+ */
184
+ log(level, message, extra = {}) {
185
+ const logger = logs.getLogger("app-logger");
186
+ const severityNumber = this.mapSeverity(level);
187
+ const severityText = this.mapSeverityText(level);
188
+ const mergedAttributes = {
189
+ ...extra || {},
190
+ pid: process.pid
191
+ };
192
+ logger.emit({
193
+ body: message,
194
+ severityNumber,
195
+ severityText,
196
+ attributes: mergedAttributes,
197
+ timestamp: /* @__PURE__ */ new Date()
198
+ });
199
+ }
200
+ async flush() {
201
+ if (this.logProcessor) {
202
+ await this.logProcessor.forceFlush();
203
+ }
204
+ }
205
+ mapSeverity(level) {
206
+ switch (level.toLowerCase()) {
207
+ case "trace":
208
+ return SeverityNumber.TRACE;
209
+ case "debug":
210
+ return SeverityNumber.DEBUG;
211
+ case "info":
212
+ return SeverityNumber.INFO;
213
+ case "warn":
214
+ return SeverityNumber.WARN;
215
+ case "error":
216
+ return SeverityNumber.ERROR;
217
+ case "fatal":
218
+ return SeverityNumber.FATAL;
219
+ default:
220
+ return SeverityNumber.INFO;
221
+ }
222
+ }
223
+ mapSeverityText(level) {
224
+ switch (level.toLowerCase()) {
225
+ case "trace":
226
+ return "INFO";
227
+ case "debug":
228
+ return "DEBUG";
229
+ case "info":
230
+ return "INFO";
231
+ case "warn":
232
+ return "WARN";
233
+ case "error":
234
+ return "ERROR";
235
+ case "fatal":
236
+ return "ERROR";
237
+ default:
238
+ return "INFO";
239
+ }
240
+ }
241
+ };
242
+ var appSdk = new AppOTelLoggerSDK();
243
+ export {
244
+ AppOTelLoggerSDK,
245
+ appSdk
246
+ };
247
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/sdk.ts","../src/core/resource-detector.ts","../src/core/exporter.ts","../src/utils/generateUUID.ts","../src/utils/hrTimeToNanoNumber.ts","../src/const.ts","../src/utils/convertAllAttrIntoString.ts"],"sourcesContent":["import { NodeSDK } from '@opentelemetry/sdk-node';\nimport { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';\nimport { detectResources } from '@opentelemetry/resources';\nimport { logs, SeverityNumber } from '@opentelemetry/api-logs';\nimport { PlatformDetector } from './resource-detector';\nimport type { LogAttributes } from '../type';\nimport { CustomExporter } from './exporter';\n\nexport class AppOTelLoggerSDK {\n private sdk: NodeSDK | null = null;\n private logProcessor: BatchLogRecordProcessor | null = null;\n\n\n start() {\n const resource = detectResources({\n detectors: [new PlatformDetector()],\n });\n\n // 将获取上下文的函数传入 Exporter,保持 SDK 对框架无感\n const logExporter = new CustomExporter();\n \n this.logProcessor = new BatchLogRecordProcessor(logExporter, {\n scheduledDelayMillis: 2000,\n maxExportBatchSize: 10,\n });\n\n this.sdk = new NodeSDK({\n resource,\n logRecordProcessor: this.logProcessor,\n traceExporter: undefined,\n });\n\n this.sdk.start();\n }\n\n /**\n * 用户使用的日志接口\n * 自动合并请求级上下文(由 ContextProvider 提供)\n */\n public log(level: string, message: string, extra: Record<string, unknown> = {}) {\n const logger = logs.getLogger('app-logger');\n const severityNumber = this.mapSeverity(level);\n const severityText = this.mapSeverityText(level);\n\n const mergedAttributes: LogAttributes = {\n ...(extra || {}),\n pid: process.pid,\n };\n\n logger.emit({\n body: message,\n severityNumber,\n severityText,\n attributes: mergedAttributes,\n timestamp: new Date(),\n });\n }\n\n async flush() {\n if (this.logProcessor) {\n await this.logProcessor.forceFlush();\n }\n }\n\n private mapSeverity(level: string): SeverityNumber {\n switch (level.toLowerCase()) {\n case 'trace': return SeverityNumber.TRACE;\n case 'debug': return SeverityNumber.DEBUG;\n case 'info': return SeverityNumber.INFO;\n case 'warn': return SeverityNumber.WARN;\n case 'error': return SeverityNumber.ERROR;\n case 'fatal': return SeverityNumber.FATAL;\n default: return SeverityNumber.INFO;\n }\n }\n\n private mapSeverityText(level: string): string {\n switch (level.toLowerCase()) {\n case 'trace': return 'INFO';\n case 'debug': return 'DEBUG';\n case 'info': return 'INFO';\n case 'warn': return 'WARN';\n case 'error': return 'ERROR';\n case 'fatal': return 'ERROR';\n default: return 'INFO';\n }\n }\n}\n\nexport const appSdk = new AppOTelLoggerSDK();\nexport type { LogAttributes };\n","import { ResourceDetector, ResourceDetectionConfig, DetectedResource } from '@opentelemetry/resources';\n\n/**\n * 业务专属资源探测器\n * 负责从环境变量中提取低代码平台特有的元数据 (Tenant, App, Env)\n */\nexport class PlatformDetector implements ResourceDetector {\n \n /**\n * 实现 detect 接口\n * @param config 探测配置 (可选)\n */\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\n detect(config?: ResourceDetectionConfig): DetectedResource {\n // 1. 从环境变量读取平台级资源字段\n const attributes: Record<string, string | number | boolean> = {};\n\n // 2. 清洗数据:移除 undefined/null/空字符串\n const cleanAttributes = Object.entries(attributes).reduce((acc, [key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n acc[key] = value;\n }\n return acc;\n }, {} as Record<string, string | number | boolean>);\n\n // 3. 返回 Resource 对象\n return {\n attributes: cleanAttributes,\n }\n }\n}\n\nexport const lowCodeDetector = new PlatformDetector();","import { LogRecordExporter, ReadableLogRecord, } from '@opentelemetry/sdk-logs';\nimport { ExportResult, ExportResultCode } from '@opentelemetry/core';\nimport { Attributes } from '@opentelemetry/api';\nimport { Injectable } from '@nestjs/common';\n\nimport { idGenerator } from '../utils/generateUUID';\nimport { hrTimeToNanosNumber } from '../utils/hrTimeToNanoNumber';\nimport { AppEnv } from '../const';\nimport { convertAttributesToString } from '../utils/convertAllAttrIntoString';\n\n@Injectable()\nexport class CustomExporter implements LogRecordExporter {\n private logPrefix: string = \"force-log-prefix\"\n private logSuffix: string = \"force-log-suffix\"\n constructor() {}\n\n export(logs: ReadableLogRecord[], resultCallback: (result: ExportResult) => void) {\n const isDev = process.env.NODE_ENV === \"development\";\n const defaultAttributes: Attributes = {\n uuid: idGenerator.generateTraceId(),\n app_env: isDev ? AppEnv.Dev : AppEnv.Prod,\n };\n\n // 模拟 OTel 的结构化过程\n const otlpLikeStructure = {\n resource: {\n // 合并 OTel Resource(此处暂留空,真实场景可由 sdk.resource 提供)\n attributes: {}\n },\n logRecords: logs.map(log => {\n const rawAttributes = {\n ...defaultAttributes,\n ...(log.attributes ?? {}),\n }\n\n // 将所有属性转换为字符串\n const attributes = convertAttributesToString(rawAttributes);\n\n return {\n timeUnixNano: hrTimeToNanosNumber(log.hrTime),\n observedTimeUnixNano: hrTimeToNanosNumber(log.hrTimeObserved),\n severityNumber: log.severityNumber,\n severityText: log.severityText,\n body: log.body,\n attributes: attributes,\n traceId: log.spanContext?.traceId,\n spanId: log.spanContext?.spanId\n }\n })\n }\n\n console.log(this.logPrefix + JSON.stringify(otlpLikeStructure) + this.logSuffix);\n resultCallback({ code: ExportResultCode.SUCCESS });\n }\n\n async shutdown() { }\n}","import { randomBytes } from 'crypto';\n\nexport const idGenerator = {\n /**\n * 生成 TraceId (128-bit / 32-char hex)\n * 符合 W3C Trace Context 标准\n * 示例: \"5b8efff798038103d269b633813fc60c\"\n */\n generateTraceId(): string {\n // 16 字节 = 128 位,转为 hex 就是 32 个字符\n return randomBytes(16).toString('hex');\n },\n\n /**\n * 生成 SpanId (64-bit / 16-char hex)\n * 示例: \"eee19b7ec3c1b174\"\n */\n generateSpanId(): string {\n // 8 字节 = 64 位,转为 hex 就是 16 个字符\n return randomBytes(8).toString('hex');\n }\n};","import type { HrTime } from '@opentelemetry/api';\n/**\n * 将 HrTime 转换为纳秒数字\n * 注意:JavaScript Number 类型只能精确表示整数到 2^53,对于超过此范围的纳秒值会丢失精度\n*/\nexport function hrTimeToNanosNumber(hrTime: HrTime): number {\n // 将秒转换为 BigInt\n const seconds = BigInt(hrTime[0]);\n // 将纳秒转换为 BigInt\n const nanos = BigInt(hrTime[1]);\n \n // 计算总纳秒数: seconds * 10^9 + nanos\n // 注意:数字后面加 'n' 表示 BigInt 字面量,或者使用 BigInt() 构造函数\n const totalNanos = (seconds * 1_000_000_000n) + nanos;\n \n // 将 BigInt 转换为 Number\n return Number(totalNanos);\n}","export enum AppEnv {\n Dev = \"preview\",\n Prod = \"runtime\",\n}\n","\nconst safeStringify = (obj: unknown) => {\n try {\n return JSON.stringify(obj);\n } catch (error) {\n return '';\n }\n};\n// 将对象的所有属性值转换为字符串\nexport function convertAttributesToString(attributes: Record<string, unknown>): Record<string, string> {\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(attributes)) {\n result[key] = value !== undefined && value !== null ? typeof value === 'object' ? safeStringify(value) : String(value) : '';\n }\n return result;\n}"],"mappings":";;;;AAAA,SAASA,eAAe;AACxB,SAASC,+BAA+B;AACxC,SAASC,uBAAuB;AAChC,SAASC,MAAMC,sBAAsB;;;ACG9B,IAAMC,mBAAN,MAAMA;EAJb,OAIaA;;;;;;;;EAOXC,OAAOC,QAAoD;AAEzD,UAAMC,aAAwD,CAAC;AAG/D,UAAMC,kBAAkBC,OAAOC,QAAQH,UAAAA,EAAYI,OAAO,CAACC,KAAK,CAACC,KAAKC,KAAAA,MAAM;AAC1E,UAAIA,UAAUC,UAAaD,UAAU,QAAQA,UAAU,IAAI;AACzDF,YAAIC,GAAAA,IAAOC;MACb;AACA,aAAOF;IACT,GAAG,CAAC,CAAA;AAGJ,WAAO;MACLL,YAAYC;IACd;EACF;AACF;AAEO,IAAMQ,kBAAkB,IAAIZ,iBAAAA;;;AC/BnC,SAAuBa,wBAAwB;AAE/C,SAASC,kBAAkB;;;ACH3B,SAASC,mBAAmB;AAErB,IAAMC,cAAc;;;;;;EAMzBC,kBAAAA;AAEE,WAAOF,YAAY,EAAA,EAAIG,SAAS,KAAA;EAClC;;;;;EAMAC,iBAAAA;AAEE,WAAOJ,YAAY,CAAA,EAAGG,SAAS,KAAA;EACjC;AACF;;;AChBO,SAASE,oBAAoBC,QAAc;AAEhD,QAAMC,UAAUC,OAAOF,OAAO,CAAA,CAAE;AAEhC,QAAMG,QAAQD,OAAOF,OAAO,CAAA,CAAE;AAI9B,QAAMI,aAAcH,UAAU,cAAkBE;AAGhD,SAAOE,OAAOD,UAAAA;AAChB;AAZgBL;;;ACLT,IAAKO,SAAAA,0BAAAA,SAAAA;;;SAAAA;;;;ACCZ,IAAMC,gBAAgB,wBAACC,QAAAA;AACrB,MAAI;AACF,WAAOC,KAAKC,UAAUF,GAAAA;EACxB,SAASG,OAAO;AACd,WAAO;EACT;AACF,GANsB;AAQf,SAASC,0BAA0BC,YAAmC;AAC3E,QAAMC,SAAiC,CAAC;AACxC,aAAW,CAACC,KAAKC,KAAAA,KAAUC,OAAOC,QAAQL,UAAAA,GAAa;AACrDC,WAAOC,GAAAA,IAAOC,UAAUG,UAAaH,UAAU,OAAO,OAAOA,UAAU,WAAWT,cAAcS,KAAAA,IAASI,OAAOJ,KAAAA,IAAS;EAC3H;AACA,SAAOF;AACT;AANgBF;;;;;;;;;;;;;;AJET,IAAMS,iBAAN,MAAMA;SAAAA;;;EACHC,YAAoB;EACpBC,YAAoB;EAC5B,cAAc;EAAC;EAEfC,OAAOC,OAA2BC,gBAAgD;AAChF,UAAMC,QAAQC,QAAQC,IAAIC,aAAa;AACvC,UAAMC,oBAAgC;MACpCC,MAAMC,YAAYC,gBAAe;MACjCC,SAASR,QAAQS,OAAOC,MAAMD,OAAOE;IACvC;AAGA,UAAMC,oBAAoB;MACxBC,UAAU;;QAERC,YAAY,CAAC;MACf;MACAC,YAAYjB,MAAKkB,IAAIC,CAAAA,QAAAA;AACnB,cAAMC,gBAAgB;UACpB,GAAGd;UACH,GAAIa,IAAIH,cAAc,CAAC;QACzB;AAGA,cAAMA,aAAaK,0BAA0BD,aAAAA;AAE7C,eAAO;UACLE,cAAcC,oBAAoBJ,IAAIK,MAAM;UAC5CC,sBAAsBF,oBAAoBJ,IAAIO,cAAc;UAC5DC,gBAAgBR,IAAIQ;UACpBC,cAAcT,IAAIS;UAClBC,MAAMV,IAAIU;UACVb;UACAc,SAASX,IAAIY,aAAaD;UAC1BE,QAAQb,IAAIY,aAAaC;QAC3B;MACF,CAAA;IACF;AAEAC,YAAQd,IAAI,KAAKtB,YAAYqC,KAAKC,UAAUrB,iBAAAA,IAAqB,KAAKhB,SAAS;AAC/EG,mBAAe;MAAEmC,MAAMC,iBAAiBC;IAAQ,CAAA;EAClD;EAEA,MAAMC,WAAW;EAAE;AACrB;;;;;;;;AFhDO,IAAMC,mBAAN,MAAMA;EARb,OAQaA;;;EACHC,MAAsB;EACtBC,eAA+C;EAGvDC,QAAQ;AACN,UAAMC,WAAWC,gBAAgB;MAC/BC,WAAW;QAAC,IAAIC,iBAAAA;;IAClB,CAAA;AAGA,UAAMC,cAAc,IAAIC,eAAAA;AAExB,SAAKP,eAAe,IAAIQ,wBAAwBF,aAAa;MAC3DG,sBAAsB;MACtBC,oBAAoB;IACtB,CAAA;AAEA,SAAKX,MAAM,IAAIY,QAAQ;MACrBT;MACAU,oBAAoB,KAAKZ;MACzBa,eAAeC;IACjB,CAAA;AAEA,SAAKf,IAAIE,MAAK;EAChB;;;;;EAMOc,IAAIC,OAAeC,SAAiBC,QAAiC,CAAC,GAAG;AAC9E,UAAMC,SAASC,KAAKC,UAAU,YAAA;AAC9B,UAAMC,iBAAiB,KAAKC,YAAYP,KAAAA;AACxC,UAAMQ,eAAe,KAAKC,gBAAgBT,KAAAA;AAE1C,UAAMU,mBAAkC;MACtC,GAAIR,SAAS,CAAC;MACdS,KAAKC,QAAQD;IACf;AAEAR,WAAOU,KAAK;MACVC,MAAMb;MACNK;MACAE;MACAO,YAAYL;MACZM,WAAW,oBAAIC,KAAAA;IACjB,CAAA;EACF;EAEA,MAAMC,QAAQ;AACZ,QAAI,KAAKlC,cAAc;AACrB,YAAM,KAAKA,aAAamC,WAAU;IACpC;EACF;EAEQZ,YAAYP,OAA+B;AACjD,YAAQA,MAAMoB,YAAW,GAAA;MACvB,KAAK;AAAS,eAAOC,eAAeC;MACpC,KAAK;AAAS,eAAOD,eAAeE;MACpC,KAAK;AAAQ,eAAOF,eAAeG;MACnC,KAAK;AAAQ,eAAOH,eAAeI;MACnC,KAAK;AAAS,eAAOJ,eAAeK;MACpC,KAAK;AAAS,eAAOL,eAAeM;MACpC;AAAS,eAAON,eAAeG;IACjC;EACF;EAEUf,gBAAgBT,OAAuB;AAC/C,YAAQA,MAAMoB,YAAW,GAAA;MACvB,KAAK;AAAS,eAAO;MACrB,KAAK;AAAS,eAAO;MACrB,KAAK;AAAQ,eAAO;MACpB,KAAK;AAAQ,eAAO;MACpB,KAAK;AAAS,eAAO;MACrB,KAAK;AAAS,eAAO;MACrB;AAAS,eAAO;IAClB;EACF;AACF;AAEO,IAAMQ,SAAS,IAAI9C,iBAAAA;","names":["NodeSDK","BatchLogRecordProcessor","detectResources","logs","SeverityNumber","PlatformDetector","detect","config","attributes","cleanAttributes","Object","entries","reduce","acc","key","value","undefined","lowCodeDetector","ExportResultCode","Injectable","randomBytes","idGenerator","generateTraceId","toString","generateSpanId","hrTimeToNanosNumber","hrTime","seconds","BigInt","nanos","totalNanos","Number","AppEnv","safeStringify","obj","JSON","stringify","error","convertAttributesToString","attributes","result","key","value","Object","entries","undefined","String","CustomExporter","logPrefix","logSuffix","export","logs","resultCallback","isDev","process","env","NODE_ENV","defaultAttributes","uuid","idGenerator","generateTraceId","app_env","AppEnv","Dev","Prod","otlpLikeStructure","resource","attributes","logRecords","map","log","rawAttributes","convertAttributesToString","timeUnixNano","hrTimeToNanosNumber","hrTime","observedTimeUnixNano","hrTimeObserved","severityNumber","severityText","body","traceId","spanContext","spanId","console","JSON","stringify","code","ExportResultCode","SUCCESS","shutdown","AppOTelLoggerSDK","sdk","logProcessor","start","resource","detectResources","detectors","PlatformDetector","logExporter","CustomExporter","BatchLogRecordProcessor","scheduledDelayMillis","maxExportBatchSize","NodeSDK","logRecordProcessor","traceExporter","undefined","log","level","message","extra","logger","logs","getLogger","severityNumber","mapSeverity","severityText","mapSeverityText","mergedAttributes","pid","process","emit","body","attributes","timestamp","Date","flush","forceFlush","toLowerCase","SeverityNumber","TRACE","DEBUG","INFO","WARN","ERROR","FATAL","appSdk"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@lark-apaas/observable",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "Observable SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "license": "MIT",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "keywords": [
13
+ "plugin",
14
+ "server",
15
+ "typescript"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18.0.0"
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "require": "./dist/index.cjs"
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "tsup",
32
+ "dev": "tsup --watch",
33
+ "typecheck": "tsc --noEmit",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "lint": "echo 'ESLint skipped for TypeScript files'",
37
+ "lint:fix": "eslint src --ext .ts --fix"
38
+ },
39
+ "dependencies": {
40
+ "@opentelemetry/api": "^1.9.0",
41
+ "@opentelemetry/api-logs": "^0.208.0",
42
+ "@opentelemetry/core": "^2.2.0",
43
+ "@opentelemetry/resources": "^2.2.0",
44
+ "@opentelemetry/sdk-logs": "^0.208.0",
45
+ "@opentelemetry/sdk-node": "^0.208.0"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^5.2.0"
49
+ },
50
+ "peerDependencies": {
51
+ "@nestjs/common": "^10.4.20",
52
+ "@nestjs/core": "^10.4.20"
53
+ }
54
+ }