logstack-js 1.0.0
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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/index.d.mts +132 -0
- package/dist/index.d.ts +132 -0
- package/dist/index.js +505 -0
- package/dist/index.mjs +477 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mosesedem
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# logstack-js
|
|
2
|
+
|
|
3
|
+
The JavaScript/TypeScript SDK for [Logstack](https://github.com/Mosesedem/logstack) — log
|
|
4
|
+
ingestion, real-time streaming, and querying.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install logstack-js
|
|
10
|
+
# or: pnpm add logstack-js / yarn add logstack-js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createLogStack } from "logstack-js";
|
|
17
|
+
|
|
18
|
+
const logstack = createLogStack({
|
|
19
|
+
apiKey: "ls_live_xxx",
|
|
20
|
+
// endpoint host (SDK appends /v1/logs); defaults to https://api.logstack.tech
|
|
21
|
+
// endpoint: "http://localhost:8080",
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
logstack.info("Application started", { version: "1.0.0" });
|
|
25
|
+
logstack.error("Payment failed", { orderId: "ord_123", code: 402 });
|
|
26
|
+
|
|
27
|
+
// flush + stop timers on shutdown
|
|
28
|
+
await logstack.close();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Behavior
|
|
32
|
+
|
|
33
|
+
- **Console and server are independent.** Every log is written to the console (always in
|
|
34
|
+
development/test; in production only when `consoleInProduction: true`, unless `silent`),
|
|
35
|
+
and is *also* shipped to the server whenever an `apiKey` is set and `disabled` is false —
|
|
36
|
+
in **all** environments.
|
|
37
|
+
- A missing API key should degrade the client to console-only (`disabled: true`); it must
|
|
38
|
+
never become a silent no-op.
|
|
39
|
+
- Browser logs are queued to `localStorage` while offline (bounded by `maxOfflineQueueSize`,
|
|
40
|
+
default 1000) and flushed on reconnect.
|
|
41
|
+
|
|
42
|
+
## Configuration
|
|
43
|
+
|
|
44
|
+
| Option | Default | Description |
|
|
45
|
+
| ------ | ------- | ----------- |
|
|
46
|
+
| `apiKey` | — | **Required.** Project API key (`ls_...`). |
|
|
47
|
+
| `endpoint` | `https://api.logstack.tech` | API host; the SDK appends `/v1/logs`. |
|
|
48
|
+
| `environment` | auto (`NODE_ENV`) | `development` \| `staging` \| `production` \| `test`. |
|
|
49
|
+
| `batchSize` | `100` | Logs buffered before an auto-flush. |
|
|
50
|
+
| `flushInterval` | `5000` | Auto-flush interval (ms). |
|
|
51
|
+
| `maxRetries` | `3` | Retry attempts for `5xx` responses. |
|
|
52
|
+
| `consoleInProduction` | `false` | Also log to console in production/staging. |
|
|
53
|
+
| `silent` | `false` | Disable all console output. |
|
|
54
|
+
| `disabled` | `false` | Console-only mode: never buffer, send, or queue. |
|
|
55
|
+
| `maxOfflineQueueSize` | `1000` | Cap on the offline (localStorage) queue. |
|
|
56
|
+
| `captureContext` | `true` | Auto-capture URL/route/user-agent in the browser. |
|
|
57
|
+
| `onError` | — | `(error, logs) => void` callback for send failures. |
|
|
58
|
+
|
|
59
|
+
## Log levels
|
|
60
|
+
|
|
61
|
+
`debug` · `info` · `warn` · `error` · `critical` · `fatal` — each available as a method, e.g.
|
|
62
|
+
`logstack.debug(message, metadata?)`.
|
|
63
|
+
|
|
64
|
+
## Querying
|
|
65
|
+
|
|
66
|
+
With `projectId` in the config you can read logs back:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const result = await logstack.getLogs({ level: "error", limit: 50 });
|
|
70
|
+
const one = await logstack.getLogById(12345);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Full reference: [docs/SDK.md](https://github.com/Mosesedem/logstack/blob/main/docs/SDK.md).
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
type LogLevel = "debug" | "info" | "warn" | "error" | "critical" | "fatal";
|
|
2
|
+
type Environment = "development" | "production" | "staging" | "test";
|
|
3
|
+
interface LogContext {
|
|
4
|
+
/** Page URL or API endpoint where the log originated */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Route path (for framework routing) */
|
|
7
|
+
route?: string;
|
|
8
|
+
/** HTTP method (for API endpoints) */
|
|
9
|
+
method?: string;
|
|
10
|
+
/** User agent string */
|
|
11
|
+
userAgent?: string;
|
|
12
|
+
/** Client IP address */
|
|
13
|
+
ip?: string;
|
|
14
|
+
/** Request ID for tracing */
|
|
15
|
+
requestId?: string;
|
|
16
|
+
/** Component or module name */
|
|
17
|
+
component?: string;
|
|
18
|
+
}
|
|
19
|
+
interface LogEntry {
|
|
20
|
+
level: LogLevel;
|
|
21
|
+
message: string;
|
|
22
|
+
source?: string;
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
timestamp?: string;
|
|
25
|
+
/** Contextual information about where the log originated */
|
|
26
|
+
context?: LogContext;
|
|
27
|
+
}
|
|
28
|
+
interface LogRecord extends LogEntry {
|
|
29
|
+
id: number;
|
|
30
|
+
projectId: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
}
|
|
33
|
+
interface LogQueryOptions {
|
|
34
|
+
/** Filter by log level */
|
|
35
|
+
level?: LogLevel;
|
|
36
|
+
/** Search in message content */
|
|
37
|
+
search?: string;
|
|
38
|
+
/** Start time for date range filter (ISO 8601) */
|
|
39
|
+
startTime?: string;
|
|
40
|
+
/** End time for date range filter (ISO 8601) */
|
|
41
|
+
endTime?: string;
|
|
42
|
+
/** Number of records to skip */
|
|
43
|
+
offset?: number;
|
|
44
|
+
/** Maximum number of records to return (max 1000) */
|
|
45
|
+
limit?: number;
|
|
46
|
+
}
|
|
47
|
+
interface LogQueryResult {
|
|
48
|
+
logs: LogRecord[];
|
|
49
|
+
total: number;
|
|
50
|
+
offset: number;
|
|
51
|
+
limit: number;
|
|
52
|
+
}
|
|
53
|
+
interface LogStackConfig {
|
|
54
|
+
apiKey: string;
|
|
55
|
+
endpoint?: string;
|
|
56
|
+
batchSize?: number;
|
|
57
|
+
flushInterval?: number;
|
|
58
|
+
maxRetries?: number;
|
|
59
|
+
onError?: (error: Error, logs: LogEntry[]) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Environment label attached to logs and used for console gating (see
|
|
62
|
+
* `consoleInProduction`). Logs are sent to the server in every environment as
|
|
63
|
+
* long as an `apiKey` is set and `disabled` is not true. Auto-detected from
|
|
64
|
+
* `NODE_ENV` when omitted; defaults to 'production'.
|
|
65
|
+
*/
|
|
66
|
+
environment?: Environment;
|
|
67
|
+
/**
|
|
68
|
+
* Whether to log to the console in production/staging mode. In development and
|
|
69
|
+
* test, console output is always on (unless `silent`). Defaults to false, so
|
|
70
|
+
* production stays quiet on the console while still shipping logs to the server.
|
|
71
|
+
*/
|
|
72
|
+
consoleInProduction?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Disable all console output regardless of environment. Defaults to false.
|
|
75
|
+
*/
|
|
76
|
+
silent?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Console-only mode: log to the console but never buffer, send, or queue to the
|
|
79
|
+
* server. Use when no API key/endpoint is available (e.g. local dev without a
|
|
80
|
+
* configured project). Defaults to false.
|
|
81
|
+
*/
|
|
82
|
+
disabled?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Maximum number of logs retained in the offline (localStorage) queue. Oldest
|
|
85
|
+
* entries are dropped past this cap to avoid exceeding storage quota.
|
|
86
|
+
* Defaults to 1000.
|
|
87
|
+
*/
|
|
88
|
+
maxOfflineQueueSize?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Auto-capture context like URL, route, user agent.
|
|
91
|
+
* Defaults to true.
|
|
92
|
+
*/
|
|
93
|
+
captureContext?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Project ID for query operations. Required for getLogs/getLogById.
|
|
96
|
+
*/
|
|
97
|
+
projectId?: string;
|
|
98
|
+
}
|
|
99
|
+
interface LogStackClient {
|
|
100
|
+
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
101
|
+
info(message: string, metadata?: Record<string, unknown>): void;
|
|
102
|
+
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
103
|
+
error(message: string, metadata?: Record<string, unknown>): void;
|
|
104
|
+
critical(message: string, metadata?: Record<string, unknown>): void;
|
|
105
|
+
fatal(message: string, metadata?: Record<string, unknown>): void;
|
|
106
|
+
log(entry: LogEntry): void;
|
|
107
|
+
flush(): Promise<void>;
|
|
108
|
+
close(): Promise<void>;
|
|
109
|
+
/** Set context that will be included with all subsequent logs */
|
|
110
|
+
setContext(context: LogContext): void;
|
|
111
|
+
/** Clear the current context */
|
|
112
|
+
clearContext(): void;
|
|
113
|
+
/** Query logs from the server (requires projectId in config) */
|
|
114
|
+
getLogs(options?: LogQueryOptions): Promise<LogQueryResult>;
|
|
115
|
+
/** Get a single log by ID (requires projectId in config) */
|
|
116
|
+
getLogById(logId: number): Promise<LogRecord>;
|
|
117
|
+
}
|
|
118
|
+
/** Structured error from Logstack API */
|
|
119
|
+
interface LogStackErrorResponse {
|
|
120
|
+
code: string;
|
|
121
|
+
message: string;
|
|
122
|
+
}
|
|
123
|
+
/** Error class for Logstack API errors */
|
|
124
|
+
declare class LogStackError extends Error {
|
|
125
|
+
code: string;
|
|
126
|
+
status: number;
|
|
127
|
+
constructor(code: string, message: string, status: number);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
declare function createLogStack(config: LogStackConfig): LogStackClient;
|
|
131
|
+
|
|
132
|
+
export { type Environment, type LogContext, type LogEntry, type LogLevel, type LogQueryOptions, type LogQueryResult, type LogRecord, type LogStackClient, type LogStackConfig, LogStackError, type LogStackErrorResponse, createLogStack };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
type LogLevel = "debug" | "info" | "warn" | "error" | "critical" | "fatal";
|
|
2
|
+
type Environment = "development" | "production" | "staging" | "test";
|
|
3
|
+
interface LogContext {
|
|
4
|
+
/** Page URL or API endpoint where the log originated */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Route path (for framework routing) */
|
|
7
|
+
route?: string;
|
|
8
|
+
/** HTTP method (for API endpoints) */
|
|
9
|
+
method?: string;
|
|
10
|
+
/** User agent string */
|
|
11
|
+
userAgent?: string;
|
|
12
|
+
/** Client IP address */
|
|
13
|
+
ip?: string;
|
|
14
|
+
/** Request ID for tracing */
|
|
15
|
+
requestId?: string;
|
|
16
|
+
/** Component or module name */
|
|
17
|
+
component?: string;
|
|
18
|
+
}
|
|
19
|
+
interface LogEntry {
|
|
20
|
+
level: LogLevel;
|
|
21
|
+
message: string;
|
|
22
|
+
source?: string;
|
|
23
|
+
metadata?: Record<string, unknown>;
|
|
24
|
+
timestamp?: string;
|
|
25
|
+
/** Contextual information about where the log originated */
|
|
26
|
+
context?: LogContext;
|
|
27
|
+
}
|
|
28
|
+
interface LogRecord extends LogEntry {
|
|
29
|
+
id: number;
|
|
30
|
+
projectId: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
}
|
|
33
|
+
interface LogQueryOptions {
|
|
34
|
+
/** Filter by log level */
|
|
35
|
+
level?: LogLevel;
|
|
36
|
+
/** Search in message content */
|
|
37
|
+
search?: string;
|
|
38
|
+
/** Start time for date range filter (ISO 8601) */
|
|
39
|
+
startTime?: string;
|
|
40
|
+
/** End time for date range filter (ISO 8601) */
|
|
41
|
+
endTime?: string;
|
|
42
|
+
/** Number of records to skip */
|
|
43
|
+
offset?: number;
|
|
44
|
+
/** Maximum number of records to return (max 1000) */
|
|
45
|
+
limit?: number;
|
|
46
|
+
}
|
|
47
|
+
interface LogQueryResult {
|
|
48
|
+
logs: LogRecord[];
|
|
49
|
+
total: number;
|
|
50
|
+
offset: number;
|
|
51
|
+
limit: number;
|
|
52
|
+
}
|
|
53
|
+
interface LogStackConfig {
|
|
54
|
+
apiKey: string;
|
|
55
|
+
endpoint?: string;
|
|
56
|
+
batchSize?: number;
|
|
57
|
+
flushInterval?: number;
|
|
58
|
+
maxRetries?: number;
|
|
59
|
+
onError?: (error: Error, logs: LogEntry[]) => void;
|
|
60
|
+
/**
|
|
61
|
+
* Environment label attached to logs and used for console gating (see
|
|
62
|
+
* `consoleInProduction`). Logs are sent to the server in every environment as
|
|
63
|
+
* long as an `apiKey` is set and `disabled` is not true. Auto-detected from
|
|
64
|
+
* `NODE_ENV` when omitted; defaults to 'production'.
|
|
65
|
+
*/
|
|
66
|
+
environment?: Environment;
|
|
67
|
+
/**
|
|
68
|
+
* Whether to log to the console in production/staging mode. In development and
|
|
69
|
+
* test, console output is always on (unless `silent`). Defaults to false, so
|
|
70
|
+
* production stays quiet on the console while still shipping logs to the server.
|
|
71
|
+
*/
|
|
72
|
+
consoleInProduction?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Disable all console output regardless of environment. Defaults to false.
|
|
75
|
+
*/
|
|
76
|
+
silent?: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Console-only mode: log to the console but never buffer, send, or queue to the
|
|
79
|
+
* server. Use when no API key/endpoint is available (e.g. local dev without a
|
|
80
|
+
* configured project). Defaults to false.
|
|
81
|
+
*/
|
|
82
|
+
disabled?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Maximum number of logs retained in the offline (localStorage) queue. Oldest
|
|
85
|
+
* entries are dropped past this cap to avoid exceeding storage quota.
|
|
86
|
+
* Defaults to 1000.
|
|
87
|
+
*/
|
|
88
|
+
maxOfflineQueueSize?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Auto-capture context like URL, route, user agent.
|
|
91
|
+
* Defaults to true.
|
|
92
|
+
*/
|
|
93
|
+
captureContext?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Project ID for query operations. Required for getLogs/getLogById.
|
|
96
|
+
*/
|
|
97
|
+
projectId?: string;
|
|
98
|
+
}
|
|
99
|
+
interface LogStackClient {
|
|
100
|
+
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
101
|
+
info(message: string, metadata?: Record<string, unknown>): void;
|
|
102
|
+
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
103
|
+
error(message: string, metadata?: Record<string, unknown>): void;
|
|
104
|
+
critical(message: string, metadata?: Record<string, unknown>): void;
|
|
105
|
+
fatal(message: string, metadata?: Record<string, unknown>): void;
|
|
106
|
+
log(entry: LogEntry): void;
|
|
107
|
+
flush(): Promise<void>;
|
|
108
|
+
close(): Promise<void>;
|
|
109
|
+
/** Set context that will be included with all subsequent logs */
|
|
110
|
+
setContext(context: LogContext): void;
|
|
111
|
+
/** Clear the current context */
|
|
112
|
+
clearContext(): void;
|
|
113
|
+
/** Query logs from the server (requires projectId in config) */
|
|
114
|
+
getLogs(options?: LogQueryOptions): Promise<LogQueryResult>;
|
|
115
|
+
/** Get a single log by ID (requires projectId in config) */
|
|
116
|
+
getLogById(logId: number): Promise<LogRecord>;
|
|
117
|
+
}
|
|
118
|
+
/** Structured error from Logstack API */
|
|
119
|
+
interface LogStackErrorResponse {
|
|
120
|
+
code: string;
|
|
121
|
+
message: string;
|
|
122
|
+
}
|
|
123
|
+
/** Error class for Logstack API errors */
|
|
124
|
+
declare class LogStackError extends Error {
|
|
125
|
+
code: string;
|
|
126
|
+
status: number;
|
|
127
|
+
constructor(code: string, message: string, status: number);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
declare function createLogStack(config: LogStackConfig): LogStackClient;
|
|
131
|
+
|
|
132
|
+
export { type Environment, type LogContext, type LogEntry, type LogLevel, type LogQueryOptions, type LogQueryResult, type LogRecord, type LogStackClient, type LogStackConfig, LogStackError, type LogStackErrorResponse, createLogStack };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
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 __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
LogStackError: () => LogStackError,
|
|
24
|
+
createLogStack: () => createLogStack
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/types.ts
|
|
29
|
+
var LogStackError = class extends Error {
|
|
30
|
+
constructor(code, message, status) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.code = code;
|
|
33
|
+
this.status = status;
|
|
34
|
+
this.name = "LogStackError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// src/index.ts
|
|
39
|
+
var DEFAULT_ENDPOINT = "https://api.logstack.tech";
|
|
40
|
+
var DEFAULT_BATCH_SIZE = 100;
|
|
41
|
+
var DEFAULT_FLUSH_INTERVAL = 5e3;
|
|
42
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
43
|
+
var DEFAULT_MAX_OFFLINE_QUEUE = 1e3;
|
|
44
|
+
var RETRY_BASE_DELAY = 1e3;
|
|
45
|
+
var OFFLINE_STORAGE_KEY = "logstack_offline_queue";
|
|
46
|
+
var OFFLINE_STORAGE_TTL = 24 * 60 * 60 * 1e3;
|
|
47
|
+
var LEVEL_COLORS = {
|
|
48
|
+
debug: "\x1B[35m",
|
|
49
|
+
// Magenta
|
|
50
|
+
info: "\x1B[36m",
|
|
51
|
+
// Cyan
|
|
52
|
+
warn: "\x1B[33m",
|
|
53
|
+
// Yellow
|
|
54
|
+
error: "\x1B[31m",
|
|
55
|
+
// Red
|
|
56
|
+
critical: "\x1B[41m\x1B[37m",
|
|
57
|
+
// White on Red background
|
|
58
|
+
fatal: "\x1B[45m\x1B[37m"
|
|
59
|
+
// White on Magenta background
|
|
60
|
+
};
|
|
61
|
+
var RESET_COLOR = "\x1B[0m";
|
|
62
|
+
var BROWSER_STYLES = {
|
|
63
|
+
debug: "color: #a371f7; font-weight: bold",
|
|
64
|
+
info: "color: #58a6ff; font-weight: bold",
|
|
65
|
+
warn: "color: #d29922; font-weight: bold",
|
|
66
|
+
error: "color: #f85149; font-weight: bold",
|
|
67
|
+
critical: "background: #da3633; color: white; font-weight: bold; padding: 2px 6px; border-radius: 2px",
|
|
68
|
+
fatal: "background: #8957e5; color: white; font-weight: bold; padding: 2px 6px; border-radius: 2px"
|
|
69
|
+
};
|
|
70
|
+
function isBrowserEnvironment() {
|
|
71
|
+
return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
72
|
+
}
|
|
73
|
+
function getBrowserWindow() {
|
|
74
|
+
if (!isBrowserEnvironment()) {
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
const win = globalThis.window;
|
|
78
|
+
if (typeof win !== "object" || win === null) {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
return win;
|
|
82
|
+
}
|
|
83
|
+
function getBrowserStorage() {
|
|
84
|
+
if (!isBrowserEnvironment()) {
|
|
85
|
+
return void 0;
|
|
86
|
+
}
|
|
87
|
+
const storage = globalThis.localStorage;
|
|
88
|
+
if (typeof storage !== "object" || storage === null) {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
return storage;
|
|
92
|
+
}
|
|
93
|
+
function getBrowserContext() {
|
|
94
|
+
const win = getBrowserWindow();
|
|
95
|
+
if (!win) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
url: win.location?.href,
|
|
100
|
+
route: win.location?.pathname,
|
|
101
|
+
userAgent: win.navigator?.userAgent
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
var LogStack = class {
|
|
105
|
+
constructor(config) {
|
|
106
|
+
this.buffer = [];
|
|
107
|
+
this.flushTimer = null;
|
|
108
|
+
this.isFlushing = false;
|
|
109
|
+
this.isClosing = false;
|
|
110
|
+
this.globalContext = {};
|
|
111
|
+
this.isOnline = true;
|
|
112
|
+
this.offlineQueue = [];
|
|
113
|
+
this.offlineRetryTimer = null;
|
|
114
|
+
this.isBrowser = isBrowserEnvironment();
|
|
115
|
+
const detectedEnvironment = this.detectEnvironment();
|
|
116
|
+
this.config = {
|
|
117
|
+
apiKey: config.apiKey,
|
|
118
|
+
endpoint: config.endpoint || DEFAULT_ENDPOINT,
|
|
119
|
+
batchSize: config.batchSize || DEFAULT_BATCH_SIZE,
|
|
120
|
+
flushInterval: config.flushInterval || DEFAULT_FLUSH_INTERVAL,
|
|
121
|
+
maxRetries: config.maxRetries || DEFAULT_MAX_RETRIES,
|
|
122
|
+
environment: config.environment || detectedEnvironment,
|
|
123
|
+
consoleInProduction: config.consoleInProduction || false,
|
|
124
|
+
silent: config.silent || false,
|
|
125
|
+
disabled: config.disabled || false,
|
|
126
|
+
maxOfflineQueueSize: config.maxOfflineQueueSize ?? DEFAULT_MAX_OFFLINE_QUEUE,
|
|
127
|
+
captureContext: config.captureContext !== false,
|
|
128
|
+
onError: config.onError,
|
|
129
|
+
projectId: config.projectId
|
|
130
|
+
};
|
|
131
|
+
if (!this.config.disabled) {
|
|
132
|
+
this.startFlushTimer();
|
|
133
|
+
}
|
|
134
|
+
if (this.config.captureContext && this.isBrowser) {
|
|
135
|
+
this.captureDefaultContext();
|
|
136
|
+
}
|
|
137
|
+
if (this.isBrowser && !this.config.disabled) {
|
|
138
|
+
this.setupOfflineDetection();
|
|
139
|
+
this.restoreOfflineQueue();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
detectEnvironment() {
|
|
143
|
+
if (typeof process !== "undefined" && process.env) {
|
|
144
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
145
|
+
if (nodeEnv === "development" || nodeEnv === "dev") return "development";
|
|
146
|
+
if (nodeEnv === "test") return "test";
|
|
147
|
+
if (nodeEnv === "staging") return "staging";
|
|
148
|
+
}
|
|
149
|
+
return "production";
|
|
150
|
+
}
|
|
151
|
+
setupOfflineDetection() {
|
|
152
|
+
const win = getBrowserWindow();
|
|
153
|
+
if (!win) return;
|
|
154
|
+
this.isOnline = win.navigator?.onLine ?? true;
|
|
155
|
+
win.addEventListener?.("online", () => {
|
|
156
|
+
this.isOnline = true;
|
|
157
|
+
this.flushOfflineQueue().catch(() => {
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
win.addEventListener?.("offline", () => {
|
|
161
|
+
this.isOnline = false;
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async flushOfflineQueue() {
|
|
165
|
+
if (this.offlineQueue.length === 0) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const logsToSend = [...this.offlineQueue];
|
|
169
|
+
this.offlineQueue = [];
|
|
170
|
+
this.saveOfflineQueue();
|
|
171
|
+
try {
|
|
172
|
+
await this.sendLogs(logsToSend);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
this.offlineQueue = [...logsToSend, ...this.offlineQueue];
|
|
175
|
+
this.saveOfflineQueue();
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
saveOfflineQueue() {
|
|
180
|
+
if (!this.isBrowser) return;
|
|
181
|
+
const storage = getBrowserStorage();
|
|
182
|
+
if (!storage) return;
|
|
183
|
+
try {
|
|
184
|
+
const dataToStore = this.offlineQueue.map((log) => ({
|
|
185
|
+
...log,
|
|
186
|
+
timestamp: log.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
187
|
+
savedAt: (/* @__PURE__ */ new Date()).getTime()
|
|
188
|
+
}));
|
|
189
|
+
storage.setItem(OFFLINE_STORAGE_KEY, JSON.stringify(dataToStore));
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (this.config.onError && error instanceof Error) {
|
|
192
|
+
this.config.onError(error, []);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
restoreOfflineQueue() {
|
|
197
|
+
if (!this.isBrowser) return;
|
|
198
|
+
const storage = getBrowserStorage();
|
|
199
|
+
if (!storage) return;
|
|
200
|
+
try {
|
|
201
|
+
const stored = storage.getItem(OFFLINE_STORAGE_KEY);
|
|
202
|
+
if (stored) {
|
|
203
|
+
const logs = JSON.parse(stored);
|
|
204
|
+
const now = (/* @__PURE__ */ new Date()).getTime();
|
|
205
|
+
this.offlineQueue = logs.filter((log) => now - log.savedAt < OFFLINE_STORAGE_TTL).map(({ savedAt, ...log }) => log);
|
|
206
|
+
this.saveOfflineQueue();
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
storage.removeItem(OFFLINE_STORAGE_KEY);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
isProductionMode() {
|
|
213
|
+
return this.config.environment === "production" || this.config.environment === "staging";
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Whether this log should be written to the console. Always on in
|
|
217
|
+
* development/test; in production/staging only when consoleInProduction is set.
|
|
218
|
+
* `silent` disables console output entirely.
|
|
219
|
+
*/
|
|
220
|
+
shouldLogToConsole() {
|
|
221
|
+
if (this.config.silent) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
if (this.isProductionMode()) {
|
|
225
|
+
return this.config.consoleInProduction;
|
|
226
|
+
}
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Append logs to the offline queue, dropping the oldest entries past the
|
|
231
|
+
* configured cap to avoid exceeding localStorage quota.
|
|
232
|
+
*/
|
|
233
|
+
enqueueOffline(logs) {
|
|
234
|
+
this.offlineQueue.push(...logs);
|
|
235
|
+
const max = this.config.maxOfflineQueueSize;
|
|
236
|
+
if (this.offlineQueue.length > max) {
|
|
237
|
+
this.offlineQueue = this.offlineQueue.slice(-max);
|
|
238
|
+
}
|
|
239
|
+
this.saveOfflineQueue();
|
|
240
|
+
}
|
|
241
|
+
captureDefaultContext() {
|
|
242
|
+
this.globalContext = getBrowserContext();
|
|
243
|
+
}
|
|
244
|
+
formatTimestamp(timestamp) {
|
|
245
|
+
const date = new Date(timestamp);
|
|
246
|
+
return date.toISOString().replace("T", " ").slice(0, 23);
|
|
247
|
+
}
|
|
248
|
+
logToConsole(entry) {
|
|
249
|
+
const timestamp = this.formatTimestamp(
|
|
250
|
+
entry.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
251
|
+
);
|
|
252
|
+
const level = entry.level.toUpperCase().padEnd(8);
|
|
253
|
+
if (this.isBrowser) {
|
|
254
|
+
const style = BROWSER_STYLES[entry.level];
|
|
255
|
+
const contextStr = entry.context?.url ? ` [${entry.context.url}]` : "";
|
|
256
|
+
console.log(
|
|
257
|
+
`%c${level}%c ${timestamp}${contextStr} - ${entry.message}`,
|
|
258
|
+
style,
|
|
259
|
+
"color: inherit",
|
|
260
|
+
entry.metadata || ""
|
|
261
|
+
);
|
|
262
|
+
} else {
|
|
263
|
+
const color = LEVEL_COLORS[entry.level];
|
|
264
|
+
const contextStr = entry.context?.url ? ` [${entry.context.url}]` : entry.context?.route ? ` [${entry.context.route}]` : "";
|
|
265
|
+
const metaStr = entry.metadata ? ` ${JSON.stringify(entry.metadata)}` : "";
|
|
266
|
+
console.log(
|
|
267
|
+
`${color}${level}${RESET_COLOR} ${timestamp}${contextStr} - ${entry.message}${metaStr}`
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
startFlushTimer() {
|
|
272
|
+
this.flushTimer = setInterval(() => {
|
|
273
|
+
if (this.buffer.length > 0 && !this.isFlushing) {
|
|
274
|
+
this.flush().catch(() => {
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}, this.config.flushInterval);
|
|
278
|
+
}
|
|
279
|
+
stopFlushTimer() {
|
|
280
|
+
if (this.flushTimer) {
|
|
281
|
+
clearInterval(this.flushTimer);
|
|
282
|
+
this.flushTimer = null;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
setContext(context) {
|
|
286
|
+
this.globalContext = { ...this.globalContext, ...context };
|
|
287
|
+
}
|
|
288
|
+
clearContext() {
|
|
289
|
+
this.globalContext = {};
|
|
290
|
+
if (this.config.captureContext && this.isBrowser) {
|
|
291
|
+
this.captureDefaultContext();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
debug(message, metadata) {
|
|
295
|
+
this.log({ level: "debug", message, metadata });
|
|
296
|
+
}
|
|
297
|
+
info(message, metadata) {
|
|
298
|
+
this.log({ level: "info", message, metadata });
|
|
299
|
+
}
|
|
300
|
+
warn(message, metadata) {
|
|
301
|
+
this.log({ level: "warn", message, metadata });
|
|
302
|
+
}
|
|
303
|
+
error(message, metadata) {
|
|
304
|
+
this.log({ level: "error", message, metadata });
|
|
305
|
+
}
|
|
306
|
+
critical(message, metadata) {
|
|
307
|
+
this.log({ level: "critical", message, metadata });
|
|
308
|
+
}
|
|
309
|
+
fatal(message, metadata) {
|
|
310
|
+
this.log({ level: "fatal", message, metadata });
|
|
311
|
+
}
|
|
312
|
+
log(entry) {
|
|
313
|
+
if (this.isClosing) {
|
|
314
|
+
console.warn("Logstack: Cannot log after close() has been called");
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
318
|
+
const context = {
|
|
319
|
+
...this.globalContext,
|
|
320
|
+
...entry.context
|
|
321
|
+
};
|
|
322
|
+
const logEntry = {
|
|
323
|
+
...entry,
|
|
324
|
+
timestamp,
|
|
325
|
+
context: Object.keys(context).length > 0 ? context : void 0
|
|
326
|
+
};
|
|
327
|
+
if (this.shouldLogToConsole()) {
|
|
328
|
+
this.logToConsole(logEntry);
|
|
329
|
+
}
|
|
330
|
+
if (this.config.disabled) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
if (!this.isOnline) {
|
|
334
|
+
this.enqueueOffline([logEntry]);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
this.buffer.push(logEntry);
|
|
338
|
+
if (this.buffer.length >= this.config.batchSize && !this.isFlushing) {
|
|
339
|
+
this.flush().catch(() => {
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
async flush() {
|
|
344
|
+
if (this.config.disabled) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (this.buffer.length === 0 || this.isFlushing) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
this.isFlushing = true;
|
|
351
|
+
const logsToSend = [...this.buffer];
|
|
352
|
+
this.buffer = [];
|
|
353
|
+
try {
|
|
354
|
+
await this.sendLogs(logsToSend);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
this.buffer = [...logsToSend, ...this.buffer];
|
|
357
|
+
if (this.config.onError && error instanceof Error) {
|
|
358
|
+
this.config.onError(error, logsToSend);
|
|
359
|
+
}
|
|
360
|
+
throw error;
|
|
361
|
+
} finally {
|
|
362
|
+
this.isFlushing = false;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
async sendLogs(logs, attempt = 1) {
|
|
366
|
+
try {
|
|
367
|
+
const response = await fetch(`${this.config.endpoint}/v1/logs`, {
|
|
368
|
+
method: "POST",
|
|
369
|
+
headers: {
|
|
370
|
+
"Content-Type": "application/json",
|
|
371
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
372
|
+
},
|
|
373
|
+
body: JSON.stringify({ logs })
|
|
374
|
+
});
|
|
375
|
+
if (!response.ok) {
|
|
376
|
+
if (response.status >= 500 && attempt < this.config.maxRetries) {
|
|
377
|
+
const delay = RETRY_BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 100;
|
|
378
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
379
|
+
return this.sendLogs(logs, attempt + 1);
|
|
380
|
+
}
|
|
381
|
+
let errorData = {
|
|
382
|
+
code: "SEND_ERROR",
|
|
383
|
+
message: `Failed to send logs: ${response.status}`
|
|
384
|
+
};
|
|
385
|
+
try {
|
|
386
|
+
const data = await response.json();
|
|
387
|
+
if (data && data.code && data.message) {
|
|
388
|
+
errorData = data;
|
|
389
|
+
}
|
|
390
|
+
} catch {
|
|
391
|
+
}
|
|
392
|
+
throw new LogStackError(
|
|
393
|
+
errorData.code,
|
|
394
|
+
errorData.message,
|
|
395
|
+
response.status
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (this.isOnline && error instanceof Error) {
|
|
400
|
+
this.enqueueOffline(logs);
|
|
401
|
+
this.isOnline = false;
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
async getLogs(options = {}) {
|
|
408
|
+
if (!this.config.projectId) {
|
|
409
|
+
throw new LogStackError(
|
|
410
|
+
"MISSING_PROJECT_ID",
|
|
411
|
+
"projectId is required in config for query operations",
|
|
412
|
+
400
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const params = new URLSearchParams();
|
|
416
|
+
params.set("projectId", this.config.projectId);
|
|
417
|
+
if (options.level) params.set("level", options.level);
|
|
418
|
+
if (options.search) params.set("search", options.search);
|
|
419
|
+
if (options.startTime) params.set("startTime", options.startTime);
|
|
420
|
+
if (options.endTime) params.set("endTime", options.endTime);
|
|
421
|
+
if (options.offset !== void 0)
|
|
422
|
+
params.set("offset", String(options.offset));
|
|
423
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
424
|
+
const response = await this.fetchWithRetry(
|
|
425
|
+
`${this.config.endpoint}/v1/logs?${params.toString()}`,
|
|
426
|
+
{ method: "GET" }
|
|
427
|
+
);
|
|
428
|
+
return await response.json();
|
|
429
|
+
}
|
|
430
|
+
async getLogById(logId) {
|
|
431
|
+
if (!this.config.projectId) {
|
|
432
|
+
throw new LogStackError(
|
|
433
|
+
"MISSING_PROJECT_ID",
|
|
434
|
+
"projectId is required in config for query operations",
|
|
435
|
+
400
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
const params = new URLSearchParams();
|
|
439
|
+
params.set("projectId", this.config.projectId);
|
|
440
|
+
const response = await this.fetchWithRetry(
|
|
441
|
+
`${this.config.endpoint}/v1/logs/${logId}?${params.toString()}`,
|
|
442
|
+
{ method: "GET" }
|
|
443
|
+
);
|
|
444
|
+
return await response.json();
|
|
445
|
+
}
|
|
446
|
+
async fetchWithRetry(url, options, attempt = 1) {
|
|
447
|
+
const response = await fetch(url, {
|
|
448
|
+
...options,
|
|
449
|
+
headers: {
|
|
450
|
+
"Content-Type": "application/json",
|
|
451
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
452
|
+
...options.headers
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
if (!response.ok) {
|
|
456
|
+
if (response.status >= 500 && attempt < this.config.maxRetries) {
|
|
457
|
+
const delay = RETRY_BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 100;
|
|
458
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
459
|
+
return this.fetchWithRetry(url, options, attempt + 1);
|
|
460
|
+
}
|
|
461
|
+
let errorData = {
|
|
462
|
+
code: "REQUEST_ERROR",
|
|
463
|
+
message: `Request failed: ${response.status}`
|
|
464
|
+
};
|
|
465
|
+
try {
|
|
466
|
+
const data = await response.json();
|
|
467
|
+
if (data && data.code && data.message) {
|
|
468
|
+
errorData = data;
|
|
469
|
+
}
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
throw new LogStackError(
|
|
473
|
+
errorData.code,
|
|
474
|
+
errorData.message,
|
|
475
|
+
response.status
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
return response;
|
|
479
|
+
}
|
|
480
|
+
async close() {
|
|
481
|
+
this.isClosing = true;
|
|
482
|
+
this.stopFlushTimer();
|
|
483
|
+
if (this.offlineRetryTimer) {
|
|
484
|
+
clearInterval(this.offlineRetryTimer);
|
|
485
|
+
this.offlineRetryTimer = null;
|
|
486
|
+
}
|
|
487
|
+
if (!this.config.disabled && this.buffer.length > 0) {
|
|
488
|
+
await this.flush();
|
|
489
|
+
}
|
|
490
|
+
if (this.isOnline && this.offlineQueue.length > 0) {
|
|
491
|
+
try {
|
|
492
|
+
await this.flushOfflineQueue();
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
function createLogStack(config) {
|
|
499
|
+
return new LogStack(config);
|
|
500
|
+
}
|
|
501
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
502
|
+
0 && (module.exports = {
|
|
503
|
+
LogStackError,
|
|
504
|
+
createLogStack
|
|
505
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var LogStackError = class extends Error {
|
|
3
|
+
constructor(code, message, status) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.name = "LogStackError";
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/index.ts
|
|
12
|
+
var DEFAULT_ENDPOINT = "https://api.logstack.tech";
|
|
13
|
+
var DEFAULT_BATCH_SIZE = 100;
|
|
14
|
+
var DEFAULT_FLUSH_INTERVAL = 5e3;
|
|
15
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
16
|
+
var DEFAULT_MAX_OFFLINE_QUEUE = 1e3;
|
|
17
|
+
var RETRY_BASE_DELAY = 1e3;
|
|
18
|
+
var OFFLINE_STORAGE_KEY = "logstack_offline_queue";
|
|
19
|
+
var OFFLINE_STORAGE_TTL = 24 * 60 * 60 * 1e3;
|
|
20
|
+
var LEVEL_COLORS = {
|
|
21
|
+
debug: "\x1B[35m",
|
|
22
|
+
// Magenta
|
|
23
|
+
info: "\x1B[36m",
|
|
24
|
+
// Cyan
|
|
25
|
+
warn: "\x1B[33m",
|
|
26
|
+
// Yellow
|
|
27
|
+
error: "\x1B[31m",
|
|
28
|
+
// Red
|
|
29
|
+
critical: "\x1B[41m\x1B[37m",
|
|
30
|
+
// White on Red background
|
|
31
|
+
fatal: "\x1B[45m\x1B[37m"
|
|
32
|
+
// White on Magenta background
|
|
33
|
+
};
|
|
34
|
+
var RESET_COLOR = "\x1B[0m";
|
|
35
|
+
var BROWSER_STYLES = {
|
|
36
|
+
debug: "color: #a371f7; font-weight: bold",
|
|
37
|
+
info: "color: #58a6ff; font-weight: bold",
|
|
38
|
+
warn: "color: #d29922; font-weight: bold",
|
|
39
|
+
error: "color: #f85149; font-weight: bold",
|
|
40
|
+
critical: "background: #da3633; color: white; font-weight: bold; padding: 2px 6px; border-radius: 2px",
|
|
41
|
+
fatal: "background: #8957e5; color: white; font-weight: bold; padding: 2px 6px; border-radius: 2px"
|
|
42
|
+
};
|
|
43
|
+
function isBrowserEnvironment() {
|
|
44
|
+
return typeof globalThis !== "undefined" && typeof globalThis.window !== "undefined";
|
|
45
|
+
}
|
|
46
|
+
function getBrowserWindow() {
|
|
47
|
+
if (!isBrowserEnvironment()) {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
const win = globalThis.window;
|
|
51
|
+
if (typeof win !== "object" || win === null) {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
return win;
|
|
55
|
+
}
|
|
56
|
+
function getBrowserStorage() {
|
|
57
|
+
if (!isBrowserEnvironment()) {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
const storage = globalThis.localStorage;
|
|
61
|
+
if (typeof storage !== "object" || storage === null) {
|
|
62
|
+
return void 0;
|
|
63
|
+
}
|
|
64
|
+
return storage;
|
|
65
|
+
}
|
|
66
|
+
function getBrowserContext() {
|
|
67
|
+
const win = getBrowserWindow();
|
|
68
|
+
if (!win) {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
url: win.location?.href,
|
|
73
|
+
route: win.location?.pathname,
|
|
74
|
+
userAgent: win.navigator?.userAgent
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
var LogStack = class {
|
|
78
|
+
constructor(config) {
|
|
79
|
+
this.buffer = [];
|
|
80
|
+
this.flushTimer = null;
|
|
81
|
+
this.isFlushing = false;
|
|
82
|
+
this.isClosing = false;
|
|
83
|
+
this.globalContext = {};
|
|
84
|
+
this.isOnline = true;
|
|
85
|
+
this.offlineQueue = [];
|
|
86
|
+
this.offlineRetryTimer = null;
|
|
87
|
+
this.isBrowser = isBrowserEnvironment();
|
|
88
|
+
const detectedEnvironment = this.detectEnvironment();
|
|
89
|
+
this.config = {
|
|
90
|
+
apiKey: config.apiKey,
|
|
91
|
+
endpoint: config.endpoint || DEFAULT_ENDPOINT,
|
|
92
|
+
batchSize: config.batchSize || DEFAULT_BATCH_SIZE,
|
|
93
|
+
flushInterval: config.flushInterval || DEFAULT_FLUSH_INTERVAL,
|
|
94
|
+
maxRetries: config.maxRetries || DEFAULT_MAX_RETRIES,
|
|
95
|
+
environment: config.environment || detectedEnvironment,
|
|
96
|
+
consoleInProduction: config.consoleInProduction || false,
|
|
97
|
+
silent: config.silent || false,
|
|
98
|
+
disabled: config.disabled || false,
|
|
99
|
+
maxOfflineQueueSize: config.maxOfflineQueueSize ?? DEFAULT_MAX_OFFLINE_QUEUE,
|
|
100
|
+
captureContext: config.captureContext !== false,
|
|
101
|
+
onError: config.onError,
|
|
102
|
+
projectId: config.projectId
|
|
103
|
+
};
|
|
104
|
+
if (!this.config.disabled) {
|
|
105
|
+
this.startFlushTimer();
|
|
106
|
+
}
|
|
107
|
+
if (this.config.captureContext && this.isBrowser) {
|
|
108
|
+
this.captureDefaultContext();
|
|
109
|
+
}
|
|
110
|
+
if (this.isBrowser && !this.config.disabled) {
|
|
111
|
+
this.setupOfflineDetection();
|
|
112
|
+
this.restoreOfflineQueue();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
detectEnvironment() {
|
|
116
|
+
if (typeof process !== "undefined" && process.env) {
|
|
117
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
118
|
+
if (nodeEnv === "development" || nodeEnv === "dev") return "development";
|
|
119
|
+
if (nodeEnv === "test") return "test";
|
|
120
|
+
if (nodeEnv === "staging") return "staging";
|
|
121
|
+
}
|
|
122
|
+
return "production";
|
|
123
|
+
}
|
|
124
|
+
setupOfflineDetection() {
|
|
125
|
+
const win = getBrowserWindow();
|
|
126
|
+
if (!win) return;
|
|
127
|
+
this.isOnline = win.navigator?.onLine ?? true;
|
|
128
|
+
win.addEventListener?.("online", () => {
|
|
129
|
+
this.isOnline = true;
|
|
130
|
+
this.flushOfflineQueue().catch(() => {
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
win.addEventListener?.("offline", () => {
|
|
134
|
+
this.isOnline = false;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async flushOfflineQueue() {
|
|
138
|
+
if (this.offlineQueue.length === 0) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const logsToSend = [...this.offlineQueue];
|
|
142
|
+
this.offlineQueue = [];
|
|
143
|
+
this.saveOfflineQueue();
|
|
144
|
+
try {
|
|
145
|
+
await this.sendLogs(logsToSend);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
this.offlineQueue = [...logsToSend, ...this.offlineQueue];
|
|
148
|
+
this.saveOfflineQueue();
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
saveOfflineQueue() {
|
|
153
|
+
if (!this.isBrowser) return;
|
|
154
|
+
const storage = getBrowserStorage();
|
|
155
|
+
if (!storage) return;
|
|
156
|
+
try {
|
|
157
|
+
const dataToStore = this.offlineQueue.map((log) => ({
|
|
158
|
+
...log,
|
|
159
|
+
timestamp: log.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
160
|
+
savedAt: (/* @__PURE__ */ new Date()).getTime()
|
|
161
|
+
}));
|
|
162
|
+
storage.setItem(OFFLINE_STORAGE_KEY, JSON.stringify(dataToStore));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (this.config.onError && error instanceof Error) {
|
|
165
|
+
this.config.onError(error, []);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
restoreOfflineQueue() {
|
|
170
|
+
if (!this.isBrowser) return;
|
|
171
|
+
const storage = getBrowserStorage();
|
|
172
|
+
if (!storage) return;
|
|
173
|
+
try {
|
|
174
|
+
const stored = storage.getItem(OFFLINE_STORAGE_KEY);
|
|
175
|
+
if (stored) {
|
|
176
|
+
const logs = JSON.parse(stored);
|
|
177
|
+
const now = (/* @__PURE__ */ new Date()).getTime();
|
|
178
|
+
this.offlineQueue = logs.filter((log) => now - log.savedAt < OFFLINE_STORAGE_TTL).map(({ savedAt, ...log }) => log);
|
|
179
|
+
this.saveOfflineQueue();
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
storage.removeItem(OFFLINE_STORAGE_KEY);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
isProductionMode() {
|
|
186
|
+
return this.config.environment === "production" || this.config.environment === "staging";
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Whether this log should be written to the console. Always on in
|
|
190
|
+
* development/test; in production/staging only when consoleInProduction is set.
|
|
191
|
+
* `silent` disables console output entirely.
|
|
192
|
+
*/
|
|
193
|
+
shouldLogToConsole() {
|
|
194
|
+
if (this.config.silent) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
if (this.isProductionMode()) {
|
|
198
|
+
return this.config.consoleInProduction;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Append logs to the offline queue, dropping the oldest entries past the
|
|
204
|
+
* configured cap to avoid exceeding localStorage quota.
|
|
205
|
+
*/
|
|
206
|
+
enqueueOffline(logs) {
|
|
207
|
+
this.offlineQueue.push(...logs);
|
|
208
|
+
const max = this.config.maxOfflineQueueSize;
|
|
209
|
+
if (this.offlineQueue.length > max) {
|
|
210
|
+
this.offlineQueue = this.offlineQueue.slice(-max);
|
|
211
|
+
}
|
|
212
|
+
this.saveOfflineQueue();
|
|
213
|
+
}
|
|
214
|
+
captureDefaultContext() {
|
|
215
|
+
this.globalContext = getBrowserContext();
|
|
216
|
+
}
|
|
217
|
+
formatTimestamp(timestamp) {
|
|
218
|
+
const date = new Date(timestamp);
|
|
219
|
+
return date.toISOString().replace("T", " ").slice(0, 23);
|
|
220
|
+
}
|
|
221
|
+
logToConsole(entry) {
|
|
222
|
+
const timestamp = this.formatTimestamp(
|
|
223
|
+
entry.timestamp || (/* @__PURE__ */ new Date()).toISOString()
|
|
224
|
+
);
|
|
225
|
+
const level = entry.level.toUpperCase().padEnd(8);
|
|
226
|
+
if (this.isBrowser) {
|
|
227
|
+
const style = BROWSER_STYLES[entry.level];
|
|
228
|
+
const contextStr = entry.context?.url ? ` [${entry.context.url}]` : "";
|
|
229
|
+
console.log(
|
|
230
|
+
`%c${level}%c ${timestamp}${contextStr} - ${entry.message}`,
|
|
231
|
+
style,
|
|
232
|
+
"color: inherit",
|
|
233
|
+
entry.metadata || ""
|
|
234
|
+
);
|
|
235
|
+
} else {
|
|
236
|
+
const color = LEVEL_COLORS[entry.level];
|
|
237
|
+
const contextStr = entry.context?.url ? ` [${entry.context.url}]` : entry.context?.route ? ` [${entry.context.route}]` : "";
|
|
238
|
+
const metaStr = entry.metadata ? ` ${JSON.stringify(entry.metadata)}` : "";
|
|
239
|
+
console.log(
|
|
240
|
+
`${color}${level}${RESET_COLOR} ${timestamp}${contextStr} - ${entry.message}${metaStr}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
startFlushTimer() {
|
|
245
|
+
this.flushTimer = setInterval(() => {
|
|
246
|
+
if (this.buffer.length > 0 && !this.isFlushing) {
|
|
247
|
+
this.flush().catch(() => {
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}, this.config.flushInterval);
|
|
251
|
+
}
|
|
252
|
+
stopFlushTimer() {
|
|
253
|
+
if (this.flushTimer) {
|
|
254
|
+
clearInterval(this.flushTimer);
|
|
255
|
+
this.flushTimer = null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
setContext(context) {
|
|
259
|
+
this.globalContext = { ...this.globalContext, ...context };
|
|
260
|
+
}
|
|
261
|
+
clearContext() {
|
|
262
|
+
this.globalContext = {};
|
|
263
|
+
if (this.config.captureContext && this.isBrowser) {
|
|
264
|
+
this.captureDefaultContext();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
debug(message, metadata) {
|
|
268
|
+
this.log({ level: "debug", message, metadata });
|
|
269
|
+
}
|
|
270
|
+
info(message, metadata) {
|
|
271
|
+
this.log({ level: "info", message, metadata });
|
|
272
|
+
}
|
|
273
|
+
warn(message, metadata) {
|
|
274
|
+
this.log({ level: "warn", message, metadata });
|
|
275
|
+
}
|
|
276
|
+
error(message, metadata) {
|
|
277
|
+
this.log({ level: "error", message, metadata });
|
|
278
|
+
}
|
|
279
|
+
critical(message, metadata) {
|
|
280
|
+
this.log({ level: "critical", message, metadata });
|
|
281
|
+
}
|
|
282
|
+
fatal(message, metadata) {
|
|
283
|
+
this.log({ level: "fatal", message, metadata });
|
|
284
|
+
}
|
|
285
|
+
log(entry) {
|
|
286
|
+
if (this.isClosing) {
|
|
287
|
+
console.warn("Logstack: Cannot log after close() has been called");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const timestamp = entry.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
291
|
+
const context = {
|
|
292
|
+
...this.globalContext,
|
|
293
|
+
...entry.context
|
|
294
|
+
};
|
|
295
|
+
const logEntry = {
|
|
296
|
+
...entry,
|
|
297
|
+
timestamp,
|
|
298
|
+
context: Object.keys(context).length > 0 ? context : void 0
|
|
299
|
+
};
|
|
300
|
+
if (this.shouldLogToConsole()) {
|
|
301
|
+
this.logToConsole(logEntry);
|
|
302
|
+
}
|
|
303
|
+
if (this.config.disabled) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (!this.isOnline) {
|
|
307
|
+
this.enqueueOffline([logEntry]);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
this.buffer.push(logEntry);
|
|
311
|
+
if (this.buffer.length >= this.config.batchSize && !this.isFlushing) {
|
|
312
|
+
this.flush().catch(() => {
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
async flush() {
|
|
317
|
+
if (this.config.disabled) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (this.buffer.length === 0 || this.isFlushing) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
this.isFlushing = true;
|
|
324
|
+
const logsToSend = [...this.buffer];
|
|
325
|
+
this.buffer = [];
|
|
326
|
+
try {
|
|
327
|
+
await this.sendLogs(logsToSend);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.buffer = [...logsToSend, ...this.buffer];
|
|
330
|
+
if (this.config.onError && error instanceof Error) {
|
|
331
|
+
this.config.onError(error, logsToSend);
|
|
332
|
+
}
|
|
333
|
+
throw error;
|
|
334
|
+
} finally {
|
|
335
|
+
this.isFlushing = false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async sendLogs(logs, attempt = 1) {
|
|
339
|
+
try {
|
|
340
|
+
const response = await fetch(`${this.config.endpoint}/v1/logs`, {
|
|
341
|
+
method: "POST",
|
|
342
|
+
headers: {
|
|
343
|
+
"Content-Type": "application/json",
|
|
344
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
345
|
+
},
|
|
346
|
+
body: JSON.stringify({ logs })
|
|
347
|
+
});
|
|
348
|
+
if (!response.ok) {
|
|
349
|
+
if (response.status >= 500 && attempt < this.config.maxRetries) {
|
|
350
|
+
const delay = RETRY_BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 100;
|
|
351
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
352
|
+
return this.sendLogs(logs, attempt + 1);
|
|
353
|
+
}
|
|
354
|
+
let errorData = {
|
|
355
|
+
code: "SEND_ERROR",
|
|
356
|
+
message: `Failed to send logs: ${response.status}`
|
|
357
|
+
};
|
|
358
|
+
try {
|
|
359
|
+
const data = await response.json();
|
|
360
|
+
if (data && data.code && data.message) {
|
|
361
|
+
errorData = data;
|
|
362
|
+
}
|
|
363
|
+
} catch {
|
|
364
|
+
}
|
|
365
|
+
throw new LogStackError(
|
|
366
|
+
errorData.code,
|
|
367
|
+
errorData.message,
|
|
368
|
+
response.status
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
} catch (error) {
|
|
372
|
+
if (this.isOnline && error instanceof Error) {
|
|
373
|
+
this.enqueueOffline(logs);
|
|
374
|
+
this.isOnline = false;
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
throw error;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async getLogs(options = {}) {
|
|
381
|
+
if (!this.config.projectId) {
|
|
382
|
+
throw new LogStackError(
|
|
383
|
+
"MISSING_PROJECT_ID",
|
|
384
|
+
"projectId is required in config for query operations",
|
|
385
|
+
400
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const params = new URLSearchParams();
|
|
389
|
+
params.set("projectId", this.config.projectId);
|
|
390
|
+
if (options.level) params.set("level", options.level);
|
|
391
|
+
if (options.search) params.set("search", options.search);
|
|
392
|
+
if (options.startTime) params.set("startTime", options.startTime);
|
|
393
|
+
if (options.endTime) params.set("endTime", options.endTime);
|
|
394
|
+
if (options.offset !== void 0)
|
|
395
|
+
params.set("offset", String(options.offset));
|
|
396
|
+
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
397
|
+
const response = await this.fetchWithRetry(
|
|
398
|
+
`${this.config.endpoint}/v1/logs?${params.toString()}`,
|
|
399
|
+
{ method: "GET" }
|
|
400
|
+
);
|
|
401
|
+
return await response.json();
|
|
402
|
+
}
|
|
403
|
+
async getLogById(logId) {
|
|
404
|
+
if (!this.config.projectId) {
|
|
405
|
+
throw new LogStackError(
|
|
406
|
+
"MISSING_PROJECT_ID",
|
|
407
|
+
"projectId is required in config for query operations",
|
|
408
|
+
400
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
const params = new URLSearchParams();
|
|
412
|
+
params.set("projectId", this.config.projectId);
|
|
413
|
+
const response = await this.fetchWithRetry(
|
|
414
|
+
`${this.config.endpoint}/v1/logs/${logId}?${params.toString()}`,
|
|
415
|
+
{ method: "GET" }
|
|
416
|
+
);
|
|
417
|
+
return await response.json();
|
|
418
|
+
}
|
|
419
|
+
async fetchWithRetry(url, options, attempt = 1) {
|
|
420
|
+
const response = await fetch(url, {
|
|
421
|
+
...options,
|
|
422
|
+
headers: {
|
|
423
|
+
"Content-Type": "application/json",
|
|
424
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
425
|
+
...options.headers
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
if (!response.ok) {
|
|
429
|
+
if (response.status >= 500 && attempt < this.config.maxRetries) {
|
|
430
|
+
const delay = RETRY_BASE_DELAY * Math.pow(2, attempt - 1) + Math.random() * 100;
|
|
431
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
432
|
+
return this.fetchWithRetry(url, options, attempt + 1);
|
|
433
|
+
}
|
|
434
|
+
let errorData = {
|
|
435
|
+
code: "REQUEST_ERROR",
|
|
436
|
+
message: `Request failed: ${response.status}`
|
|
437
|
+
};
|
|
438
|
+
try {
|
|
439
|
+
const data = await response.json();
|
|
440
|
+
if (data && data.code && data.message) {
|
|
441
|
+
errorData = data;
|
|
442
|
+
}
|
|
443
|
+
} catch {
|
|
444
|
+
}
|
|
445
|
+
throw new LogStackError(
|
|
446
|
+
errorData.code,
|
|
447
|
+
errorData.message,
|
|
448
|
+
response.status
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
return response;
|
|
452
|
+
}
|
|
453
|
+
async close() {
|
|
454
|
+
this.isClosing = true;
|
|
455
|
+
this.stopFlushTimer();
|
|
456
|
+
if (this.offlineRetryTimer) {
|
|
457
|
+
clearInterval(this.offlineRetryTimer);
|
|
458
|
+
this.offlineRetryTimer = null;
|
|
459
|
+
}
|
|
460
|
+
if (!this.config.disabled && this.buffer.length > 0) {
|
|
461
|
+
await this.flush();
|
|
462
|
+
}
|
|
463
|
+
if (this.isOnline && this.offlineQueue.length > 0) {
|
|
464
|
+
try {
|
|
465
|
+
await this.flushOfflineQueue();
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
function createLogStack(config) {
|
|
472
|
+
return new LogStack(config);
|
|
473
|
+
}
|
|
474
|
+
export {
|
|
475
|
+
LogStackError,
|
|
476
|
+
createLogStack
|
|
477
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "logstack-js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Logstack JavaScript/TypeScript SDK for log ingestion and querying",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
22
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
23
|
+
"lint": "eslint src --ext .ts",
|
|
24
|
+
"test": "vitest",
|
|
25
|
+
"prepublishOnly": "pnpm build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"logstack",
|
|
29
|
+
"logging",
|
|
30
|
+
"logs",
|
|
31
|
+
"monitoring",
|
|
32
|
+
"observability"
|
|
33
|
+
],
|
|
34
|
+
"author": "Mosesedem",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"homepage": "https://github.com/Mosesedem/logstack/tree/main/packages/logstack-js#readme",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/Mosesedem/logstack.git",
|
|
40
|
+
"directory": "packages/logstack-js"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/Mosesedem/logstack/issues"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^20.10.0",
|
|
50
|
+
"eslint": "^8.55.0",
|
|
51
|
+
"tsup": "^8.0.1",
|
|
52
|
+
"typescript": "^5.3.3",
|
|
53
|
+
"vitest": "^1.1.0"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=16"
|
|
57
|
+
}
|
|
58
|
+
}
|