@devminister/applog-client 0.0.1

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 ADDED
@@ -0,0 +1,214 @@
1
+ # @devminister/applog-client
2
+
3
+ Application log client for the **Monitoring** platform. Send structured business logs (auth, payments, orders, notifications) with buffered, batched delivery, retry, and duration tracking.
4
+
5
+ Works as a **standalone Node.js client** or as a **NestJS module**.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pnpm add @devminister/applog-client
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ### Standalone (Express, Fastify, any Node.js app)
16
+
17
+ ```typescript
18
+ import { createAppLogClient } from '@devminister/applog-client';
19
+
20
+ const appLog = createAppLogClient({
21
+ apiUrl: 'https://monitor.example.com',
22
+ clientId: process.env.MONITOR_CLIENT_ID!,
23
+ clientSecret: process.env.MONITOR_CLIENT_SECRET!,
24
+ });
25
+
26
+ // Simple logging
27
+ appLog.info('auth', 'user.login', {
28
+ message: 'User logged in',
29
+ userId: 'user-123',
30
+ metadata: { method: 'oauth', provider: 'google' },
31
+ tags: ['web'],
32
+ });
33
+
34
+ appLog.error('payment', 'charge.failed', {
35
+ message: 'Card declined',
36
+ userId: 'user-123',
37
+ metadata: { reason: 'insufficient_funds', amount: 99.99 },
38
+ });
39
+
40
+ // Duration tracking
41
+ const end = appLog.startTimer('payment', 'payment.charge');
42
+ await processPayment(order);
43
+ end({ userId: order.userId, metadata: { orderId: order.id } });
44
+
45
+ // Shutdown (flushes remaining buffer)
46
+ process.on('SIGTERM', async () => {
47
+ await appLog.shutdown();
48
+ process.exit(0);
49
+ });
50
+ ```
51
+
52
+ ### NestJS Module
53
+
54
+ ```typescript
55
+ // app.module.ts
56
+ import { AppLogModule } from '@devminister/applog-client';
57
+
58
+ @Module({
59
+ imports: [
60
+ AppLogModule.register({
61
+ apiUrl: process.env.MONITOR_API_URL,
62
+ clientId: process.env.APPLOG_CLIENT_ID,
63
+ clientSecret: process.env.APPLOG_CLIENT_SECRET,
64
+ }),
65
+ ],
66
+ })
67
+ export class AppModule {}
68
+ ```
69
+
70
+ #### With ConfigService (async)
71
+
72
+ ```typescript
73
+ AppLogModule.registerAsync({
74
+ inject: [ConfigService],
75
+ useFactory: (config: ConfigService) => ({
76
+ apiUrl: config.get('MONITOR_API_URL'),
77
+ clientId: config.get('APPLOG_CLIENT_ID'),
78
+ clientSecret: config.get('APPLOG_CLIENT_SECRET'),
79
+ defaultCategory: 'my-service',
80
+ defaultTags: ['production'],
81
+ }),
82
+ })
83
+ ```
84
+
85
+ #### Inject and use in services
86
+
87
+ ```typescript
88
+ import { AppLogNestService } from '@devminister/applog-client';
89
+
90
+ @Injectable()
91
+ export class PaymentService {
92
+ constructor(private readonly appLog: AppLogNestService) {}
93
+
94
+ async charge(order: Order) {
95
+ const end = this.appLog.startTimer('payment', 'payment.charge', {
96
+ userId: order.userId,
97
+ });
98
+
99
+ try {
100
+ const result = await this.stripe.charge(order);
101
+ end({ message: `Charged $${order.total}`, metadata: { orderId: order.id } });
102
+ return result;
103
+ } catch (err) {
104
+ this.appLog.error('payment', 'payment.charge.failed', {
105
+ message: err.message,
106
+ userId: order.userId,
107
+ metadata: { orderId: order.id, error: err.message },
108
+ });
109
+ throw err;
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ #### Auto-log all HTTP requests (interceptor)
116
+
117
+ ```typescript
118
+ import { AppLogInterceptor } from '@devminister/applog-client';
119
+
120
+ // app.module.ts
121
+ @Module({
122
+ providers: [
123
+ {
124
+ provide: APP_INTERCEPTOR,
125
+ useClass: AppLogInterceptor,
126
+ },
127
+ ],
128
+ })
129
+ export class AppModule {}
130
+ ```
131
+
132
+ This will automatically log every request with controller name, handler, method, path, status code, duration, and userId.
133
+
134
+ ## API
135
+
136
+ ### Log Methods
137
+
138
+ ```typescript
139
+ appLog.debug(category, action, options?)
140
+ appLog.info(category, action, options?)
141
+ appLog.warn(category, action, options?)
142
+ appLog.error(category, action, options?)
143
+ appLog.fatal(category, action, options?)
144
+ ```
145
+
146
+ ### Options
147
+
148
+ | Field | Type | Description |
149
+ |------------|----------|------------------------------------------|
150
+ | `message` | string | Human-readable log message |
151
+ | `metadata` | object | Any structured data (JSON) |
152
+ | `userId` | string | User identifier |
153
+ | `duration` | number | Duration in milliseconds |
154
+ | `tags` | string[] | Searchable tags |
155
+
156
+ ### Timer
157
+
158
+ ```typescript
159
+ const end = appLog.startTimer(category, action, options?);
160
+ // ... do work ...
161
+ end(extraOptions?); // logs with duration automatically
162
+ ```
163
+
164
+ ### Low-level
165
+
166
+ ```typescript
167
+ appLog.push(payload) // Push a raw AppLogPayload
168
+ appLog.flush() // Force flush buffer
169
+ appLog.shutdown() // Stop timer + flush
170
+ appLog.bufferSize // Current buffer length
171
+ ```
172
+
173
+ ## Configuration
174
+
175
+ | Option | Type | Default | Description |
176
+ |--------------------|----------|---------|--------------------------------------------------|
177
+ | `apiUrl` | string | — | Monitoring API base URL (required) |
178
+ | `clientId` | string | — | Environment client ID (required) |
179
+ | `clientSecret` | string | — | Environment client secret (required) |
180
+ | `batchSize` | number | `50` | Auto-flush when buffer reaches this size |
181
+ | `flushInterval` | number | `5000` | Flush interval in ms |
182
+ | `maxBufferSize` | number | `1000` | Max buffer size (oldest logs dropped when full) |
183
+ | `timeout` | number | `10000` | HTTP request timeout in ms |
184
+ | `maxRetries` | number | `3` | Retry attempts with exponential backoff |
185
+ | `defaultCategory` | string | — | Default category when none specified |
186
+ | `defaultTags` | string[] | `[]` | Tags added to every log |
187
+ | `disabled` | boolean | `false` | Disable all logging |
188
+ | `logger` | object | console | Custom logger `{ debug, warn, error }` |
189
+
190
+ ## Category & Action Conventions
191
+
192
+ | Category | Example Actions |
193
+ |----------------|--------------------------------------------------------------|
194
+ | `auth` | `user.login`, `user.logout`, `user.register`, `token.refresh` |
195
+ | `payment` | `payment.charge`, `payment.refund`, `subscription.create` |
196
+ | `order` | `order.create`, `order.update`, `order.cancel`, `order.ship` |
197
+ | `notification` | `email.send`, `sms.send`, `push.send` |
198
+ | `file` | `file.upload`, `file.download`, `file.delete` |
199
+ | `admin` | `user.ban`, `config.update`, `data.export` |
200
+ | `integration` | `webhook.receive`, `api.call`, `sync.complete` |
201
+
202
+ ## How It Works
203
+
204
+ 1. Logs are buffered in memory
205
+ 2. Buffer is flushed when it reaches `batchSize` or every `flushInterval` ms
206
+ 3. Logs are sent as a batch POST to `/api/app-logs/ingest` with Basic Auth
207
+ 4. On failure, retries with exponential backoff (200ms, 400ms, 800ms)
208
+ 5. Failed batches are re-added to the buffer if space is available
209
+ 6. Buffer has a hard cap (`maxBufferSize`) — oldest logs are dropped when full
210
+ 7. On shutdown, remaining buffer is flushed
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,11 @@
1
+ import { OnModuleDestroy } from '@nestjs/common';
2
+ import { AppLogClientConfig } from './interfaces/applog-config.interface';
3
+ import { AppLogClientService } from './applog.service';
4
+ /**
5
+ * NestJS-managed AppLog service. Wraps AppLogClientService with
6
+ * NestJS lifecycle hooks and the NestJS Logger.
7
+ */
8
+ export declare class AppLogNestService extends AppLogClientService implements OnModuleDestroy {
9
+ constructor(config: AppLogClientConfig);
10
+ onModuleDestroy(): Promise<void>;
11
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AppLogNestService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const applog_config_interface_1 = require("./interfaces/applog-config.interface");
18
+ const applog_service_1 = require("./applog.service");
19
+ /**
20
+ * NestJS-managed AppLog service. Wraps AppLogClientService with
21
+ * NestJS lifecycle hooks and the NestJS Logger.
22
+ */
23
+ let AppLogNestService = class AppLogNestService extends applog_service_1.AppLogClientService {
24
+ constructor(config) {
25
+ const nestLogger = new common_1.Logger('AppLog');
26
+ super({
27
+ ...config,
28
+ logger: {
29
+ debug: (msg) => nestLogger.debug(msg),
30
+ warn: (msg) => nestLogger.warn(msg),
31
+ error: (msg) => nestLogger.error(msg),
32
+ },
33
+ });
34
+ }
35
+ async onModuleDestroy() {
36
+ await this.shutdown();
37
+ }
38
+ };
39
+ exports.AppLogNestService = AppLogNestService;
40
+ exports.AppLogNestService = AppLogNestService = __decorate([
41
+ (0, common_1.Injectable)(),
42
+ __param(0, (0, common_1.Inject)(applog_config_interface_1.APPLOG_CLIENT_CONFIG)),
43
+ __metadata("design:paramtypes", [Object])
44
+ ], AppLogNestService);
@@ -0,0 +1,24 @@
1
+ import { CallHandler, ExecutionContext, NestInterceptor } from '@nestjs/common';
2
+ import { Observable } from 'rxjs';
3
+ import { AppLogNestService } from './applog-nestjs.service';
4
+ /**
5
+ * NestJS interceptor that automatically logs every HTTP request as an app log.
6
+ *
7
+ * Captures: controller name, handler name, method, path, status, duration, userId.
8
+ * Errors are logged at level 'error', successful requests at 'info'.
9
+ *
10
+ * @example
11
+ * // Global interceptor
12
+ * app.useGlobalInterceptors(app.get(AppLogInterceptor));
13
+ *
14
+ * // Or via module providers
15
+ * { provide: APP_INTERCEPTOR, useClass: AppLogInterceptor }
16
+ *
17
+ * // Or per-controller
18
+ * @UseInterceptors(AppLogInterceptor)
19
+ */
20
+ export declare class AppLogInterceptor implements NestInterceptor {
21
+ private readonly appLog;
22
+ constructor(appLog: AppLogNestService);
23
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
24
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AppLogInterceptor = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const rxjs_1 = require("rxjs");
15
+ const operators_1 = require("rxjs/operators");
16
+ const applog_nestjs_service_1 = require("./applog-nestjs.service");
17
+ /**
18
+ * NestJS interceptor that automatically logs every HTTP request as an app log.
19
+ *
20
+ * Captures: controller name, handler name, method, path, status, duration, userId.
21
+ * Errors are logged at level 'error', successful requests at 'info'.
22
+ *
23
+ * @example
24
+ * // Global interceptor
25
+ * app.useGlobalInterceptors(app.get(AppLogInterceptor));
26
+ *
27
+ * // Or via module providers
28
+ * { provide: APP_INTERCEPTOR, useClass: AppLogInterceptor }
29
+ *
30
+ * // Or per-controller
31
+ * @UseInterceptors(AppLogInterceptor)
32
+ */
33
+ let AppLogInterceptor = class AppLogInterceptor {
34
+ constructor(appLog) {
35
+ this.appLog = appLog;
36
+ }
37
+ intercept(context, next) {
38
+ if (this.appLog.getConfig().disabled)
39
+ return next.handle();
40
+ const ctx = context.switchToHttp();
41
+ const request = ctx.getRequest();
42
+ const controller = context.getClass().name;
43
+ const handler = context.getHandler().name;
44
+ const start = Date.now();
45
+ const userId = request.user?.id || request.user?.sub || undefined;
46
+ return next.handle().pipe((0, operators_1.tap)(() => {
47
+ const response = ctx.getResponse();
48
+ this.appLog.info('api', `${controller}.${handler}`, {
49
+ userId,
50
+ duration: Date.now() - start,
51
+ metadata: {
52
+ method: request.method,
53
+ path: request.url,
54
+ statusCode: response.statusCode,
55
+ },
56
+ });
57
+ }), (0, operators_1.catchError)((error) => {
58
+ const statusCode = error instanceof common_1.HttpException
59
+ ? error.getStatus()
60
+ : common_1.HttpStatus.INTERNAL_SERVER_ERROR;
61
+ this.appLog.error('api', `${controller}.${handler}`, {
62
+ message: error.message,
63
+ userId,
64
+ duration: Date.now() - start,
65
+ metadata: {
66
+ method: request.method,
67
+ path: request.url,
68
+ statusCode,
69
+ error: error.message,
70
+ },
71
+ });
72
+ return (0, rxjs_1.throwError)(() => error);
73
+ }));
74
+ }
75
+ };
76
+ exports.AppLogInterceptor = AppLogInterceptor;
77
+ exports.AppLogInterceptor = AppLogInterceptor = __decorate([
78
+ (0, common_1.Injectable)(),
79
+ __metadata("design:paramtypes", [applog_nestjs_service_1.AppLogNestService])
80
+ ], AppLogInterceptor);
@@ -0,0 +1,33 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { AppLogClientConfig } from './interfaces/applog-config.interface';
3
+ export declare class AppLogModule {
4
+ /**
5
+ * Register the app log client with static config.
6
+ *
7
+ * @example
8
+ * AppLogModule.register({
9
+ * apiUrl: 'http://localhost:4000',
10
+ * clientId: 'your-client-id',
11
+ * clientSecret: 'your-client-secret',
12
+ * })
13
+ */
14
+ static register(config: AppLogClientConfig): DynamicModule;
15
+ /**
16
+ * Register the app log client with async config (e.g. from ConfigService).
17
+ *
18
+ * @example
19
+ * AppLogModule.registerAsync({
20
+ * inject: [ConfigService],
21
+ * useFactory: (config: ConfigService) => ({
22
+ * apiUrl: config.get('MONITOR_API_URL'),
23
+ * clientId: config.get('APPLOG_CLIENT_ID'),
24
+ * clientSecret: config.get('APPLOG_CLIENT_SECRET'),
25
+ * }),
26
+ * })
27
+ */
28
+ static registerAsync(options: {
29
+ inject?: any[];
30
+ useFactory: (...args: any[]) => AppLogClientConfig | Promise<AppLogClientConfig>;
31
+ imports?: any[];
32
+ }): DynamicModule;
33
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var AppLogModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.AppLogModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const applog_config_interface_1 = require("./interfaces/applog-config.interface");
13
+ const applog_nestjs_service_1 = require("./applog-nestjs.service");
14
+ const applog_interceptor_1 = require("./applog.interceptor");
15
+ let AppLogModule = AppLogModule_1 = class AppLogModule {
16
+ /**
17
+ * Register the app log client with static config.
18
+ *
19
+ * @example
20
+ * AppLogModule.register({
21
+ * apiUrl: 'http://localhost:4000',
22
+ * clientId: 'your-client-id',
23
+ * clientSecret: 'your-client-secret',
24
+ * })
25
+ */
26
+ static register(config) {
27
+ return {
28
+ module: AppLogModule_1,
29
+ providers: [
30
+ { provide: applog_config_interface_1.APPLOG_CLIENT_CONFIG, useValue: config },
31
+ applog_nestjs_service_1.AppLogNestService,
32
+ applog_interceptor_1.AppLogInterceptor,
33
+ ],
34
+ exports: [applog_nestjs_service_1.AppLogNestService, applog_interceptor_1.AppLogInterceptor],
35
+ };
36
+ }
37
+ /**
38
+ * Register the app log client with async config (e.g. from ConfigService).
39
+ *
40
+ * @example
41
+ * AppLogModule.registerAsync({
42
+ * inject: [ConfigService],
43
+ * useFactory: (config: ConfigService) => ({
44
+ * apiUrl: config.get('MONITOR_API_URL'),
45
+ * clientId: config.get('APPLOG_CLIENT_ID'),
46
+ * clientSecret: config.get('APPLOG_CLIENT_SECRET'),
47
+ * }),
48
+ * })
49
+ */
50
+ static registerAsync(options) {
51
+ return {
52
+ module: AppLogModule_1,
53
+ imports: [...(options.imports || [])],
54
+ providers: [
55
+ {
56
+ provide: applog_config_interface_1.APPLOG_CLIENT_CONFIG,
57
+ inject: options.inject || [],
58
+ useFactory: options.useFactory,
59
+ },
60
+ applog_nestjs_service_1.AppLogNestService,
61
+ applog_interceptor_1.AppLogInterceptor,
62
+ ],
63
+ exports: [applog_nestjs_service_1.AppLogNestService, applog_interceptor_1.AppLogInterceptor],
64
+ };
65
+ }
66
+ };
67
+ exports.AppLogModule = AppLogModule;
68
+ exports.AppLogModule = AppLogModule = AppLogModule_1 = __decorate([
69
+ (0, common_1.Global)(),
70
+ (0, common_1.Module)({})
71
+ ], AppLogModule);
@@ -0,0 +1,55 @@
1
+ import { AppLogClientConfig } from './interfaces/applog-config.interface';
2
+ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
3
+ export interface AppLogPayload {
4
+ level: LogLevel;
5
+ category: string;
6
+ action: string;
7
+ message?: string;
8
+ metadata?: Record<string, any>;
9
+ userId?: string;
10
+ duration?: number;
11
+ tags?: string[];
12
+ createdAt?: string;
13
+ }
14
+ export interface AppLogOptions {
15
+ message?: string;
16
+ metadata?: Record<string, any>;
17
+ userId?: string;
18
+ duration?: number;
19
+ tags?: string[];
20
+ }
21
+ export declare class AppLogClientService {
22
+ private readonly config;
23
+ private buffer;
24
+ private flushTimer;
25
+ private flushing;
26
+ private readonly authHeader;
27
+ private readonly batchSize;
28
+ private readonly maxBufferSize;
29
+ private readonly timeout;
30
+ private readonly maxRetries;
31
+ private readonly defaultCategory?;
32
+ private readonly defaultTags;
33
+ private readonly log;
34
+ constructor(config: AppLogClientConfig);
35
+ debug(category: string, action: string, opts?: AppLogOptions): void;
36
+ info(category: string, action: string, opts?: AppLogOptions): void;
37
+ warn(category: string, action: string, opts?: AppLogOptions): void;
38
+ error(category: string, action: string, opts?: AppLogOptions): void;
39
+ fatal(category: string, action: string, opts?: AppLogOptions): void;
40
+ /**
41
+ * Start a timer. Returns a function that, when called, logs the action with the elapsed duration.
42
+ *
43
+ * @example
44
+ * const end = appLog.startTimer('payment', 'payment.charge', { userId: 'u1' });
45
+ * await processPayment();
46
+ * end(); // logs with duration automatically
47
+ */
48
+ startTimer(category: string, action: string, opts?: Omit<AppLogOptions, 'duration'>): (extra?: Partial<AppLogOptions>) => void;
49
+ push(log: AppLogPayload): void;
50
+ flush(): Promise<void>;
51
+ private sendWithRetry;
52
+ shutdown(): Promise<void>;
53
+ get bufferSize(): number;
54
+ getConfig(): AppLogClientConfig;
55
+ }
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppLogClientService = void 0;
4
+ // ─── Core Client ────────────────────────────────────────
5
+ class AppLogClientService {
6
+ constructor(config) {
7
+ this.config = config;
8
+ this.buffer = [];
9
+ this.flushTimer = null;
10
+ this.flushing = false;
11
+ this.batchSize = config.batchSize ?? 50;
12
+ this.maxBufferSize = config.maxBufferSize ?? 1000;
13
+ this.timeout = config.timeout ?? 10000;
14
+ this.maxRetries = config.maxRetries ?? 3;
15
+ this.defaultCategory = config.defaultCategory;
16
+ this.defaultTags = config.defaultTags ?? [];
17
+ this.log = {
18
+ debug: config.logger?.debug ?? (() => { }),
19
+ warn: config.logger?.warn ?? console.warn.bind(console),
20
+ error: config.logger?.error ?? console.error.bind(console),
21
+ };
22
+ if (config.disabled) {
23
+ this.authHeader = '';
24
+ return;
25
+ }
26
+ this.authHeader =
27
+ 'Basic ' + Buffer.from(`${config.clientId}:${config.clientSecret}`).toString('base64');
28
+ const interval = config.flushInterval ?? 5000;
29
+ this.flushTimer = setInterval(() => this.flush(), interval);
30
+ }
31
+ // ─── Convenience Methods ────────────────────────────────
32
+ debug(category, action, opts) {
33
+ this.push({ level: 'debug', category, action, ...opts });
34
+ }
35
+ info(category, action, opts) {
36
+ this.push({ level: 'info', category, action, ...opts });
37
+ }
38
+ warn(category, action, opts) {
39
+ this.push({ level: 'warn', category, action, ...opts });
40
+ }
41
+ error(category, action, opts) {
42
+ this.push({ level: 'error', category, action, ...opts });
43
+ }
44
+ fatal(category, action, opts) {
45
+ this.push({ level: 'fatal', category, action, ...opts });
46
+ }
47
+ // ─── Duration Tracking ─────────────────────────────────
48
+ /**
49
+ * Start a timer. Returns a function that, when called, logs the action with the elapsed duration.
50
+ *
51
+ * @example
52
+ * const end = appLog.startTimer('payment', 'payment.charge', { userId: 'u1' });
53
+ * await processPayment();
54
+ * end(); // logs with duration automatically
55
+ */
56
+ startTimer(category, action, opts) {
57
+ const start = Date.now();
58
+ return (extra) => {
59
+ const duration = Date.now() - start;
60
+ this.info(category, action, { ...opts, ...extra, duration });
61
+ };
62
+ }
63
+ // ─── Core Push ──────────────────────────────────────────
64
+ push(log) {
65
+ if (this.config.disabled)
66
+ return;
67
+ // Apply defaults
68
+ if (this.defaultCategory && !log.category) {
69
+ log.category = this.defaultCategory;
70
+ }
71
+ if (this.defaultTags.length > 0) {
72
+ log.tags = [...this.defaultTags, ...(log.tags ?? [])];
73
+ }
74
+ this.buffer.push(log);
75
+ // Enforce max buffer size — drop oldest
76
+ if (this.buffer.length > this.maxBufferSize) {
77
+ const dropped = this.buffer.length - this.maxBufferSize;
78
+ this.buffer = this.buffer.slice(dropped);
79
+ this.log.warn(`[AppLog] Buffer full, dropped ${dropped} oldest logs`);
80
+ }
81
+ if (this.buffer.length >= this.batchSize) {
82
+ this.flush().catch(() => { });
83
+ }
84
+ }
85
+ // ─── Flush ──────────────────────────────────────────────
86
+ async flush() {
87
+ if (this.buffer.length === 0 || this.flushing)
88
+ return;
89
+ if (!this.config.clientId || !this.config.clientSecret) {
90
+ this.log.warn('[AppLog] Credentials not configured, skipping flush');
91
+ return;
92
+ }
93
+ this.flushing = true;
94
+ const batch = this.buffer.splice(0, Math.min(this.buffer.length, 1000));
95
+ try {
96
+ await this.sendWithRetry(batch);
97
+ this.log.debug(`[AppLog] Flushed ${batch.length} logs`);
98
+ }
99
+ catch (err) {
100
+ this.log.error(`[AppLog] Failed to flush ${batch.length} logs after ${this.maxRetries} retries: ${err.message}`);
101
+ // Re-add failed batch if space available
102
+ const available = this.maxBufferSize - this.buffer.length;
103
+ if (available > 0) {
104
+ this.buffer.unshift(...batch.slice(0, available));
105
+ }
106
+ }
107
+ finally {
108
+ this.flushing = false;
109
+ }
110
+ }
111
+ // ─── Retry Logic ────────────────────────────────────────
112
+ async sendWithRetry(batch, attempt = 1) {
113
+ try {
114
+ const res = await fetch(`${this.config.apiUrl}/api/app-logs/ingest`, {
115
+ method: 'POST',
116
+ headers: {
117
+ 'Content-Type': 'application/json',
118
+ Authorization: this.authHeader,
119
+ },
120
+ body: JSON.stringify({ logs: batch }),
121
+ signal: AbortSignal.timeout(this.timeout),
122
+ });
123
+ if (!res.ok) {
124
+ const body = await res.text().catch(() => '');
125
+ throw new Error(`HTTP ${res.status}: ${body.slice(0, 200)}`);
126
+ }
127
+ }
128
+ catch (err) {
129
+ if (attempt < this.maxRetries) {
130
+ // Exponential backoff: 200ms, 400ms, 800ms...
131
+ const delay = 200 * Math.pow(2, attempt - 1);
132
+ await new Promise((r) => setTimeout(r, delay));
133
+ return this.sendWithRetry(batch, attempt + 1);
134
+ }
135
+ throw err;
136
+ }
137
+ }
138
+ // ─── Shutdown ───────────────────────────────────────────
139
+ async shutdown() {
140
+ if (this.flushTimer) {
141
+ clearInterval(this.flushTimer);
142
+ this.flushTimer = null;
143
+ }
144
+ await this.flush();
145
+ }
146
+ // ─── Stats ──────────────────────────────────────────────
147
+ get bufferSize() {
148
+ return this.buffer.length;
149
+ }
150
+ getConfig() {
151
+ return this.config;
152
+ }
153
+ }
154
+ exports.AppLogClientService = AppLogClientService;
@@ -0,0 +1,6 @@
1
+ export { AppLogClientService, AppLogPayload, AppLogOptions, LogLevel } from './applog.service';
2
+ export { AppLogClientConfig, APPLOG_CLIENT_CONFIG } from './interfaces/applog-config.interface';
3
+ export { createAppLogClient } from './standalone';
4
+ export { AppLogModule } from './applog.module';
5
+ export { AppLogNestService } from './applog-nestjs.service';
6
+ export { AppLogInterceptor } from './applog.interceptor';
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppLogInterceptor = exports.AppLogNestService = exports.AppLogModule = exports.createAppLogClient = exports.APPLOG_CLIENT_CONFIG = exports.AppLogClientService = void 0;
4
+ // Core client (works anywhere)
5
+ var applog_service_1 = require("./applog.service");
6
+ Object.defineProperty(exports, "AppLogClientService", { enumerable: true, get: function () { return applog_service_1.AppLogClientService; } });
7
+ var applog_config_interface_1 = require("./interfaces/applog-config.interface");
8
+ Object.defineProperty(exports, "APPLOG_CLIENT_CONFIG", { enumerable: true, get: function () { return applog_config_interface_1.APPLOG_CLIENT_CONFIG; } });
9
+ var standalone_1 = require("./standalone");
10
+ Object.defineProperty(exports, "createAppLogClient", { enumerable: true, get: function () { return standalone_1.createAppLogClient; } });
11
+ // NestJS integration
12
+ var applog_module_1 = require("./applog.module");
13
+ Object.defineProperty(exports, "AppLogModule", { enumerable: true, get: function () { return applog_module_1.AppLogModule; } });
14
+ var applog_nestjs_service_1 = require("./applog-nestjs.service");
15
+ Object.defineProperty(exports, "AppLogNestService", { enumerable: true, get: function () { return applog_nestjs_service_1.AppLogNestService; } });
16
+ var applog_interceptor_1 = require("./applog.interceptor");
17
+ Object.defineProperty(exports, "AppLogInterceptor", { enumerable: true, get: function () { return applog_interceptor_1.AppLogInterceptor; } });
@@ -0,0 +1,31 @@
1
+ export interface AppLogClientConfig {
2
+ /** The monitoring API base URL (e.g. http://localhost:4000) */
3
+ apiUrl: string;
4
+ /** Client ID from the monitoring environment credentials */
5
+ clientId: string;
6
+ /** Client Secret from the monitoring environment credentials */
7
+ clientSecret: string;
8
+ /** Batch size before auto-flushing (default: 50) */
9
+ batchSize?: number;
10
+ /** Flush interval in ms (default: 5000) */
11
+ flushInterval?: number;
12
+ /** Max buffer size to prevent memory leaks (default: 1000) */
13
+ maxBufferSize?: number;
14
+ /** Request timeout in ms (default: 10000) */
15
+ timeout?: number;
16
+ /** Max retry attempts on failure (default: 3) */
17
+ maxRetries?: number;
18
+ /** Default category for logs (optional) */
19
+ defaultCategory?: string;
20
+ /** Default tags added to every log (optional) */
21
+ defaultTags?: string[];
22
+ /** Disable all logging when true */
23
+ disabled?: boolean;
24
+ /** Custom logger for internal messages (default: console) */
25
+ logger?: {
26
+ debug?: (msg: string) => void;
27
+ warn?: (msg: string) => void;
28
+ error?: (msg: string) => void;
29
+ };
30
+ }
31
+ export declare const APPLOG_CLIENT_CONFIG = "APPLOG_CLIENT_CONFIG";
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.APPLOG_CLIENT_CONFIG = void 0;
4
+ exports.APPLOG_CLIENT_CONFIG = 'APPLOG_CLIENT_CONFIG';
@@ -0,0 +1,19 @@
1
+ import { AppLogClientConfig } from './interfaces/applog-config.interface';
2
+ import { AppLogClientService } from './applog.service';
3
+ /**
4
+ * Create a standalone AppLog client (no NestJS required).
5
+ * Works in any Node.js application, Express, Fastify, etc.
6
+ *
7
+ * @example
8
+ * const appLog = createAppLogClient({
9
+ * apiUrl: 'https://monitor.example.com',
10
+ * clientId: process.env.MONITOR_CLIENT_ID,
11
+ * clientSecret: process.env.MONITOR_CLIENT_SECRET,
12
+ * });
13
+ *
14
+ * appLog.info('auth', 'user.login', { userId: 'u1', message: 'Logged in' });
15
+ *
16
+ * // On app shutdown:
17
+ * await appLog.shutdown();
18
+ */
19
+ export declare function createAppLogClient(config: AppLogClientConfig): AppLogClientService;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAppLogClient = createAppLogClient;
4
+ const applog_service_1 = require("./applog.service");
5
+ /**
6
+ * Create a standalone AppLog client (no NestJS required).
7
+ * Works in any Node.js application, Express, Fastify, etc.
8
+ *
9
+ * @example
10
+ * const appLog = createAppLogClient({
11
+ * apiUrl: 'https://monitor.example.com',
12
+ * clientId: process.env.MONITOR_CLIENT_ID,
13
+ * clientSecret: process.env.MONITOR_CLIENT_SECRET,
14
+ * });
15
+ *
16
+ * appLog.info('auth', 'user.login', { userId: 'u1', message: 'Logged in' });
17
+ *
18
+ * // On app shutdown:
19
+ * await appLog.shutdown();
20
+ */
21
+ function createAppLogClient(config) {
22
+ return new applog_service_1.AppLogClientService(config);
23
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@devminister/applog-client",
3
+ "version": "0.0.1",
4
+ "description": "Application log client for the Monitoring platform. Buffered, batched delivery with retry. Works standalone or as a NestJS module.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "prepublishOnly": "tsc"
14
+ },
15
+ "keywords": [
16
+ "nestjs",
17
+ "logging",
18
+ "monitoring",
19
+ "application-logs",
20
+ "observability",
21
+ "audit-trail",
22
+ "structured-logging"
23
+ ],
24
+ "license": "MIT",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "peerDependencies": {
29
+ "@nestjs/common": "^10.0.0 || ^11.0.0",
30
+ "@nestjs/core": "^10.0.0 || ^11.0.0",
31
+ "rxjs": "^7.0.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "@nestjs/common": { "optional": true },
35
+ "@nestjs/core": { "optional": true },
36
+ "rxjs": { "optional": true }
37
+ },
38
+ "devDependencies": {
39
+ "@nestjs/common": "^11.1.3",
40
+ "@nestjs/core": "^11.1.3",
41
+ "@types/node": "^24.0.0",
42
+ "rxjs": "^7.8.2",
43
+ "typescript": "^5.8.3"
44
+ }
45
+ }