@promise-inc/devlog 0.1.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/README.md +55 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.js +43 -0
- package/dist/env.d.ts +3 -0
- package/dist/env.js +31 -0
- package/dist/formatter.d.ts +2 -0
- package/dist/formatter.js +96 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/log.d.ts +1 -0
- package/dist/log.js +2 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +35 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.js +7 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<img src="./assets/logo.svg" alt="devlog" height="36" />
|
|
2
|
+
|
|
3
|
+
Simple logger with automatic context (file + line). Zero dependencies.
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<img src="./assets/output.svg" alt="devlog output" width="680" />
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @promise-inc/devlog
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { log } from "@promise-inc/devlog";
|
|
19
|
+
|
|
20
|
+
log.info("User created", { userId: 42 });
|
|
21
|
+
log.warn("Deprecated method");
|
|
22
|
+
log.error("Failed to save", err);
|
|
23
|
+
log.success("Migration complete");
|
|
24
|
+
log.debug("Cache hit", { key: "users:1" });
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Custom instance
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { createLogger } from "@promise-inc/devlog";
|
|
31
|
+
|
|
32
|
+
const logger = createLogger({
|
|
33
|
+
enabled: true,
|
|
34
|
+
debugEnabled: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
logger.info("Custom logger");
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Environment variables
|
|
41
|
+
|
|
42
|
+
| Variable | Effect |
|
|
43
|
+
|----------|--------|
|
|
44
|
+
| `DEVLOG_ENABLED=false` | Disables all logs |
|
|
45
|
+
| `DEVLOG_ENABLED=true` | Enables all logs including debug |
|
|
46
|
+
| `NODE_ENV=production` | Disables debug level (unless `DEVLOG_ENABLED=true`) |
|
|
47
|
+
|
|
48
|
+
## Output
|
|
49
|
+
|
|
50
|
+
- **Node.js** — Colored output with ANSI escape codes
|
|
51
|
+
- **Browser** — `console.groupCollapsed` with `%c` styling. Data nested inside collapsible groups
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
Developed by [Promise Inc.](https://promise.codes)
|
package/dist/context.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const NODE_STACK_REGEX = /at\s+.*\((.+):(\d+):(\d+)\)/;
|
|
2
|
+
const NODE_STACK_REGEX_NO_PARENS = /at\s+(.+):(\d+):(\d+)/;
|
|
3
|
+
const BROWSER_STACK_REGEX = /(?:@)(.+):(\d+):(\d+)/;
|
|
4
|
+
function parseLine(line) {
|
|
5
|
+
const nodeMatch = line.match(NODE_STACK_REGEX);
|
|
6
|
+
if (nodeMatch) {
|
|
7
|
+
return {
|
|
8
|
+
file: nodeMatch[1],
|
|
9
|
+
line: Number(nodeMatch[2]),
|
|
10
|
+
column: Number(nodeMatch[3]),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const nodeMatchNoParens = line.match(NODE_STACK_REGEX_NO_PARENS);
|
|
14
|
+
if (nodeMatchNoParens) {
|
|
15
|
+
return {
|
|
16
|
+
file: nodeMatchNoParens[1],
|
|
17
|
+
line: Number(nodeMatchNoParens[2]),
|
|
18
|
+
column: Number(nodeMatchNoParens[3]),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const browserMatch = line.match(BROWSER_STACK_REGEX);
|
|
22
|
+
if (browserMatch) {
|
|
23
|
+
return {
|
|
24
|
+
file: browserMatch[1],
|
|
25
|
+
line: Number(browserMatch[2]),
|
|
26
|
+
column: Number(browserMatch[3]),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
export function getCallerInfo() {
|
|
32
|
+
const stack = new Error().stack;
|
|
33
|
+
if (!stack)
|
|
34
|
+
return null;
|
|
35
|
+
const lines = stack.split("\n");
|
|
36
|
+
// Skip: Error, getCallerInfo, logMethod, log.level → caller is at index 4+
|
|
37
|
+
for (let i = 4; i < lines.length; i++) {
|
|
38
|
+
const info = parseLine(lines[i]);
|
|
39
|
+
if (info)
|
|
40
|
+
return info;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
package/dist/env.d.ts
ADDED
package/dist/env.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function isBrowser() {
|
|
2
|
+
return typeof window !== "undefined";
|
|
3
|
+
}
|
|
4
|
+
function getEnv(key) {
|
|
5
|
+
if (isBrowser()) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
return process.env[key];
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function isEnabled() {
|
|
16
|
+
const value = getEnv("DEVLOG_ENABLED");
|
|
17
|
+
if (value === "false")
|
|
18
|
+
return false;
|
|
19
|
+
if (value === "true")
|
|
20
|
+
return true;
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
export function isDebugEnabled() {
|
|
24
|
+
const value = getEnv("DEVLOG_ENABLED");
|
|
25
|
+
if (value === "true")
|
|
26
|
+
return true;
|
|
27
|
+
if (value === "false")
|
|
28
|
+
return false;
|
|
29
|
+
const nodeEnv = getEnv("NODE_ENV");
|
|
30
|
+
return nodeEnv !== "production";
|
|
31
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { isBrowser } from "./env.js";
|
|
2
|
+
const ANSI = {
|
|
3
|
+
reset: "\x1b[0m",
|
|
4
|
+
dim: "\x1b[2m",
|
|
5
|
+
bold: "\x1b[1m",
|
|
6
|
+
red: "\x1b[31m",
|
|
7
|
+
yellow: "\x1b[33m",
|
|
8
|
+
blue: "\x1b[34m",
|
|
9
|
+
green: "\x1b[32m",
|
|
10
|
+
cyan: "\x1b[36m",
|
|
11
|
+
gray: "\x1b[90m",
|
|
12
|
+
};
|
|
13
|
+
const LEVEL_COLORS = {
|
|
14
|
+
debug: ANSI.gray,
|
|
15
|
+
info: ANSI.blue,
|
|
16
|
+
success: ANSI.green,
|
|
17
|
+
warn: ANSI.yellow,
|
|
18
|
+
error: ANSI.red,
|
|
19
|
+
};
|
|
20
|
+
const LEVEL_LABELS = {
|
|
21
|
+
debug: "DEBUG",
|
|
22
|
+
info: "INFO",
|
|
23
|
+
success: "OK",
|
|
24
|
+
warn: "WARN",
|
|
25
|
+
error: "ERROR",
|
|
26
|
+
};
|
|
27
|
+
const BROWSER_LEVEL_STYLES = {
|
|
28
|
+
debug: "color: #6b7280; font-weight: normal;",
|
|
29
|
+
info: "color: #3b82f6; font-weight: bold;",
|
|
30
|
+
success: "color: #22c55e; font-weight: bold;",
|
|
31
|
+
warn: "color: #eab308; font-weight: bold;",
|
|
32
|
+
error: "color: #ef4444; font-weight: bold;",
|
|
33
|
+
};
|
|
34
|
+
const BROWSER_CONSOLE_METHOD = {
|
|
35
|
+
debug: "debug",
|
|
36
|
+
info: "log",
|
|
37
|
+
success: "log",
|
|
38
|
+
warn: "warn",
|
|
39
|
+
error: "error",
|
|
40
|
+
};
|
|
41
|
+
function formatTimestamp(date) {
|
|
42
|
+
return date.toLocaleTimeString("en-US", { hour12: false });
|
|
43
|
+
}
|
|
44
|
+
function shortenPath(filePath) {
|
|
45
|
+
const parts = filePath.split("/");
|
|
46
|
+
if (parts.length <= 3)
|
|
47
|
+
return filePath;
|
|
48
|
+
return parts.slice(-3).join("/");
|
|
49
|
+
}
|
|
50
|
+
function formatNode(entry) {
|
|
51
|
+
const color = LEVEL_COLORS[entry.level];
|
|
52
|
+
const label = LEVEL_LABELS[entry.level];
|
|
53
|
+
const time = formatTimestamp(entry.timestamp);
|
|
54
|
+
const callerStr = entry.caller
|
|
55
|
+
? `${ANSI.dim}${shortenPath(entry.caller.file)}:${entry.caller.line}${ANSI.reset}`
|
|
56
|
+
: "";
|
|
57
|
+
const prefix = `${ANSI.dim}${time}${ANSI.reset} ${color}${ANSI.bold}[${label}]${ANSI.reset}`;
|
|
58
|
+
const line = callerStr
|
|
59
|
+
? `${prefix} ${entry.message} ${callerStr}`
|
|
60
|
+
: `${prefix} ${entry.message}`;
|
|
61
|
+
const method = entry.level === "error" ? "error" : entry.level === "warn" ? "warn" : "log";
|
|
62
|
+
if (entry.data !== undefined) {
|
|
63
|
+
console[method](line);
|
|
64
|
+
console[method](entry.data);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console[method](line);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function formatBrowser(entry) {
|
|
71
|
+
const label = LEVEL_LABELS[entry.level];
|
|
72
|
+
const style = BROWSER_LEVEL_STYLES[entry.level];
|
|
73
|
+
const time = formatTimestamp(entry.timestamp);
|
|
74
|
+
const method = BROWSER_CONSOLE_METHOD[entry.level];
|
|
75
|
+
const callerStr = entry.caller
|
|
76
|
+
? `${shortenPath(entry.caller.file)}:${entry.caller.line}`
|
|
77
|
+
: "";
|
|
78
|
+
const hasData = entry.data !== undefined;
|
|
79
|
+
const groupLabel = `%c[${label}]%c ${time} ${entry.message}${callerStr ? ` (${callerStr})` : ""}`;
|
|
80
|
+
if (hasData) {
|
|
81
|
+
console.groupCollapsed(groupLabel, style, "color: inherit;");
|
|
82
|
+
console[method](entry.data);
|
|
83
|
+
console.groupEnd();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console[method](groupLabel, style, "color: inherit;");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function formatEntry(entry) {
|
|
90
|
+
if (isBrowser()) {
|
|
91
|
+
formatBrowser(entry);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
formatNode(entry);
|
|
95
|
+
}
|
|
96
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const log: import("./types.js").Logger;
|
package/dist/log.js
ADDED
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { isEnabled, isDebugEnabled } from "./env.js";
|
|
2
|
+
import { getCallerInfo } from "./context.js";
|
|
3
|
+
import { formatEntry } from "./formatter.js";
|
|
4
|
+
function shouldLog(level, config) {
|
|
5
|
+
const enabled = config?.enabled ?? isEnabled();
|
|
6
|
+
if (!enabled)
|
|
7
|
+
return false;
|
|
8
|
+
if (level === "debug") {
|
|
9
|
+
return config?.debugEnabled ?? isDebugEnabled();
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
function logMethod(level, config) {
|
|
14
|
+
return (message, data) => {
|
|
15
|
+
if (!shouldLog(level, config))
|
|
16
|
+
return;
|
|
17
|
+
const caller = getCallerInfo();
|
|
18
|
+
formatEntry({
|
|
19
|
+
level,
|
|
20
|
+
message,
|
|
21
|
+
data,
|
|
22
|
+
timestamp: new Date(),
|
|
23
|
+
caller,
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function createLogger(config) {
|
|
28
|
+
return {
|
|
29
|
+
info: logMethod("info", config),
|
|
30
|
+
warn: logMethod("warn", config),
|
|
31
|
+
error: logMethod("error", config),
|
|
32
|
+
success: logMethod("success", config),
|
|
33
|
+
debug: logMethod("debug", config),
|
|
34
|
+
};
|
|
35
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const LOG_LEVELS: {
|
|
2
|
+
readonly debug: 0;
|
|
3
|
+
readonly info: 1;
|
|
4
|
+
readonly success: 2;
|
|
5
|
+
readonly warn: 3;
|
|
6
|
+
readonly error: 4;
|
|
7
|
+
};
|
|
8
|
+
export type LogLevel = keyof typeof LOG_LEVELS;
|
|
9
|
+
export interface CallerInfo {
|
|
10
|
+
file: string;
|
|
11
|
+
line: number;
|
|
12
|
+
column: number;
|
|
13
|
+
}
|
|
14
|
+
export interface LogEntry {
|
|
15
|
+
level: LogLevel;
|
|
16
|
+
message: string;
|
|
17
|
+
data?: unknown;
|
|
18
|
+
timestamp: Date;
|
|
19
|
+
caller: CallerInfo | null;
|
|
20
|
+
}
|
|
21
|
+
export interface LoggerConfig {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
debugEnabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface Logger {
|
|
26
|
+
info: (message: string, data?: unknown) => void;
|
|
27
|
+
warn: (message: string, data?: unknown) => void;
|
|
28
|
+
error: (message: string, data?: unknown) => void;
|
|
29
|
+
success: (message: string, data?: unknown) => void;
|
|
30
|
+
debug: (message: string, data?: unknown) => void;
|
|
31
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@promise-inc/devlog",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Simple logger with automatic context (file + line)",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"clean": "rm -rf dist"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^25.1.0",
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
|
+
}
|
|
26
|
+
}
|