yinzerflow 0.3.0 → 0.4.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 +1 -1
- package/docs/configuration/advanced-configuration-options.md +1 -1
- package/docs/configuration/configuration-patterns.md +1 -1
- package/docs/core/logging.md +130 -0
- package/docs/security/security-overview.md +2 -2
- package/docs/start-here.md +2 -2
- package/example/README.md +11 -0
- package/example/app/handlers/example.ts +27 -0
- package/example/app/index.ts +163 -0
- package/example/app/routes/example.ts +18 -0
- package/example/app/routes/group-example.ts +13 -0
- package/example/app/util/customLogger.ts +166 -0
- package/example/docker-compose.yml +28 -0
- package/example/package.json +16 -0
- package/example/tsconfig.json +54 -0
- package/index.d.ts +45 -24
- package/index.js +24 -14
- package/index.js.map +13 -12
- package/package.json +1 -1
- package/docs/security/logging.md +0 -348
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ await app.listen();
|
|
|
41
41
|
- **[Getting Started](docs/start-here.md)** - Complete guide and examples
|
|
42
42
|
- **[Routes](docs/routes.md)** - Routing system and handlers
|
|
43
43
|
- **[Request/Response](docs/request.md)** - Request and response objects
|
|
44
|
-
- **[Logging](docs/logging.md)** - Logging configuration and customization
|
|
44
|
+
- **[Logging](docs/core/logging.md)** - Logging configuration and customization
|
|
45
45
|
- **[Advanced Configuration](docs/advanced-configuration-options.md)** - Detailed configuration options
|
|
46
46
|
|
|
47
47
|
## 🛡️ Security
|
|
@@ -210,7 +210,7 @@ await app.listen();
|
|
|
210
210
|
|
|
211
211
|
### Logging Configuration
|
|
212
212
|
|
|
213
|
-
Control framework logging output with built-in Pittsburgh personality or custom logging libraries. See [Logging Documentation](
|
|
213
|
+
Control framework logging output with built-in Pittsburgh personality or custom logging libraries. See [Logging Documentation](../core/logging.md) for detailed setup, custom logger integration, and advanced use cases.
|
|
214
214
|
|
|
215
215
|
```typescript
|
|
216
216
|
const app = new YinzerFlow({
|
|
@@ -443,7 +443,7 @@ For detailed configuration options, see:
|
|
|
443
443
|
- **[Body Parsing Security](../security/body-parsing.md)** - Body parser configuration
|
|
444
444
|
- **[CORS Security](../security/cors.md)** - CORS configuration
|
|
445
445
|
- **[IP Security](../security/ip-security.md)** - IP security configuration
|
|
446
|
-
- **[Logging Security](../
|
|
446
|
+
- **[Logging Security](../core/logging.md)** - Logging configuration
|
|
447
447
|
|
|
448
448
|
## Common Issues and Solutions
|
|
449
449
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Logging
|
|
2
|
+
|
|
3
|
+
YinzerFlow provides a flexible logging system with built-in Pittsburgh personality and performance tracking.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { YinzerFlow, createLogger } from 'yinzerflow';
|
|
9
|
+
|
|
10
|
+
const logger = createLogger({
|
|
11
|
+
prefix: 'MYAPP',
|
|
12
|
+
logLevel: 'info'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const server = new YinzerFlow({
|
|
16
|
+
port: 3000,
|
|
17
|
+
logger,
|
|
18
|
+
networkLogs: true
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Configuration Options
|
|
23
|
+
|
|
24
|
+
| Option | Type | Default | Description |
|
|
25
|
+
|--------|------|---------|-------------|
|
|
26
|
+
| `logLevel` | `'off' \| 'error' \| 'warn' \| 'info'` | `'info'` | Minimum log level to output |
|
|
27
|
+
| `prefix` | `string` | `'YINZER'` | Prefix for log messages |
|
|
28
|
+
| `logger` | `Logger` | `undefined` | Custom logger implementation (Winston, Pino, etc.) |
|
|
29
|
+
| `networkLogs` | `boolean` | `false` | Enable nginx-style network request logging |
|
|
30
|
+
| `networkLogger` | `Logger` | `undefined` | Custom logger for network logs (can be same as `logger` or different) |
|
|
31
|
+
|
|
32
|
+
## Examples
|
|
33
|
+
|
|
34
|
+
### Basic Example
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { log } from 'yinzerflow';
|
|
38
|
+
|
|
39
|
+
// Use the shared logger (default YINZER prefix)
|
|
40
|
+
log.info('Application started');
|
|
41
|
+
log.warn('Deprecated feature used');
|
|
42
|
+
log.error('Operation failed');
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Custom Logger
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { createLogger } from 'yinzerflow';
|
|
49
|
+
|
|
50
|
+
const dbLogger = createLogger({
|
|
51
|
+
prefix: 'DATABASE',
|
|
52
|
+
logLevel: 'error'
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
dbLogger.error('Connection failed');
|
|
56
|
+
// Output: [DATABASE] ❌ [timestamp] [ERROR] Connection failed - aw jeez
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Unified Network and App Logging
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { YinzerFlow, createLogger } from 'yinzerflow';
|
|
63
|
+
|
|
64
|
+
const logger = createLogger({ prefix: 'APP', logLevel: 'info' });
|
|
65
|
+
|
|
66
|
+
const server = new YinzerFlow({
|
|
67
|
+
port: 3000,
|
|
68
|
+
logger,
|
|
69
|
+
networkLogs: true,
|
|
70
|
+
networkLogger: logger // Route network logs to same logger
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Both app and network logs go to the same custom logger
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Common Use Cases
|
|
77
|
+
|
|
78
|
+
- **Debug Development Issues**: Use default logger with `networkLogs: true` for comprehensive debugging
|
|
79
|
+
- **Track Application Errors**: Use `logLevel: 'error'` to capture only critical errors
|
|
80
|
+
- **Monitor Component Health**: Create isolated loggers per subsystem with custom prefixes
|
|
81
|
+
- **Integrate External Logging**: Use custom logger implementation for Winston, Pino, etc.
|
|
82
|
+
- **Silent Operation**: Set `logLevel: 'off'` for environments with external log management
|
|
83
|
+
- **Script and Utility Logging**: Use shared `log` instance in standalone scripts and database seeds
|
|
84
|
+
|
|
85
|
+
## Methods
|
|
86
|
+
|
|
87
|
+
### `createLogger(options?): Logger`
|
|
88
|
+
|
|
89
|
+
Creates a logger instance with isolated state.
|
|
90
|
+
|
|
91
|
+
**Parameters:**
|
|
92
|
+
- `logLevel?: 'off' | 'error' | 'warn' | 'info'` - Minimum log level (default: 'info')
|
|
93
|
+
- `prefix?: string` - Log message prefix (default: 'YINZER')
|
|
94
|
+
- `logger?: Logger` - Custom logger implementation
|
|
95
|
+
|
|
96
|
+
**Returns:** Logger with `info`, `warn`, `error`, and `levels` methods.
|
|
97
|
+
|
|
98
|
+
### Logger Methods
|
|
99
|
+
|
|
100
|
+
- `info(...args): void` - Log info-level messages
|
|
101
|
+
- `warn(...args): void` - Log warning messages
|
|
102
|
+
- `error(...args): void` - Log error messages
|
|
103
|
+
- `levels` - Access to log level constants
|
|
104
|
+
|
|
105
|
+
## Properties
|
|
106
|
+
|
|
107
|
+
### Log Levels
|
|
108
|
+
|
|
109
|
+
- `off` (0): No logging
|
|
110
|
+
- `error` (1): Only errors
|
|
111
|
+
- `warn` (2): Warnings and errors
|
|
112
|
+
- `info` (3): All messages (most verbose)
|
|
113
|
+
|
|
114
|
+
## Security Considerations
|
|
115
|
+
|
|
116
|
+
YinzerFlow implements several security measures for safe logging:
|
|
117
|
+
|
|
118
|
+
### 🛡️ Safe Data Handling
|
|
119
|
+
- **Problem**: Logging sensitive data can expose secrets in log files
|
|
120
|
+
- **YinzerFlow Solution**: Uses native `console.log` formatting to prevent accidental serialization of sensitive objects
|
|
121
|
+
|
|
122
|
+
### 🛡️ Log Level Protection
|
|
123
|
+
- **Problem**: Verbose logging in production can impact performance and expose internal details
|
|
124
|
+
- **YinzerFlow Solution**: Configurable log levels with early returns to minimize overhead when logging is disabled
|
|
125
|
+
|
|
126
|
+
### 🛡️ Logger Isolation
|
|
127
|
+
- **Problem**: Custom loggers could interfere with framework logging
|
|
128
|
+
- **YinzerFlow Solution**: Clean interface boundaries and isolated logger instances prevent conflicts
|
|
129
|
+
|
|
130
|
+
These security measures ensure YinzerFlow's logging implementation follows security best practices and prevents common attack vectors while maintaining spec compliance.
|
|
@@ -124,7 +124,7 @@ app.onError(({ response }, error) => {
|
|
|
124
124
|
### 🛡️ Logging Security
|
|
125
125
|
**Protection**: Log injection, sensitive data exposure
|
|
126
126
|
**Implementation**: Structured logging, sensitive data filtering
|
|
127
|
-
**Documentation**: [Logging Security](
|
|
127
|
+
**Documentation**: [Logging Security](../core/logging.md)
|
|
128
128
|
|
|
129
129
|
```typescript
|
|
130
130
|
const secureApp = new YinzerFlow({
|
|
@@ -272,7 +272,7 @@ For detailed security documentation:
|
|
|
272
272
|
- **[Body Parsing](./body-parsing.md)** - File upload and JSON parsing security
|
|
273
273
|
- **[CORS](./cors.md)** - Cross-origin request security
|
|
274
274
|
- **[IP Security](./ip-security.md)** - Client IP validation and protection
|
|
275
|
-
- **[Logging](
|
|
275
|
+
- **[Logging](../core/logging.md)** - Secure logging practices
|
|
276
276
|
- **[Error Handling](../core/error-handling.md)** - Secure error handling patterns
|
|
277
277
|
|
|
278
278
|
For security issues or questions:
|
package/docs/start-here.md
CHANGED
|
@@ -120,7 +120,7 @@ For custom shutdown handling, see [Advanced Configuration](./advanced-configurat
|
|
|
120
120
|
- **[Body Parsing](./security/body-parsing.md)** - Secure parsing of JSON, file uploads, and form data with DoS protection
|
|
121
121
|
- **[CORS](./security/cors.md)** - Cross-Origin Resource Sharing with comprehensive security measures
|
|
122
122
|
- **[IP Security](./security/ip-security.md)** - Client IP validation and spoofing protection for load balancers and CDNs
|
|
123
|
-
- **[Logging](./
|
|
123
|
+
- **[Logging](./core/logging.md)** - Flexible logging with custom logger support and Pittsburgh personality
|
|
124
124
|
- **[Configuration Patterns](./configuration/configuration-patterns.md)** - Common configuration patterns and best practices
|
|
125
125
|
- **[Advanced Configuration](./configuration/advanced-configuration-options.md)** - Fine-tune security, performance, and functionality
|
|
126
126
|
|
|
@@ -164,7 +164,7 @@ For custom shutdown handling, see [Advanced Configuration](./advanced-configurat
|
|
|
164
164
|
1. **Start with Routes**: Learn the routing system in [routes.md](./routes.md)
|
|
165
165
|
2. **Understand Requests**: Explore request handling in [request.md](./request.md)
|
|
166
166
|
3. **Configure Security**: Set up IP security and CORS in [ip-security.md](./ip-security.md) and [cors.md](./cors.md)
|
|
167
|
-
4. **Customize Logging**: Implement custom loggers in [logging.md](./logging.md)
|
|
167
|
+
4. **Customize Logging**: Implement custom loggers in [logging.md](./core/logging.md)
|
|
168
168
|
5. **Advanced Configuration**: Fine-tune settings in [advanced-configuration-options.md](./advanced-configuration-options.md)
|
|
169
169
|
|
|
170
170
|
## Examples
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
For the types to work, you need to connect to the docker container using the IDE
|
|
2
|
+
|
|
3
|
+
1. Ensure the [Dev Containers](https://marketplace.cursorapi.com/items?itemName=ms-vscode-remote.remote-containers) extension is installed in IDE
|
|
4
|
+
2. Start this image using `docker compose up -d`
|
|
5
|
+
3. To start the test server run `bun start`
|
|
6
|
+
4. Use `Ctrl + Shift + P`
|
|
7
|
+
5. Select "Dev Containers: Attach to Running Container ..."
|
|
8
|
+
6. Select this container which should be something like `yinzerflow-testing-app...`
|
|
9
|
+
7. In the IDE that just opened, select folder `/app/yinzerflow-local`
|
|
10
|
+
|
|
11
|
+
To aid in development, you can use the `bun run build:watch` command in the yinzerflow (the actual module) terminal
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
import type { HandlerCallback } from 'yinzerflow';
|
|
3
|
+
|
|
4
|
+
export const exampleHandler: HandlerCallback<{
|
|
5
|
+
body: {
|
|
6
|
+
something: string;
|
|
7
|
+
};
|
|
8
|
+
response: {
|
|
9
|
+
message: string;
|
|
10
|
+
success: boolean;
|
|
11
|
+
data: {
|
|
12
|
+
something: string;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}> = async ({ request }) => {
|
|
16
|
+
const { something } = request.body;
|
|
17
|
+
|
|
18
|
+
throw new Error("test");
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
success: true,
|
|
22
|
+
message: "Hello World",
|
|
23
|
+
data: {
|
|
24
|
+
something
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { registerExampleRoutes } from "@app/routes/example.ts";
|
|
2
|
+
import { registerGroupExampleRoutes } from "@app/routes/group-example.ts";
|
|
3
|
+
import log from "app/util/customLogger.ts";
|
|
4
|
+
import { createLogger, YinzerFlow, type HandlerCallback } from "yinzerflow";
|
|
5
|
+
|
|
6
|
+
const logger = createLogger({
|
|
7
|
+
prefix: "worker",
|
|
8
|
+
logLevel: "info",
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const app = new YinzerFlow({
|
|
12
|
+
cors: {
|
|
13
|
+
enabled: true,
|
|
14
|
+
origin: "http://localhost:8085",
|
|
15
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
16
|
+
allowedHeaders: ["Content-Type", "Authorization", "x-session-id"],
|
|
17
|
+
preflightContinue: false,
|
|
18
|
+
},
|
|
19
|
+
networkLogs: true,
|
|
20
|
+
logger: logger,
|
|
21
|
+
// networkLogger: log,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// app.onError((ctx) => {
|
|
27
|
+
// ctx.response.setStatusCode(500);
|
|
28
|
+
// return {
|
|
29
|
+
// success: false,
|
|
30
|
+
// message: "Something went wrong",
|
|
31
|
+
// };
|
|
32
|
+
// });
|
|
33
|
+
|
|
34
|
+
app.onNotFound(async (ctx) => {
|
|
35
|
+
ctx.response.setStatusCode(404);
|
|
36
|
+
return {
|
|
37
|
+
success: false,
|
|
38
|
+
message: "Not Found",
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
app.beforeAll([({ state }: any) => {
|
|
45
|
+
log.info("======== beforeAll ========");
|
|
46
|
+
// TODO: setup auth middleware here
|
|
47
|
+
// const user = {
|
|
48
|
+
// id: 1,
|
|
49
|
+
// };
|
|
50
|
+
// state.user = user as any;
|
|
51
|
+
return;
|
|
52
|
+
}]);
|
|
53
|
+
|
|
54
|
+
app.afterAll([
|
|
55
|
+
(ctx) => {
|
|
56
|
+
// ctx.response.setStatusCode(202);
|
|
57
|
+
log.info("afterAll");
|
|
58
|
+
},
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
const login: HandlerCallback<{
|
|
62
|
+
response: {
|
|
63
|
+
success: boolean;
|
|
64
|
+
message: string;
|
|
65
|
+
};
|
|
66
|
+
body: {
|
|
67
|
+
email: string;
|
|
68
|
+
password: string;
|
|
69
|
+
};
|
|
70
|
+
}> = (({ request }) => {
|
|
71
|
+
const { email, password } = request.body;
|
|
72
|
+
log.info("========================");
|
|
73
|
+
log.info(email, password);
|
|
74
|
+
log.info("========================");
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
success: true,
|
|
78
|
+
message: "Login successful",
|
|
79
|
+
};
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
app.post("/login", login);
|
|
83
|
+
|
|
84
|
+
app.group("/api", (app) => {
|
|
85
|
+
|
|
86
|
+
app.group('/user', (app) => {
|
|
87
|
+
app.get('/get', ((ctx) => {
|
|
88
|
+
log.info("route handler");
|
|
89
|
+
// log.info(ctx.request);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
message: false
|
|
93
|
+
};
|
|
94
|
+
}),
|
|
95
|
+
{
|
|
96
|
+
beforeHooks: [
|
|
97
|
+
(ctx) => {
|
|
98
|
+
log.info("beforeRoute3");
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}, {
|
|
104
|
+
beforeHooks: [
|
|
105
|
+
(ctx) => {
|
|
106
|
+
ctx.state.test = "test state";
|
|
107
|
+
log.info("beforeRoute2");
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
afterHooks: [
|
|
111
|
+
(ctx) => {
|
|
112
|
+
log.info("afterRoute2");
|
|
113
|
+
log.info(ctx.state.test);
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
app.post(
|
|
119
|
+
"/get/:id",
|
|
120
|
+
((ctx) => {
|
|
121
|
+
log.info("route handler");
|
|
122
|
+
log.info(ctx.request);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
message: false
|
|
126
|
+
};
|
|
127
|
+
}),
|
|
128
|
+
{
|
|
129
|
+
beforeHooks: [
|
|
130
|
+
(ctx) => {
|
|
131
|
+
ctx.response.setStatusCode(200);
|
|
132
|
+
log.info("beforeRoute");
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
afterHooks: [
|
|
136
|
+
() => {
|
|
137
|
+
log.info("afterRoute");
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
}, {
|
|
143
|
+
beforeHooks: [
|
|
144
|
+
(ctx) => {
|
|
145
|
+
log.info("beforeRoute1");
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
afterHooks: [
|
|
149
|
+
(ctx) => {
|
|
150
|
+
log.info("afterRoute1");
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Register example routes
|
|
156
|
+
registerExampleRoutes(app);
|
|
157
|
+
registerGroupExampleRoutes(app);
|
|
158
|
+
|
|
159
|
+
// Start the server AFTER defining all routes
|
|
160
|
+
await app.listen();
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { exampleHandler } from '@app/handlers/example.ts';
|
|
2
|
+
import type { YinzerFlow } from 'yinzerflow';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register example routes on the main app instance
|
|
6
|
+
*/
|
|
7
|
+
export const registerExampleRoutes = (app: YinzerFlow) => {
|
|
8
|
+
/**
|
|
9
|
+
* Get all users
|
|
10
|
+
*/
|
|
11
|
+
app.post('/get-users', exampleHandler);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Add an override reason for a user to be able to add a payout method
|
|
15
|
+
* This is used to remove all requirements bypassing any checks such as the $20 minimum balance
|
|
16
|
+
*/
|
|
17
|
+
// app.post('/user/update', updateUserController);
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { exampleHandler } from '@app/handlers/example.ts';
|
|
2
|
+
import type { YinzerFlow } from 'yinzerflow';
|
|
3
|
+
|
|
4
|
+
export const registerGroupExampleRoutes = (app: YinzerFlow): void => {
|
|
5
|
+
app.group('/admin', (group) => {
|
|
6
|
+
/**
|
|
7
|
+
* Get all users
|
|
8
|
+
*/
|
|
9
|
+
group.post('/get-users', exampleHandler);
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
|
|
2
|
+
import type { TransformableInfo } from 'logform';
|
|
3
|
+
import { addColors, createLogger, format, transports } from 'winston';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logging levels in winston conform to the severity ordering specified by RFC5424: severity of all levels is assumed to
|
|
7
|
+
* be numerically ascending from most important to least important.
|
|
8
|
+
*/
|
|
9
|
+
enum LogLevel {
|
|
10
|
+
CRITICAL = 0,
|
|
11
|
+
ERROR = 1,
|
|
12
|
+
WARN = 2,
|
|
13
|
+
INFO = 3,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
enum LogColor {
|
|
17
|
+
CRITICAL = 'bold white redBG',
|
|
18
|
+
ERROR = 'bold red',
|
|
19
|
+
WARN = 'bold yellow',
|
|
20
|
+
INFO = 'bold cyan',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Transports {
|
|
24
|
+
console: transports.ConsoleTransportInstance;
|
|
25
|
+
file: transports.FileTransportInstance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Define the levels that winston will use. This is used later on
|
|
30
|
+
* when we create the logger
|
|
31
|
+
*/
|
|
32
|
+
const logLevels = {
|
|
33
|
+
critical: LogLevel.CRITICAL,
|
|
34
|
+
error: LogLevel.ERROR,
|
|
35
|
+
warn: LogLevel.WARN,
|
|
36
|
+
info: LogLevel.INFO,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* This lets us add custom logging levels and colors so that
|
|
43
|
+
* winston recognizes them
|
|
44
|
+
*/
|
|
45
|
+
addColors({
|
|
46
|
+
critical: LogColor.CRITICAL,
|
|
47
|
+
error: LogColor.ERROR,
|
|
48
|
+
warn: LogColor.WARN,
|
|
49
|
+
info: LogColor.INFO,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Safely formats a message for logging, handling different types appropriately
|
|
54
|
+
*/
|
|
55
|
+
const formatLogMessage = (message: unknown, meta: Record<string, unknown> = {}): string => {
|
|
56
|
+
let formattedMessage = '';
|
|
57
|
+
|
|
58
|
+
if (typeof message === 'string') {
|
|
59
|
+
formattedMessage = message;
|
|
60
|
+
} else if (typeof message === 'object' && message !== null) {
|
|
61
|
+
formattedMessage = JSON.stringify(message);
|
|
62
|
+
} else {
|
|
63
|
+
formattedMessage = String(message);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const metaKeys = Object.keys(meta);
|
|
67
|
+
if (metaKeys.length === 0) {
|
|
68
|
+
return formattedMessage;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const formattedMeta = JSON.stringify(meta, null, 2);
|
|
72
|
+
|
|
73
|
+
return `${formattedMessage}\n${formattedMeta}`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* This is the format that will be used for all logs except File logs
|
|
78
|
+
*/
|
|
79
|
+
const formatter = format.combine(
|
|
80
|
+
/** Adds color to the format */
|
|
81
|
+
format.colorize(),
|
|
82
|
+
|
|
83
|
+
/** Adds timestamp to the format */
|
|
84
|
+
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
85
|
+
|
|
86
|
+
/** Format the way the log is output to the console */
|
|
87
|
+
format.printf((info: TransformableInfo) => {
|
|
88
|
+
const { timestamp, level, message, ...meta } = info;
|
|
89
|
+
const formattedMessage = formatLogMessage(message, Object.keys(meta).length > 0 ? meta : undefined);
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
91
|
+
return `${timestamp} [${level}]: ${formattedMessage}`;
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* This is the format that will be used for all File logs
|
|
97
|
+
* The only difference is that we don't want to add color because it will mess up the log file
|
|
98
|
+
*/
|
|
99
|
+
const fileFormatter = format.combine(
|
|
100
|
+
/** Adds timestamp to the format */
|
|
101
|
+
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
102
|
+
|
|
103
|
+
/** Format the way the log is output to the console */
|
|
104
|
+
format.printf((info: TransformableInfo) => {
|
|
105
|
+
const { timestamp, level, message, ...meta } = info;
|
|
106
|
+
const formattedMessage = formatLogMessage(message, Object.keys(meta).length > 0 ? meta : undefined);
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
108
|
+
return `${timestamp} [${level}]: ${formattedMessage}`;
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
/** ======================================== Defineing Transports ============================================ */
|
|
113
|
+
const transporters: Transports = {
|
|
114
|
+
console: new transports.Console({
|
|
115
|
+
format: formatter,
|
|
116
|
+
}),
|
|
117
|
+
file: new transports.File({
|
|
118
|
+
filename: 'storage/logs/info.log',
|
|
119
|
+
format: fileFormatter,
|
|
120
|
+
}),
|
|
121
|
+
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Creates and configures the logger based on the current configuration
|
|
126
|
+
*/
|
|
127
|
+
const createConfiguredLogger = (): ReturnType<typeof createLogger> => {
|
|
128
|
+
const transportsToUse = [];
|
|
129
|
+
for (const transport of ['console', 'file']) {
|
|
130
|
+
switch (transport) {
|
|
131
|
+
case 'console':
|
|
132
|
+
transportsToUse.push(transporters.console);
|
|
133
|
+
break;
|
|
134
|
+
case 'file':
|
|
135
|
+
transportsToUse.push(transporters.file);
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
transportsToUse.push(transporters.console);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return createLogger({
|
|
144
|
+
level: 'info',
|
|
145
|
+
transports: transportsToUse,
|
|
146
|
+
levels: logLevels,
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Create the logger with the configured settings
|
|
151
|
+
const log = createConfiguredLogger();
|
|
152
|
+
|
|
153
|
+
export default log;
|
|
154
|
+
export const networkLog = createLogger({
|
|
155
|
+
level: 'info',
|
|
156
|
+
transports: [
|
|
157
|
+
new transports.Console({
|
|
158
|
+
format: formatter,
|
|
159
|
+
}),
|
|
160
|
+
new transports.File({
|
|
161
|
+
filename: 'storage/logs/info.log',
|
|
162
|
+
format: fileFormatter,
|
|
163
|
+
}),
|
|
164
|
+
],
|
|
165
|
+
levels: logLevels,
|
|
166
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
user: bun
|
|
4
|
+
image: oven/bun:1.2.17
|
|
5
|
+
working_dir: /app/yinzerflow-local
|
|
6
|
+
volumes:
|
|
7
|
+
- .:/app/yinzerflow-local
|
|
8
|
+
- ../yinzerflow/lib:/app/yinzerflow/lib
|
|
9
|
+
ports:
|
|
10
|
+
- "3000:5000"
|
|
11
|
+
command: sh -c "bun install && tail -f /dev/null"
|
|
12
|
+
tty: true
|
|
13
|
+
stdin_open: true
|
|
14
|
+
networks:
|
|
15
|
+
- yinzerflow-network
|
|
16
|
+
|
|
17
|
+
benchmark:
|
|
18
|
+
image: williamyeh/wrk
|
|
19
|
+
restart: no
|
|
20
|
+
command: ["-t20", "-c400", "-d10s", "http://api:5000/status"]
|
|
21
|
+
depends_on:
|
|
22
|
+
- app
|
|
23
|
+
networks:
|
|
24
|
+
- yinzerflow-network
|
|
25
|
+
|
|
26
|
+
networks:
|
|
27
|
+
yinzerflow-network:
|
|
28
|
+
driver: bridge
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yinzerflow-local",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"start": "bun --watch app/index.ts"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"logform": "^2.7.0",
|
|
9
|
+
"winston": "^3.17.0",
|
|
10
|
+
"yinzerflow": "file:../yinzerflow/lib"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"bun-types": "1.2.17",
|
|
14
|
+
"typescript": "^5.8.2"
|
|
15
|
+
}
|
|
16
|
+
}
|