@gratheon/log-lib 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 +186 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.js +167 -0
- package/dist/types.d.ts +28 -0
- package/dist/types.js +2 -0
- package/example.ts +41 -0
- package/migrations/001-create-logs-table.sql +11 -0
- package/package.json +28 -0
- package/src/index.ts +2 -0
- package/src/logger.ts +153 -0
- package/src/types.ts +31 -0
- package/tsconfig.json +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gratheon
|
|
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,186 @@
|
|
|
1
|
+
# log-lib
|
|
2
|
+
|
|
3
|
+
A TypeScript logging library with console output and MySQL database persistence support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Dual Output**: Logs to both console and MySQL database
|
|
8
|
+
- **Color-Coded Console**: ANSI colored output for different log levels
|
|
9
|
+
- **TypeScript Support**: Full type definitions included
|
|
10
|
+
- **Fastify Integration**: Special logger interface for Fastify framework
|
|
11
|
+
- **Flexible Metadata**: Support for structured metadata in logs
|
|
12
|
+
- **Error Handling**: Graceful handling of database connection failures
|
|
13
|
+
- **Multiple Log Levels**: info, error, warn, debug (debug logs are not persisted to DB)
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @gratheon/log-lib
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Database Setup
|
|
22
|
+
|
|
23
|
+
Before using the logger, you need to set up the MySQL database. Run the migration script:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
mysql -u root -p < migrations/001-create-logs-table.sql
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This will create:
|
|
30
|
+
- A `logs` database
|
|
31
|
+
- A `logs` table with fields: id, level, message, meta, timestamp
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Basic Usage
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { createLogger, LoggerConfig } from '@gratheon/log-lib';
|
|
39
|
+
|
|
40
|
+
const config: LoggerConfig = {
|
|
41
|
+
mysql: {
|
|
42
|
+
host: 'localhost',
|
|
43
|
+
port: 3306,
|
|
44
|
+
user: 'your_user',
|
|
45
|
+
password: 'your_password',
|
|
46
|
+
database: 'logs' // optional, defaults to 'logs'
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const { logger, fastifyLogger } = createLogger(config);
|
|
51
|
+
|
|
52
|
+
// Log messages
|
|
53
|
+
logger.info('Application started');
|
|
54
|
+
logger.warn('Low memory warning', { available: '100MB' });
|
|
55
|
+
logger.error('Failed to connect to API', { endpoint: '/api/users' });
|
|
56
|
+
logger.debug('Processing item', { id: 123 }); // Not stored in DB
|
|
57
|
+
|
|
58
|
+
// Error with stack trace
|
|
59
|
+
try {
|
|
60
|
+
throw new Error('Something went wrong');
|
|
61
|
+
} catch (err) {
|
|
62
|
+
logger.error(err); // Logs error with stack trace
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Enriched error logging
|
|
66
|
+
logger.errorEnriched('Database query failed', err, { query: 'SELECT * FROM users' });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Fastify Integration
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
import Fastify from 'fastify';
|
|
73
|
+
import { createLogger, LoggerConfig } from '@gratheon/log-lib';
|
|
74
|
+
|
|
75
|
+
const config: LoggerConfig = {
|
|
76
|
+
mysql: {
|
|
77
|
+
host: 'localhost',
|
|
78
|
+
port: 3306,
|
|
79
|
+
user: 'your_user',
|
|
80
|
+
password: 'your_password'
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const { fastifyLogger } = createLogger(config);
|
|
85
|
+
|
|
86
|
+
const fastify = Fastify({
|
|
87
|
+
logger: fastifyLogger
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
fastify.listen(3000);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### `createLogger(config: LoggerConfig)`
|
|
96
|
+
|
|
97
|
+
Creates and returns logger instances.
|
|
98
|
+
|
|
99
|
+
**Parameters:**
|
|
100
|
+
- `config`: Configuration object with MySQL connection details
|
|
101
|
+
|
|
102
|
+
**Returns:**
|
|
103
|
+
```typescript
|
|
104
|
+
{
|
|
105
|
+
logger: Logger,
|
|
106
|
+
fastifyLogger: FastifyLogger
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Logger Methods
|
|
111
|
+
|
|
112
|
+
#### `logger.info(message: string, meta?: LogMetadata)`
|
|
113
|
+
Logs informational messages (console + DB)
|
|
114
|
+
|
|
115
|
+
#### `logger.error(message: string | Error, meta?: LogMetadata)`
|
|
116
|
+
Logs errors with automatic Error object detection (console + DB)
|
|
117
|
+
|
|
118
|
+
#### `logger.errorEnriched(message: string, error: Error, meta?: LogMetadata)`
|
|
119
|
+
Logs enriched error messages with context (console + DB)
|
|
120
|
+
|
|
121
|
+
#### `logger.warn(message: string, meta?: LogMetadata)`
|
|
122
|
+
Logs warning messages (console + DB)
|
|
123
|
+
|
|
124
|
+
#### `logger.debug(message: string, meta?: LogMetadata)`
|
|
125
|
+
Logs debug messages (console only, not persisted)
|
|
126
|
+
|
|
127
|
+
### FastifyLogger Methods
|
|
128
|
+
|
|
129
|
+
Compatible with Fastify's logger interface:
|
|
130
|
+
- `info(msg: any)`
|
|
131
|
+
- `error(message: string | Error, meta?: LogMetadata)`
|
|
132
|
+
- `warn(msg: any)`
|
|
133
|
+
- `debug(msg: any)`
|
|
134
|
+
- `fatal(msg: any)` - Logs error and calls `process.exit(1)`
|
|
135
|
+
- `trace(msg: any)` - No-op
|
|
136
|
+
- `child(meta: any)` - Returns the same logger instance
|
|
137
|
+
|
|
138
|
+
## Console Output Colors
|
|
139
|
+
|
|
140
|
+
- **Time**: Blue
|
|
141
|
+
- **Error**: Red (level) + Magenta (metadata)
|
|
142
|
+
- **Info**: Green (level) + Magenta (metadata)
|
|
143
|
+
- **Debug**: Gray (dimmed)
|
|
144
|
+
- **Warn**: Yellow (level) + Magenta (metadata)
|
|
145
|
+
|
|
146
|
+
## Database Schema
|
|
147
|
+
|
|
148
|
+
```sql
|
|
149
|
+
CREATE TABLE `logs` (
|
|
150
|
+
`id` int auto_increment primary key,
|
|
151
|
+
`level` varchar(16) not null,
|
|
152
|
+
`message` varchar(2048) not null,
|
|
153
|
+
`meta` varchar(2048) not null,
|
|
154
|
+
`timestamp` datetime not null
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Error Handling
|
|
159
|
+
|
|
160
|
+
The logger gracefully handles database connection failures:
|
|
161
|
+
- Logs are always written to console
|
|
162
|
+
- Database errors are logged to console only
|
|
163
|
+
- Application continues running even if database is unavailable
|
|
164
|
+
- Connection errors (ECONNREFUSED, ENOTFOUND, ETIMEDOUT) are handled with warnings
|
|
165
|
+
|
|
166
|
+
## TypeScript Types
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
interface LoggerConfig {
|
|
170
|
+
mysql: {
|
|
171
|
+
host: string;
|
|
172
|
+
port: number;
|
|
173
|
+
user: string;
|
|
174
|
+
password: string;
|
|
175
|
+
database?: string; // defaults to 'logs'
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface LogMetadata {
|
|
180
|
+
[key: string]: any;
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
ISC
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLogger = void 0;
|
|
4
|
+
var logger_1 = require("./logger");
|
|
5
|
+
Object.defineProperty(exports, "createLogger", { enumerable: true, get: function () { return logger_1.createLogger; } });
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.createLogger = void 0;
|
|
30
|
+
const mysql_1 = __importStar(require("@databases/mysql"));
|
|
31
|
+
const fast_safe_stringify_1 = __importDefault(require("fast-safe-stringify"));
|
|
32
|
+
let conn;
|
|
33
|
+
function initializeConnection(config) {
|
|
34
|
+
const database = config.mysql.database || 'logs';
|
|
35
|
+
conn = (0, mysql_1.default)(`mysql://${config.mysql.user}:${config.mysql.password}@${config.mysql.host}:${config.mysql.port}/${database}`);
|
|
36
|
+
}
|
|
37
|
+
function log(level, message, meta) {
|
|
38
|
+
let time = new Date().toISOString();
|
|
39
|
+
let hhMMTime = time.slice(11, 19);
|
|
40
|
+
// colorize time to have ansi blue color
|
|
41
|
+
hhMMTime = `\x1b[34m${hhMMTime}\x1b[0m`;
|
|
42
|
+
// colorize level to have ansi red color for errors
|
|
43
|
+
meta = meta ? (0, fast_safe_stringify_1.default)(meta) : "";
|
|
44
|
+
if (level === "error") {
|
|
45
|
+
level = `\x1b[31m${level}\x1b[0m`;
|
|
46
|
+
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
47
|
+
}
|
|
48
|
+
else if (level === "info") {
|
|
49
|
+
level = `\x1b[32m${level}\x1b[0m`;
|
|
50
|
+
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
51
|
+
}
|
|
52
|
+
else if (level === "debug") {
|
|
53
|
+
level = `\x1b[90m${level}\x1b[0m`;
|
|
54
|
+
message = `\x1b[90m${message}\x1b[0m`;
|
|
55
|
+
meta = `\x1b[90m${meta}\x1b[0m`;
|
|
56
|
+
}
|
|
57
|
+
else if (level === "warn") {
|
|
58
|
+
level = `\x1b[33m${level}\x1b[0m`;
|
|
59
|
+
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
60
|
+
}
|
|
61
|
+
console.log(`${hhMMTime} [${level}]: ${message} ${meta}`);
|
|
62
|
+
}
|
|
63
|
+
function storeInDB(level, message, meta) {
|
|
64
|
+
if (!conn) {
|
|
65
|
+
console.error(`\x1b[31m[Logger DB Error] Logger not initialized. Call createLogger() first.\x1b[0m`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (!meta)
|
|
69
|
+
meta = "";
|
|
70
|
+
// Use the logger's dedicated connection pool
|
|
71
|
+
conn.query((0, mysql_1.sql) `
|
|
72
|
+
INSERT INTO \`logs\` (\`level\`, \`message\`, \`meta\`, \`timestamp\`)
|
|
73
|
+
VALUES (${level}, ${message}, ${JSON.stringify(meta)}, NOW())
|
|
74
|
+
`).catch(err => {
|
|
75
|
+
// Log connection errors to console only, don't crash
|
|
76
|
+
console.error(`\x1b[31m[Logger DB Error] Failed to store log in DB:\x1b[0m ${err.message}`);
|
|
77
|
+
// Optionally check if it's a connection error vs. other query error
|
|
78
|
+
if (err.code && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT')) {
|
|
79
|
+
console.warn(`\x1b[33m[Logger DB Warning] Logger DB connection failed (${err.code}). Is the DB ready?\x1b[0m`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function createLogger(config) {
|
|
84
|
+
initializeConnection(config);
|
|
85
|
+
const logger = {
|
|
86
|
+
info: (message, meta) => {
|
|
87
|
+
log("info", message, meta);
|
|
88
|
+
storeInDB("info", message, meta);
|
|
89
|
+
},
|
|
90
|
+
error: (message, meta) => {
|
|
91
|
+
if (message.message && message.stack) {
|
|
92
|
+
// Pass the error message string, not the whole object, to storeInDB
|
|
93
|
+
storeInDB("error", message.message, meta);
|
|
94
|
+
return log("error", message.message, {
|
|
95
|
+
stack: message.stack,
|
|
96
|
+
...meta,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// If message is not an Error object, check if it's another type of object
|
|
100
|
+
const messageString = typeof message === 'object' && message !== null && !Array.isArray(message)
|
|
101
|
+
? (0, fast_safe_stringify_1.default)(message) // Stringify if it's a plain object
|
|
102
|
+
: String(message); // Otherwise, convert to string as before
|
|
103
|
+
log("error", messageString, meta);
|
|
104
|
+
// Store the original message or its stringified form in DB
|
|
105
|
+
storeInDB("error", typeof message === 'object' ? (0, fast_safe_stringify_1.default)(message) : message, meta);
|
|
106
|
+
},
|
|
107
|
+
errorEnriched: (message, error, meta) => {
|
|
108
|
+
const enrichedMessage = `${message}: ${error.message}`;
|
|
109
|
+
if (error.message && error.stack) {
|
|
110
|
+
// Store the combined error message in the DB
|
|
111
|
+
storeInDB("error", enrichedMessage, meta);
|
|
112
|
+
return log("error", enrichedMessage, {
|
|
113
|
+
stack: error.stack,
|
|
114
|
+
...meta,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
log("error", String(message), meta);
|
|
118
|
+
storeInDB("error", message, meta);
|
|
119
|
+
},
|
|
120
|
+
warn: (message, meta) => {
|
|
121
|
+
log("warn", message, meta);
|
|
122
|
+
storeInDB("warn", message, meta);
|
|
123
|
+
},
|
|
124
|
+
// do not store debug logs in DB
|
|
125
|
+
debug: (message, meta) => {
|
|
126
|
+
log("debug", message, meta);
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
const fastifyLogger = {
|
|
130
|
+
// Stringify potential objects passed to info/warn
|
|
131
|
+
info: (msg) => {
|
|
132
|
+
const messageString = typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : String(msg);
|
|
133
|
+
log("info", messageString);
|
|
134
|
+
// storeInDB("info", messageString); // Keep commented out as original
|
|
135
|
+
},
|
|
136
|
+
error: (message, meta) => {
|
|
137
|
+
const errorMessage = (message && message.message) ? message.message : String(message);
|
|
138
|
+
log("error", errorMessage, meta);
|
|
139
|
+
// Ensure string is passed to storeInDB
|
|
140
|
+
storeInDB("error", typeof message === 'object' ? (0, fast_safe_stringify_1.default)(message) : errorMessage, meta);
|
|
141
|
+
},
|
|
142
|
+
warn: (msg) => {
|
|
143
|
+
const messageString = typeof msg === 'object' ? (0, fast_safe_stringify_1.default)(msg) : String(msg);
|
|
144
|
+
log("warn", messageString);
|
|
145
|
+
storeInDB("warn", messageString); // Pass stringified message
|
|
146
|
+
},
|
|
147
|
+
// do not store debug logs in DB
|
|
148
|
+
debug: (msg) => {
|
|
149
|
+
log("debug", msg);
|
|
150
|
+
},
|
|
151
|
+
fatal: (msg) => {
|
|
152
|
+
log("error", msg);
|
|
153
|
+
storeInDB("error", msg);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
},
|
|
156
|
+
trace: (msg) => { },
|
|
157
|
+
child: (meta) => {
|
|
158
|
+
return fastifyLogger;
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
// Set up uncaught exception handler
|
|
162
|
+
process.on("uncaughtException", function (err) {
|
|
163
|
+
logger.errorEnriched("UncaughtException processing: %s", err);
|
|
164
|
+
});
|
|
165
|
+
return { logger, fastifyLogger };
|
|
166
|
+
}
|
|
167
|
+
exports.createLogger = createLogger;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface LoggerConfig {
|
|
2
|
+
mysql: {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
user: string;
|
|
6
|
+
password: string;
|
|
7
|
+
database?: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export interface LogMetadata {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
export interface Logger {
|
|
14
|
+
info: (message: string, meta?: LogMetadata) => void;
|
|
15
|
+
error: (message: string | Error | any, meta?: LogMetadata) => void;
|
|
16
|
+
errorEnriched: (message: string, error: Error | any, meta?: LogMetadata) => void;
|
|
17
|
+
warn: (message: string, meta?: LogMetadata) => void;
|
|
18
|
+
debug: (message: string, meta?: LogMetadata) => void;
|
|
19
|
+
}
|
|
20
|
+
export interface FastifyLogger {
|
|
21
|
+
info: (msg: any) => void;
|
|
22
|
+
error: (message: string | Error | any, meta?: LogMetadata) => void;
|
|
23
|
+
warn: (msg: any) => void;
|
|
24
|
+
debug: (msg: any) => void;
|
|
25
|
+
fatal: (msg: any) => void;
|
|
26
|
+
trace: (msg: any) => void;
|
|
27
|
+
child: (meta: any) => FastifyLogger;
|
|
28
|
+
}
|
package/dist/types.js
ADDED
package/example.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createLogger, LoggerConfig } from './src/index';
|
|
2
|
+
|
|
3
|
+
// Example configuration
|
|
4
|
+
const config: LoggerConfig = {
|
|
5
|
+
mysql: {
|
|
6
|
+
host: 'localhost',
|
|
7
|
+
port: 3306,
|
|
8
|
+
user: 'root',
|
|
9
|
+
password: 'test',
|
|
10
|
+
database: 'logs' // optional, defaults to 'logs'
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Create logger instances
|
|
15
|
+
const { logger, fastifyLogger } = createLogger(config);
|
|
16
|
+
|
|
17
|
+
// Example usage
|
|
18
|
+
logger.info('Application started successfully');
|
|
19
|
+
logger.debug('Debug information', { userId: 123, action: 'login' });
|
|
20
|
+
logger.warn('Low memory warning', { available: '100MB', threshold: '200MB' });
|
|
21
|
+
|
|
22
|
+
// Error logging examples
|
|
23
|
+
try {
|
|
24
|
+
throw new Error('Something went wrong');
|
|
25
|
+
} catch (err) {
|
|
26
|
+
logger.error(err); // Logs with stack trace
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Enriched error logging
|
|
30
|
+
try {
|
|
31
|
+
// Some database operation
|
|
32
|
+
throw new Error('Connection timeout');
|
|
33
|
+
} catch (err) {
|
|
34
|
+
logger.errorEnriched('Database query failed', err, {
|
|
35
|
+
query: 'SELECT * FROM users',
|
|
36
|
+
timeout: 5000
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Object error logging
|
|
41
|
+
logger.error({ code: 'AUTH_FAILED', user: 'john@example.com' });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Create the logs database
|
|
2
|
+
CREATE DATABASE IF NOT EXISTS `logs`;
|
|
3
|
+
|
|
4
|
+
-- Create the logs table within the 'logs' database
|
|
5
|
+
CREATE TABLE IF NOT EXISTS `logs`.`logs` (
|
|
6
|
+
`id` int auto_increment primary key,
|
|
7
|
+
`level` varchar(16) not null,
|
|
8
|
+
`message` varchar(2048) not null,
|
|
9
|
+
`meta` varchar(2048) not null,
|
|
10
|
+
`timestamp` datetime not null
|
|
11
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gratheon/log-lib",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Logging library with console and MySQL database persistence",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsc --watch",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"logging",
|
|
14
|
+
"logger",
|
|
15
|
+
"mysql",
|
|
16
|
+
"typescript"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "ISC",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@databases/mysql": "^6.0.0",
|
|
22
|
+
"fast-safe-stringify": "^2.1.1"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^18.11.11",
|
|
26
|
+
"typescript": "^4.9.4"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/index.ts
ADDED
package/src/logger.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import createConnectionPool, { sql, ConnectionPool } from "@databases/mysql";
|
|
2
|
+
import jsonStringify from "fast-safe-stringify";
|
|
3
|
+
import { LoggerConfig, Logger, FastifyLogger, LogMetadata } from "./types";
|
|
4
|
+
|
|
5
|
+
let conn: ConnectionPool;
|
|
6
|
+
|
|
7
|
+
function initializeConnection(config: LoggerConfig) {
|
|
8
|
+
const database = config.mysql.database || 'logs';
|
|
9
|
+
conn = createConnectionPool(
|
|
10
|
+
`mysql://${config.mysql.user}:${config.mysql.password}@${config.mysql.host}:${config.mysql.port}/${database}`,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function log(level: string, message: string, meta?: any) {
|
|
15
|
+
let time = new Date().toISOString();
|
|
16
|
+
let hhMMTime = time.slice(11, 19);
|
|
17
|
+
// colorize time to have ansi blue color
|
|
18
|
+
hhMMTime = `\x1b[34m${hhMMTime}\x1b[0m`;
|
|
19
|
+
|
|
20
|
+
// colorize level to have ansi red color for errors
|
|
21
|
+
meta = meta ? jsonStringify(meta) : "";
|
|
22
|
+
|
|
23
|
+
if (level === "error") {
|
|
24
|
+
level = `\x1b[31m${level}\x1b[0m`;
|
|
25
|
+
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
26
|
+
} else if (level === "info") {
|
|
27
|
+
level = `\x1b[32m${level}\x1b[0m`;
|
|
28
|
+
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
29
|
+
} else if (level === "debug") {
|
|
30
|
+
level = `\x1b[90m${level}\x1b[0m`;
|
|
31
|
+
message = `\x1b[90m${message}\x1b[0m`;
|
|
32
|
+
meta = `\x1b[90m${meta}\x1b[0m`;
|
|
33
|
+
} else if (level === "warn") {
|
|
34
|
+
level = `\x1b[33m${level}\x1b[0m`;
|
|
35
|
+
meta = `\x1b[35m${meta}\x1b[0m`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`${hhMMTime} [${level}]: ${message} ${meta}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function storeInDB(level: string, message: string, meta?: any) {
|
|
42
|
+
if (!conn) {
|
|
43
|
+
console.error(`\x1b[31m[Logger DB Error] Logger not initialized. Call createLogger() first.\x1b[0m`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!meta) meta = "";
|
|
48
|
+
// Use the logger's dedicated connection pool
|
|
49
|
+
conn.query(sql`
|
|
50
|
+
INSERT INTO \`logs\` (\`level\`, \`message\`, \`meta\`, \`timestamp\`)
|
|
51
|
+
VALUES (${level}, ${message}, ${JSON.stringify(meta)}, NOW())
|
|
52
|
+
`).catch(err => {
|
|
53
|
+
// Log connection errors to console only, don't crash
|
|
54
|
+
console.error(`\x1b[31m[Logger DB Error] Failed to store log in DB:\x1b[0m ${err.message}`);
|
|
55
|
+
// Optionally check if it's a connection error vs. other query error
|
|
56
|
+
if (err.code && (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND' || err.code === 'ETIMEDOUT')) {
|
|
57
|
+
console.warn(`\x1b[33m[Logger DB Warning] Logger DB connection failed (${err.code}). Is the DB ready?\x1b[0m`);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createLogger(config: LoggerConfig): { logger: Logger; fastifyLogger: FastifyLogger } {
|
|
63
|
+
initializeConnection(config);
|
|
64
|
+
|
|
65
|
+
const logger: Logger = {
|
|
66
|
+
info: (message: string, meta?: LogMetadata) => {
|
|
67
|
+
log("info", message, meta);
|
|
68
|
+
storeInDB("info", message, meta);
|
|
69
|
+
},
|
|
70
|
+
error: (message: string | Error | any, meta?: LogMetadata) => {
|
|
71
|
+
if (message.message && message.stack) {
|
|
72
|
+
// Pass the error message string, not the whole object, to storeInDB
|
|
73
|
+
storeInDB("error", message.message, meta);
|
|
74
|
+
return log("error", message.message, {
|
|
75
|
+
stack: message.stack,
|
|
76
|
+
...meta,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// If message is not an Error object, check if it's another type of object
|
|
80
|
+
const messageString = typeof message === 'object' && message !== null && !Array.isArray(message)
|
|
81
|
+
? jsonStringify(message) // Stringify if it's a plain object
|
|
82
|
+
: String(message); // Otherwise, convert to string as before
|
|
83
|
+
log("error", messageString, meta);
|
|
84
|
+
// Store the original message or its stringified form in DB
|
|
85
|
+
storeInDB("error", typeof message === 'object' ? jsonStringify(message) : message, meta);
|
|
86
|
+
},
|
|
87
|
+
errorEnriched: (message: string, error: Error | any, meta?: LogMetadata) => {
|
|
88
|
+
const enrichedMessage = `${message}: ${error.message}`;
|
|
89
|
+
if (error.message && error.stack) {
|
|
90
|
+
// Store the combined error message in the DB
|
|
91
|
+
storeInDB("error", enrichedMessage, meta);
|
|
92
|
+
return log("error", enrichedMessage, {
|
|
93
|
+
stack: error.stack,
|
|
94
|
+
...meta,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
log("error", String(message), meta);
|
|
98
|
+
storeInDB("error", message, meta);
|
|
99
|
+
},
|
|
100
|
+
warn: (message: string, meta?: LogMetadata) => {
|
|
101
|
+
log("warn", message, meta);
|
|
102
|
+
storeInDB("warn", message, meta);
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
// do not store debug logs in DB
|
|
106
|
+
debug: (message: string, meta?: LogMetadata) => {
|
|
107
|
+
log("debug", message, meta);
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const fastifyLogger: FastifyLogger = {
|
|
112
|
+
// Stringify potential objects passed to info/warn
|
|
113
|
+
info: (msg) => {
|
|
114
|
+
const messageString = typeof msg === 'object' ? jsonStringify(msg) : String(msg);
|
|
115
|
+
log("info", messageString);
|
|
116
|
+
// storeInDB("info", messageString); // Keep commented out as original
|
|
117
|
+
},
|
|
118
|
+
error: (message: string | Error | any, meta?: LogMetadata) => {
|
|
119
|
+
const errorMessage = (message && message.message) ? message.message : String(message);
|
|
120
|
+
log("error", errorMessage, meta);
|
|
121
|
+
// Ensure string is passed to storeInDB
|
|
122
|
+
storeInDB("error", typeof message === 'object' ? jsonStringify(message) : errorMessage, meta);
|
|
123
|
+
},
|
|
124
|
+
warn: (msg) => {
|
|
125
|
+
const messageString = typeof msg === 'object' ? jsonStringify(msg) : String(msg);
|
|
126
|
+
log("warn", messageString);
|
|
127
|
+
storeInDB("warn", messageString); // Pass stringified message
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// do not store debug logs in DB
|
|
131
|
+
debug: (msg) => {
|
|
132
|
+
log("debug", msg);
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
fatal: (msg) => {
|
|
136
|
+
log("error", msg);
|
|
137
|
+
storeInDB("error", msg);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
trace: (msg) => {},
|
|
142
|
+
child: (meta: any) => {
|
|
143
|
+
return fastifyLogger;
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Set up uncaught exception handler
|
|
148
|
+
process.on("uncaughtException", function (err) {
|
|
149
|
+
logger.errorEnriched("UncaughtException processing: %s", err);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return { logger, fastifyLogger };
|
|
153
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface LoggerConfig {
|
|
2
|
+
mysql: {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
user: string;
|
|
6
|
+
password: string;
|
|
7
|
+
database?: string; // defaults to 'logs'
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface LogMetadata {
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface Logger {
|
|
16
|
+
info: (message: string, meta?: LogMetadata) => void;
|
|
17
|
+
error: (message: string | Error | any, meta?: LogMetadata) => void;
|
|
18
|
+
errorEnriched: (message: string, error: Error | any, meta?: LogMetadata) => void;
|
|
19
|
+
warn: (message: string, meta?: LogMetadata) => void;
|
|
20
|
+
debug: (message: string, meta?: LogMetadata) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FastifyLogger {
|
|
24
|
+
info: (msg: any) => void;
|
|
25
|
+
error: (message: string | Error | any, meta?: LogMetadata) => void;
|
|
26
|
+
warn: (msg: any) => void;
|
|
27
|
+
debug: (msg: any) => void;
|
|
28
|
+
fatal: (msg: any) => void;
|
|
29
|
+
trace: (msg: any) => void;
|
|
30
|
+
child: (meta: any) => FastifyLogger;
|
|
31
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"moduleResolution": "node",
|
|
14
|
+
"resolveJsonModule": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|