@logbrew/sdk 0.1.4 → 0.1.5

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.
@@ -0,0 +1,95 @@
1
+ const ZERO_TRACE_ID = "00000000000000000000000000000000";
2
+ const ZERO_SPAN_ID = "0000000000000000";
3
+
4
+ function buildLogContextHelpers({ SdkError }) {
5
+ function compactMetadata(metadata) {
6
+ if (metadata === undefined) {
7
+ return {};
8
+ }
9
+ if (!metadata || Array.isArray(metadata) || typeof metadata !== "object") {
10
+ throw new SdkError("validation_error", "metadata must be an object");
11
+ }
12
+ const safeMetadata = {};
13
+ for (const [key, value] of Object.entries(metadata)) {
14
+ if (isMetadataValue(value)) {
15
+ safeMetadata[key] = value;
16
+ }
17
+ }
18
+ return safeMetadata;
19
+ }
20
+
21
+ function traceFromProvider(provider, onError) {
22
+ if (!provider) {
23
+ return undefined;
24
+ }
25
+ try {
26
+ return provider();
27
+ } catch (error) {
28
+ onError(error);
29
+ return undefined;
30
+ }
31
+ }
32
+
33
+ return {
34
+ compactMetadata,
35
+ isMetadataValue,
36
+ normalizeLogTraceContext,
37
+ traceFromProvider,
38
+ traceMetadataFromLogContext
39
+ };
40
+ }
41
+
42
+ function isMetadataValue(value) {
43
+ return (
44
+ value === null
45
+ || typeof value === "string"
46
+ || typeof value === "number" && Number.isFinite(value)
47
+ || typeof value === "boolean"
48
+ );
49
+ }
50
+
51
+ function traceMetadataFromLogContext(trace) {
52
+ const normalized = normalizeLogTraceContext(trace);
53
+ if (!normalized) {
54
+ return {};
55
+ }
56
+ return {
57
+ traceId: normalized.traceId,
58
+ spanId: normalized.spanId,
59
+ ...(normalized.parentSpanId !== undefined ? { parentSpanId: normalized.parentSpanId } : {}),
60
+ ...(normalized.sampled !== undefined ? { sampled: normalized.sampled } : {})
61
+ };
62
+ }
63
+
64
+ function normalizeLogTraceContext(trace) {
65
+ if (!trace || Array.isArray(trace) || typeof trace !== "object") {
66
+ return undefined;
67
+ }
68
+ const traceId = normalizeHexId(trace.traceId, 32, ZERO_TRACE_ID);
69
+ const spanId = normalizeHexId(trace.spanId, 16, ZERO_SPAN_ID);
70
+ if (!traceId || !spanId) {
71
+ return undefined;
72
+ }
73
+ const parentSpanId = normalizeHexId(trace.parentSpanId, 16, ZERO_SPAN_ID);
74
+ return {
75
+ traceId,
76
+ spanId,
77
+ ...(parentSpanId !== undefined ? { parentSpanId } : {}),
78
+ ...(typeof trace.sampled === "boolean" ? { sampled: trace.sampled } : {})
79
+ };
80
+ }
81
+
82
+ function normalizeHexId(value, length, zeroValue) {
83
+ if (typeof value !== "string") {
84
+ return undefined;
85
+ }
86
+ const pattern = length === 32
87
+ ? /^[0-9a-fA-F]{32}$/u
88
+ : /^[0-9a-fA-F]{16}$/u;
89
+ if (!pattern.test(value) || value.toLowerCase() === zeroValue) {
90
+ return undefined;
91
+ }
92
+ return value.toLowerCase();
93
+ }
94
+
95
+ module.exports = { buildLogContextHelpers };
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "@logbrew/sdk",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",
7
+ "react-native": "./react-native.js",
7
8
  "types": "./index.d.ts",
8
9
  "bin": {
9
10
  "logbrew-release-artifacts": "./release-artifacts.js"
10
11
  },
11
12
  "exports": {
12
13
  ".": {
14
+ "react-native": {
15
+ "types": "./react-native.d.ts",
16
+ "default": "./react-native.js"
17
+ },
13
18
  "import": {
14
19
  "types": "./index.d.ts",
15
20
  "default": "./index.js"
@@ -34,13 +39,18 @@
34
39
  },
35
40
  "files": [
36
41
  "index.cjs",
42
+ "core.cjs",
37
43
  "index.js",
38
44
  "index.d.ts",
39
45
  "index.d.cts",
40
46
  "issue-stack.cjs",
47
+ "log-context.cjs",
41
48
  "opentelemetry.cjs",
49
+ "react-native.js",
50
+ "react-native.d.ts",
42
51
  "support-ticket.cjs",
43
52
  "trace-context.cjs",
53
+ "winston.cjs",
44
54
  "release-artifacts-common.js",
45
55
  "release-artifacts-build.cjs",
46
56
  "release-artifacts.js",
@@ -0,0 +1,103 @@
1
+ import * as core from "./index.js";
2
+
3
+ export type {
4
+ ActionAttributes,
5
+ BaggageEntry,
6
+ ConsoleCaptureConfig,
7
+ ConsoleCaptureHandle,
8
+ ConsoleLike,
9
+ ConsoleMethodName,
10
+ CurrentOpenTelemetryTraceContextOptions,
11
+ DeliveryHealthSnapshot,
12
+ DroppedEvent,
13
+ EnvironmentAttributes,
14
+ Event,
15
+ EventFilter,
16
+ EventStore,
17
+ IssueAttributes,
18
+ IssueStackFrame,
19
+ JavaScriptErrorIssueOptions,
20
+ LogAttributes,
21
+ LogCorrelationTraceContext,
22
+ Metadata,
23
+ MetadataValue,
24
+ MetricAttributes,
25
+ NetworkMilestoneInput,
26
+ OpenTelemetryApiLike,
27
+ OpenTelemetryExportResult,
28
+ OpenTelemetryHrTimeLike,
29
+ OpenTelemetryReadableSpanLike,
30
+ OpenTelemetryReadableSpanOptions,
31
+ OpenTelemetrySpanContextLike,
32
+ OpenTelemetrySpanExporterConfig,
33
+ OpenTelemetrySpanExporterHandle,
34
+ OpenTelemetrySpanLike,
35
+ OpenTelemetrySpanLinkLike,
36
+ OpenTelemetrySpanProcessorConfig,
37
+ OpenTelemetrySpanProcessorHandle,
38
+ OpenTelemetryTimedEventLike,
39
+ OpenTelemetryTraceContextOptions,
40
+ PinoDestinationConfig,
41
+ PinoDestinationHandle,
42
+ PinoLogRecord,
43
+ ProductActionInput,
44
+ ReleaseAttributes,
45
+ Severity,
46
+ SeverityAlias,
47
+ SeverityInput,
48
+ SpanAttributes,
49
+ SpanEventSummary,
50
+ SpanLinkSummary,
51
+ StoredEvent,
52
+ SupportDiagnosticsValue,
53
+ SupportTicketCategory,
54
+ SupportTicketDraft,
55
+ SupportTicketDraftInput,
56
+ SupportTicketSource,
57
+ TimelineAttributesOptions,
58
+ TraceContextInput,
59
+ TraceparentContext,
60
+ TraceparentInput,
61
+ TraceparentSpanInput,
62
+ TracestateEntry,
63
+ Transport,
64
+ TransportResponse
65
+ } from "./index.js";
66
+
67
+ export {
68
+ createBaggage,
69
+ createIssueAttributesFromError,
70
+ createLogBrewOpenTelemetrySpanExporter,
71
+ createLogBrewOpenTelemetrySpanProcessor,
72
+ createLogBrewPinoDestination,
73
+ createNetworkMilestoneAttributes,
74
+ createProductActionAttributes,
75
+ createSupportTicketDraft,
76
+ createTraceContextHeaders,
77
+ createTraceparent,
78
+ createTraceparentHeaders,
79
+ createTracestate,
80
+ installLogBrewConsoleCapture,
81
+ LogBrewClient,
82
+ logAttributesFromConsoleArgs,
83
+ logAttributesFromPinoRecord,
84
+ logbrewLevelFromConsoleMethod,
85
+ logbrewTraceContextFromCurrentOpenTelemetrySpan,
86
+ logbrewTraceContextFromOpenTelemetrySpan,
87
+ logbrewTraceContextFromOpenTelemetrySpanContext,
88
+ parseBaggage,
89
+ parseTraceparent,
90
+ parseTracestate,
91
+ RecordingTransport,
92
+ SdkError,
93
+ spanAttributesFromOpenTelemetryReadableSpan,
94
+ spanAttributesFromTraceparent,
95
+ TransportError
96
+ } from "./index.js";
97
+
98
+ declare const sdk: Omit<
99
+ typeof core,
100
+ "createLogBrewWinstonTransport" | "logAttributesFromWinstonInfo"
101
+ >;
102
+
103
+ export default sdk;
@@ -0,0 +1,34 @@
1
+ import sdk from "./core.cjs";
2
+
3
+ export const {
4
+ createBaggage,
5
+ createIssueAttributesFromError,
6
+ createNetworkMilestoneAttributes,
7
+ createProductActionAttributes,
8
+ createLogBrewOpenTelemetrySpanExporter,
9
+ createLogBrewOpenTelemetrySpanProcessor,
10
+ createSupportTicketDraft,
11
+ createTraceContextHeaders,
12
+ createTraceparent,
13
+ createTraceparentHeaders,
14
+ createTracestate,
15
+ createLogBrewPinoDestination,
16
+ installLogBrewConsoleCapture,
17
+ LogBrewClient,
18
+ logbrewTraceContextFromCurrentOpenTelemetrySpan,
19
+ logbrewTraceContextFromOpenTelemetrySpan,
20
+ logbrewTraceContextFromOpenTelemetrySpanContext,
21
+ logAttributesFromConsoleArgs,
22
+ logAttributesFromPinoRecord,
23
+ logbrewLevelFromConsoleMethod,
24
+ parseBaggage,
25
+ parseTraceparent,
26
+ parseTracestate,
27
+ RecordingTransport,
28
+ SdkError,
29
+ spanAttributesFromOpenTelemetryReadableSpan,
30
+ spanAttributesFromTraceparent,
31
+ TransportError
32
+ } = sdk;
33
+
34
+ export default sdk;
package/winston.cjs ADDED
@@ -0,0 +1,273 @@
1
+ const { Writable } = require("node:stream");
2
+
3
+ const { LogBrewClient, SdkError } = require("./core.cjs");
4
+ const { buildLogContextHelpers } = require("./log-context.cjs");
5
+
6
+ const WINSTON_RESERVED_FIELDS = new Set([
7
+ "level",
8
+ "message",
9
+ "timestamp",
10
+ "time",
11
+ "err",
12
+ "error",
13
+ "stack"
14
+ ]);
15
+ const {
16
+ compactMetadata,
17
+ isMetadataValue,
18
+ traceFromProvider,
19
+ traceMetadataFromLogContext
20
+ } = buildLogContextHelpers({ SdkError });
21
+
22
+ function createLogBrewWinstonTransport(config) {
23
+ if (!config || typeof config !== "object") {
24
+ throw new SdkError("validation_error", "Winston transport config must be an object");
25
+ }
26
+
27
+ const client = config.client;
28
+ if (!(client instanceof LogBrewClient)) {
29
+ throw new SdkError("validation_error", "Winston transport client must be a LogBrewClient");
30
+ }
31
+
32
+ const transport = config.transport;
33
+ const flushOnWrite = config.flushOnWrite === true;
34
+ const includeErrorStack = config.includeErrorStack === true;
35
+ const logger = config.logger ?? "winston";
36
+ const metadata = compactMetadata(config.metadata);
37
+ const timestamp = typeof config.timestamp === "function"
38
+ ? config.timestamp
39
+ : () => new Date().toISOString();
40
+ const eventIdPrefix = config.eventIdPrefix ?? "winston";
41
+ const onError = typeof config.onError === "function" ? config.onError : () => {};
42
+ const traceProvider = typeof config.traceProvider === "function" ? config.traceProvider : null;
43
+ const state = {
44
+ captured: 0,
45
+ pendingFlush: Promise.resolve(null)
46
+ };
47
+
48
+ const winstonTransport = new Writable({
49
+ objectMode: true,
50
+ write(info, _encoding, callback) {
51
+ try {
52
+ captureWinstonInfo({
53
+ client,
54
+ eventIdPrefix,
55
+ flushOnWrite,
56
+ includeErrorStack,
57
+ info,
58
+ logger,
59
+ metadata,
60
+ onError,
61
+ state,
62
+ timestamp,
63
+ traceProvider,
64
+ transport
65
+ });
66
+ } catch (error) {
67
+ onError(error);
68
+ } finally {
69
+ callback();
70
+ }
71
+ }
72
+ });
73
+
74
+ winstonTransport.log = function log(info, callback) {
75
+ this.write(info);
76
+ if (typeof callback === "function") {
77
+ callback();
78
+ }
79
+ };
80
+ winstonTransport.flush = async () => {
81
+ if (transport && client.pendingEvents() > 0) {
82
+ state.pendingFlush = Promise.resolve(client.flush(transport)).catch((error) => {
83
+ onError(error);
84
+ return null;
85
+ });
86
+ }
87
+ return state.pendingFlush;
88
+ };
89
+ if (typeof config.level === "string" && config.level.trim() !== "") {
90
+ winstonTransport.level = config.level;
91
+ }
92
+ if (config.name !== undefined) {
93
+ winstonTransport.name = String(config.name);
94
+ }
95
+ if (config.silent === true) {
96
+ winstonTransport.silent = true;
97
+ }
98
+ if (config.handleExceptions === true) {
99
+ winstonTransport.handleExceptions = true;
100
+ }
101
+ if (config.handleRejections === true) {
102
+ winstonTransport.handleRejections = true;
103
+ }
104
+
105
+ return winstonTransport;
106
+ }
107
+
108
+ function captureWinstonInfo(config) {
109
+ if (config.info?.silent === true) {
110
+ return;
111
+ }
112
+ config.state.captured += 1;
113
+ config.client.log(
114
+ `${config.eventIdPrefix}_${config.state.captured}`,
115
+ timestampFromWinstonInfo(config.info, config.timestamp),
116
+ logAttributesFromWinstonInfo(config.info, {
117
+ includeErrorStack: config.includeErrorStack,
118
+ logger: config.logger,
119
+ metadata: config.metadata,
120
+ trace: traceFromProvider(config.traceProvider, config.onError)
121
+ })
122
+ );
123
+ if (config.flushOnWrite && config.transport) {
124
+ config.state.pendingFlush = Promise.resolve(config.client.flush(config.transport)).catch((error) => {
125
+ config.onError(error);
126
+ return null;
127
+ });
128
+ }
129
+ }
130
+
131
+ function logAttributesFromWinstonInfo(info, options = {}) {
132
+ if (!info || Array.isArray(info) || typeof info !== "object") {
133
+ throw new SdkError("validation_error", "Winston info must be an object");
134
+ }
135
+
136
+ const level = logbrewLevelFromWinstonLevel(info.level);
137
+ const metadata = {
138
+ ...compactMetadata(options.metadata),
139
+ winstonLevel: winstonLevelLabel(info.level),
140
+ ...winstonContextMetadata(info),
141
+ ...traceMetadataFromLogContext(options.trace)
142
+ };
143
+ addWinstonErrorMetadata(metadata, info, options.includeErrorStack === true);
144
+
145
+ return {
146
+ message: winstonMessage(info),
147
+ level,
148
+ ...(options.logger ? { logger: options.logger } : {}),
149
+ metadata
150
+ };
151
+ }
152
+
153
+ function timestampFromWinstonInfo(info, fallbackTimestamp) {
154
+ const value = info?.timestamp ?? info?.time;
155
+ if (typeof value === "number" && Number.isFinite(value)) {
156
+ return new Date(value).toISOString();
157
+ }
158
+ if (typeof value === "string" && value.trim() !== "") {
159
+ const parsed = new Date(value);
160
+ if (!Number.isNaN(parsed.valueOf())) {
161
+ return parsed.toISOString();
162
+ }
163
+ return value;
164
+ }
165
+ if (value instanceof Date && !Number.isNaN(value.valueOf())) {
166
+ return value.toISOString();
167
+ }
168
+ return fallbackTimestamp();
169
+ }
170
+
171
+ function logbrewLevelFromWinstonLevel(level) {
172
+ switch (String(level).toLowerCase()) {
173
+ case "debug":
174
+ case "silly":
175
+ return "info";
176
+ case "warn":
177
+ case "warning":
178
+ return "warning";
179
+ case "error":
180
+ return "error";
181
+ case "crit":
182
+ case "critical":
183
+ case "fatal":
184
+ return "critical";
185
+ case "http":
186
+ case "verbose":
187
+ case "info":
188
+ default:
189
+ return "info";
190
+ }
191
+ }
192
+
193
+ function winstonLevelLabel(level) {
194
+ return typeof level === "string" && level.trim() !== "" ? level : "info";
195
+ }
196
+
197
+ function winstonMessage(info) {
198
+ if (typeof info.message === "string" && info.message.trim() !== "") {
199
+ return info.message;
200
+ }
201
+ const error = info.err ?? info.error;
202
+ if (error && typeof error === "object" && typeof error.message === "string" && error.message.trim() !== "") {
203
+ return error.message;
204
+ }
205
+ return "winston event";
206
+ }
207
+
208
+ function winstonContextMetadata(info) {
209
+ const metadata = {};
210
+ for (const [key, value] of Object.entries(info)) {
211
+ if (!WINSTON_RESERVED_FIELDS.has(key) && isMetadataValue(value)) {
212
+ metadata[`context.${key}`] = value;
213
+ }
214
+ }
215
+ return metadata;
216
+ }
217
+
218
+ function addWinstonErrorMetadata(metadata, info, includeErrorStack) {
219
+ const error = info.err ?? info.error;
220
+ addWinstonNestedErrorMetadata(metadata, error, includeErrorStack);
221
+ if (typeof info.stack === "string" && info.stack.trim() !== "") {
222
+ const firstLine = info.stack.split(/\r?\n/u)[0] ?? "";
223
+ const match = /^([A-Za-z][A-Za-z0-9_.]*(?:Error|Exception)?):\s*(.*)$/u.exec(firstLine);
224
+ if (match && metadata.errorName === undefined) {
225
+ metadata.errorName = match[1];
226
+ }
227
+ if (match && match[2] && metadata.errorMessage === undefined) {
228
+ metadata.errorMessage = match[2];
229
+ }
230
+ if (includeErrorStack) {
231
+ metadata.errorStack = info.stack;
232
+ }
233
+ }
234
+ }
235
+
236
+ function addWinstonNestedErrorMetadata(metadata, error, includeErrorStack) {
237
+ if (!error) {
238
+ return;
239
+ }
240
+ if (error instanceof Error) {
241
+ metadata.errorName = error.name || "Error";
242
+ if (error.message) {
243
+ metadata.errorMessage = error.message;
244
+ }
245
+ if (includeErrorStack && error.stack) {
246
+ metadata.errorStack = error.stack;
247
+ }
248
+ return;
249
+ }
250
+ if (typeof error === "object") {
251
+ const name = error.type ?? error.name;
252
+ const message = error.message;
253
+ const stack = error.stack;
254
+ if (typeof name === "string" && name.trim() !== "") {
255
+ metadata.errorName = name;
256
+ }
257
+ if (typeof message === "string" && message.trim() !== "") {
258
+ metadata.errorMessage = message;
259
+ }
260
+ if (includeErrorStack && typeof stack === "string" && stack.trim() !== "") {
261
+ metadata.errorStack = stack;
262
+ }
263
+ return;
264
+ }
265
+ if (typeof error === "string" && error.trim() !== "") {
266
+ metadata.errorMessage = error;
267
+ }
268
+ }
269
+
270
+ module.exports = {
271
+ createLogBrewWinstonTransport,
272
+ logAttributesFromWinstonInfo
273
+ };