@cepseudo/adonis-audit-log 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.md ADDED
@@ -0,0 +1,9 @@
1
+ # The MIT License
2
+
3
+ Copyright (c) 2023
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,190 @@
1
+ # @cepseudo/adonis-audit-log
2
+
3
+ Simple audit logging package for AdonisJS 6.
4
+
5
+ ## Features
6
+
7
+ - Custom audit logs for tracking user actions and business events
8
+ - Automatic HTTP request logging via middleware
9
+ - Error logging with exception handler integration
10
+ - Configurable field sanitization for sensitive data
11
+ - Retention settings for log cleanup
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @cepseudo/adonis-audit-log
17
+ ```
18
+
19
+ ## Configuration
20
+
21
+ ```bash
22
+ node ace configure @cepseudo/adonis-audit-log
23
+ ```
24
+
25
+ This will:
26
+
27
+ 1. Publish migrations to `database/migrations/`
28
+ 2. Publish config to `config/audit.ts`
29
+ 3. Register the provider in `adonisrc.ts`
30
+
31
+ Then run the migrations:
32
+
33
+ ```bash
34
+ node ace migration:run
35
+ ```
36
+
37
+ ## Configuration Options
38
+
39
+ ```typescript
40
+ // config/audit.ts
41
+ import { defineConfig } from '@cepseudo/adonis-audit-log'
42
+
43
+ export default defineConfig({
44
+ enabled: true,
45
+
46
+ requestLog: {
47
+ enabled: true,
48
+ excludeRoutes: ['/health', '/metrics'],
49
+ excludeMethods: ['OPTIONS'],
50
+ logBody: false,
51
+ logQuery: true,
52
+ sanitizeFields: ['password', 'token', 'secret'],
53
+ },
54
+
55
+ errorLog: {
56
+ enabled: true,
57
+ excludeStatusCodes: [404],
58
+ includeStack: true,
59
+ },
60
+
61
+ auditLog: {
62
+ enabled: true,
63
+ },
64
+
65
+ retention: {
66
+ requestLogs: 30,
67
+ errorLogs: 90,
68
+ auditLogs: 365,
69
+ },
70
+ })
71
+ ```
72
+
73
+ ## Usage
74
+
75
+ ### Custom Audit Logs
76
+
77
+ ```typescript
78
+ import audit from '@cepseudo/adonis-audit-log/services/main'
79
+
80
+ // Simple log
81
+ await audit.log('user.login', { user_id: 1 })
82
+
83
+ // With metadata
84
+ await audit.log('submission.create', {
85
+ user_id: auth.user.id,
86
+ submission_id: submission.id,
87
+ project_acronym: submission.projectAcronym,
88
+ })
89
+
90
+ // Track changes
91
+ await audit.logChange('user.update', {
92
+ userId: auth.user.id,
93
+ targetId: targetUser.id,
94
+ changes: {
95
+ email: { from: 'old@example.com', to: 'new@example.com' },
96
+ },
97
+ })
98
+ ```
99
+
100
+ ### Request Logging (Middleware)
101
+
102
+ Add the middleware to your router in `start/kernel.ts`:
103
+
104
+ ```typescript
105
+ router.use([() => import('@cepseudo/adonis-audit-log/middleware/request_logger')])
106
+ ```
107
+
108
+ ### Error Logging (Exception Handler)
109
+
110
+ Integrate with your exception handler in `app/exceptions/handler.ts`:
111
+
112
+ ```typescript
113
+ import { AuditErrorLogger } from '@cepseudo/adonis-audit-log'
114
+
115
+ export default class HttpExceptionHandler extends ExceptionHandler {
116
+ async report(error: unknown, ctx: HttpContext) {
117
+ await AuditErrorLogger.log(error, ctx)
118
+ return super.report(error, ctx)
119
+ }
120
+ }
121
+ ```
122
+
123
+ ## Database Schema
124
+
125
+ The package creates three tables:
126
+
127
+ ### audit_logs
128
+
129
+ - `id` - Primary key
130
+ - `action_type` - Event type (e.g., 'user.login', 'submission.create')
131
+ - `metadata` - JSON object with additional data
132
+ - `user_id` - Optional reference to users table
133
+ - `created_at` - Timestamp
134
+
135
+ ### request_logs
136
+
137
+ - `id` - Primary key
138
+ - `method` - HTTP method
139
+ - `url` - Request URL
140
+ - `route_name` - Named route if available
141
+ - `status_code` - HTTP response status
142
+ - `response_time_ms` - Response time in milliseconds
143
+ - `ip` - Client IP address
144
+ - `user_agent` - Browser/client user agent
145
+ - `user_id` - Optional reference to users table
146
+ - `request_body` - Sanitized request body (optional)
147
+ - `request_query` - Query parameters
148
+ - `created_at` - Timestamp
149
+
150
+ ### error_logs
151
+
152
+ - `id` - Primary key
153
+ - `error_type` - Error class name
154
+ - `message` - Error message
155
+ - `stack` - Stack trace (optional)
156
+ - `url` - URL where error occurred
157
+ - `method` - HTTP method
158
+ - `status_code` - HTTP status returned
159
+ - `user_id` - Optional reference to users table
160
+ - `context` - Additional context (params, query)
161
+ - `created_at` - Timestamp
162
+
163
+ ## Development
164
+
165
+ ```bash
166
+ # Install dependencies
167
+ npm install
168
+
169
+ # Run tests
170
+ npm run test
171
+
172
+ # Run tests without linting
173
+ npm run quick:test
174
+
175
+ # Build
176
+ npm run build
177
+
178
+ # Type check
179
+ npm run typecheck
180
+
181
+ # Lint
182
+ npm run lint
183
+
184
+ # Format
185
+ npm run format
186
+ ```
187
+
188
+ ## License
189
+
190
+ MIT
@@ -0,0 +1,2 @@
1
+ import ConfigureCommand from '@adonisjs/core/commands/configure';
2
+ export declare function configure(command: ConfigureCommand): Promise<void>;
@@ -0,0 +1,58 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Configure hook
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | The configure hook is called when someone runs "node ace configure <package>"
7
+ | command. It publishes migrations, config, and registers the provider.
8
+ |
9
+ */
10
+ import { stubsRoot } from './stubs/main.js';
11
+ export async function configure(command) {
12
+ const codemods = await command.createCodemods();
13
+ // Publish config file
14
+ await codemods.makeUsingStub(stubsRoot, 'config.stub', {});
15
+ // Publish migrations
16
+ const now = new Date();
17
+ const timestamp = now
18
+ .toISOString()
19
+ .replace(/[-:]/g, '')
20
+ .replace('T', '_')
21
+ .replace(/\.\d{3}Z$/, '');
22
+ await codemods.makeUsingStub(stubsRoot, 'migrations/create_audit_logs_table.stub', {
23
+ migration: {
24
+ folder: 'database/migrations',
25
+ fileName: `${timestamp}_create_audit_logs_table.ts`,
26
+ },
27
+ });
28
+ // Add 1 second to timestamp for ordering
29
+ const timestamp2 = new Date(now.getTime() + 1000)
30
+ .toISOString()
31
+ .replace(/[-:]/g, '')
32
+ .replace('T', '_')
33
+ .replace(/\.\d{3}Z$/, '');
34
+ await codemods.makeUsingStub(stubsRoot, 'migrations/create_request_logs_table.stub', {
35
+ migration: {
36
+ folder: 'database/migrations',
37
+ fileName: `${timestamp2}_create_request_logs_table.ts`,
38
+ },
39
+ });
40
+ // Add 2 seconds to timestamp for ordering
41
+ const timestamp3 = new Date(now.getTime() + 2000)
42
+ .toISOString()
43
+ .replace(/[-:]/g, '')
44
+ .replace('T', '_')
45
+ .replace(/\.\d{3}Z$/, '');
46
+ await codemods.makeUsingStub(stubsRoot, 'migrations/create_error_logs_table.stub', {
47
+ migration: {
48
+ folder: 'database/migrations',
49
+ fileName: `${timestamp3}_create_error_logs_table.ts`,
50
+ },
51
+ });
52
+ // Register provider in adonisrc.ts
53
+ await codemods.updateRcFile((rcFile) => {
54
+ rcFile.addProvider('@cepseudo/adonis-audit-log/providers/audit_provider');
55
+ });
56
+ command.logger.success('Audit log package configured successfully!');
57
+ command.logger.info('Run "node ace migration:run" to create the audit tables.');
58
+ }
@@ -0,0 +1,9 @@
1
+ export { configure } from './configure.js';
2
+ export { defineConfig } from './src/types.js';
3
+ export { AuditService } from './src/audit_service.js';
4
+ export { RequestLogger } from './src/request_logger.js';
5
+ export { ErrorLogger, AuditErrorLogger } from './src/error_logger.js';
6
+ export { default as AuditLog } from './src/models/audit_log.js';
7
+ export { default as RequestLog } from './src/models/request_log.js';
8
+ export { default as ErrorLog } from './src/models/error_log.js';
9
+ export type { AuditConfig, AuditLogEntry, RequestLogEntry, ErrorLogEntry } from './src/types.js';
package/build/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Package entrypoint
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Export values from the package entrypoint.
7
+ |
8
+ */
9
+ export { configure } from './configure.js';
10
+ export { defineConfig } from './src/types.js';
11
+ export { AuditService } from './src/audit_service.js';
12
+ export { RequestLogger } from './src/request_logger.js';
13
+ export { ErrorLogger, AuditErrorLogger } from './src/error_logger.js';
14
+ export { default as AuditLog } from './src/models/audit_log.js';
15
+ export { default as RequestLog } from './src/models/request_log.js';
16
+ export { default as ErrorLog } from './src/models/error_log.js';
@@ -0,0 +1,5 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { NextFn } from '@adonisjs/core/types/http';
3
+ export default class RequestLoggerMiddleware {
4
+ handle(ctx: HttpContext, next: NextFn): Promise<void>;
5
+ }
@@ -0,0 +1,36 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Request Logger Middleware
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | This middleware logs HTTP requests automatically. Add it to your router
7
+ | middleware stack in start/kernel.ts.
8
+ |
9
+ */
10
+ import app from '@adonisjs/core/services/app';
11
+ import { RequestLogger } from '../src/request_logger.js';
12
+ let requestLogger = null;
13
+ export default class RequestLoggerMiddleware {
14
+ async handle(ctx, next) {
15
+ const startTime = performance.now();
16
+ // Execute the request
17
+ await next();
18
+ // Log the request after response (non-blocking)
19
+ try {
20
+ if (!requestLogger) {
21
+ const config = app.config.get('audit');
22
+ if (config) {
23
+ requestLogger = new RequestLogger(config);
24
+ }
25
+ }
26
+ if (requestLogger) {
27
+ const responseTimeMs = Math.round(performance.now() - startTime);
28
+ // Use async version to not block the response
29
+ requestLogger.logAsync(ctx, responseTimeMs);
30
+ }
31
+ }
32
+ catch (error) {
33
+ console.error('[RequestLoggerMiddleware] Error:', error);
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,13 @@
1
+ import type { ApplicationService } from '@adonisjs/core/types';
2
+ import { AuditService } from '../src/audit_service.js';
3
+ declare module '@adonisjs/core/types' {
4
+ interface ContainerBindings {
5
+ audit: AuditService;
6
+ }
7
+ }
8
+ export default class AuditProvider {
9
+ protected app: ApplicationService;
10
+ constructor(app: ApplicationService);
11
+ register(): void;
12
+ boot(): Promise<void>;
13
+ }
@@ -0,0 +1,52 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Audit Provider
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | This provider registers the audit service in the container and initializes
7
+ | the error logger.
8
+ |
9
+ */
10
+ import { AuditService } from '../src/audit_service.js';
11
+ import { AuditErrorLogger } from '../src/error_logger.js';
12
+ export default class AuditProvider {
13
+ app;
14
+ constructor(app) {
15
+ this.app = app;
16
+ }
17
+ register() {
18
+ this.app.container.singleton('audit', () => {
19
+ const config = this.app.config.get('audit', {
20
+ enabled: true,
21
+ requestLog: {
22
+ enabled: true,
23
+ excludeRoutes: [],
24
+ excludeMethods: ['OPTIONS'],
25
+ logBody: false,
26
+ logQuery: true,
27
+ sanitizeFields: ['password', 'token', 'secret'],
28
+ },
29
+ errorLog: {
30
+ enabled: true,
31
+ excludeStatusCodes: [404],
32
+ includeStack: true,
33
+ },
34
+ auditLog: {
35
+ enabled: true,
36
+ },
37
+ retention: {
38
+ requestLogs: 30,
39
+ errorLogs: 90,
40
+ auditLogs: 365,
41
+ },
42
+ });
43
+ return new AuditService(config);
44
+ });
45
+ }
46
+ async boot() {
47
+ const config = this.app.config.get('audit');
48
+ if (config) {
49
+ AuditErrorLogger.init(config);
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,3 @@
1
+ import { AuditService } from '../src/audit_service.js';
2
+ declare let audit: AuditService;
3
+ export { audit as default };
@@ -0,0 +1,15 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Audit Service Singleton
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | This file exports the audit service singleton that can be imported
7
+ | anywhere in the application.
8
+ |
9
+ */
10
+ import app from '@adonisjs/core/services/app';
11
+ let audit;
12
+ await app.booted(async () => {
13
+ audit = await app.container.make('audit');
14
+ });
15
+ export { audit as default };
@@ -0,0 +1,43 @@
1
+ import type { AuditConfig } from './types.js';
2
+ import AuditLog from './models/audit_log.js';
3
+ export declare class AuditService {
4
+ #private;
5
+ protected config: AuditConfig;
6
+ constructor(config: AuditConfig);
7
+ /**
8
+ * Check if logging is enabled
9
+ */
10
+ get isEnabled(): boolean;
11
+ /**
12
+ * Create an audit log entry (fire-and-forget, non-blocking)
13
+ */
14
+ logAsync(actionType: string, metadata?: Record<string, unknown>): void;
15
+ /**
16
+ * Create an audit log entry (blocking, returns the created log)
17
+ */
18
+ log(actionType: string, metadata?: Record<string, unknown>): Promise<AuditLog | null>;
19
+ /**
20
+ * Log with change tracking (fire-and-forget)
21
+ */
22
+ logChangeAsync(actionType: string, options: {
23
+ userId?: number | null;
24
+ targetId?: number | string;
25
+ changes: Record<string, {
26
+ from: unknown;
27
+ to: unknown;
28
+ }>;
29
+ extra?: Record<string, unknown>;
30
+ }): void;
31
+ /**
32
+ * Log with change tracking (blocking)
33
+ */
34
+ logChange(actionType: string, options: {
35
+ userId?: number | null;
36
+ targetId?: number | string;
37
+ changes: Record<string, {
38
+ from: unknown;
39
+ to: unknown;
40
+ }>;
41
+ extra?: Record<string, unknown>;
42
+ }): Promise<AuditLog | null>;
43
+ }
@@ -0,0 +1,76 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Audit Service
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Main service for creating custom audit logs. Provides a simple API
7
+ | for logging user actions and business events.
8
+ |
9
+ */
10
+ import AuditLog from './models/audit_log.js';
11
+ export class AuditService {
12
+ config;
13
+ constructor(config) {
14
+ this.config = config;
15
+ }
16
+ /**
17
+ * Check if logging is enabled
18
+ */
19
+ get isEnabled() {
20
+ return this.config.enabled && this.config.auditLog.enabled;
21
+ }
22
+ /**
23
+ * Create an audit log entry (fire-and-forget, non-blocking)
24
+ */
25
+ logAsync(actionType, metadata = {}) {
26
+ if (!this.isEnabled) {
27
+ return;
28
+ }
29
+ setImmediate(() => {
30
+ this.#createLog(actionType, metadata).catch((error) => {
31
+ console.error('[AuditService] Failed to log:', error);
32
+ });
33
+ });
34
+ }
35
+ /**
36
+ * Create an audit log entry (blocking, returns the created log)
37
+ */
38
+ async log(actionType, metadata = {}) {
39
+ if (!this.isEnabled) {
40
+ return null;
41
+ }
42
+ return this.#createLog(actionType, metadata);
43
+ }
44
+ /**
45
+ * Log with change tracking (fire-and-forget)
46
+ */
47
+ logChangeAsync(actionType, options) {
48
+ this.logAsync(actionType, {
49
+ user_id: options.userId,
50
+ target_id: options.targetId,
51
+ changes: options.changes,
52
+ ...options.extra,
53
+ });
54
+ }
55
+ /**
56
+ * Log with change tracking (blocking)
57
+ */
58
+ async logChange(actionType, options) {
59
+ return this.log(actionType, {
60
+ user_id: options.userId,
61
+ target_id: options.targetId,
62
+ changes: options.changes,
63
+ ...options.extra,
64
+ });
65
+ }
66
+ async #createLog(actionType, metadata) {
67
+ const userId = (metadata.user_id ?? metadata.userId ?? null);
68
+ // Create clean metadata without user_id fields
69
+ const cleanMetadata = Object.fromEntries(Object.entries(metadata).filter(([key]) => key !== 'user_id' && key !== 'userId'));
70
+ return AuditLog.create({
71
+ actionType,
72
+ metadata: cleanMetadata,
73
+ userId,
74
+ });
75
+ }
76
+ }
@@ -0,0 +1,35 @@
1
+ import type { HttpContext } from '@adonisjs/core/http';
2
+ import type { AuditConfig } from './types.js';
3
+ import ErrorLog from './models/error_log.js';
4
+ export declare class ErrorLogger {
5
+ #private;
6
+ protected config: AuditConfig;
7
+ constructor(config: AuditConfig);
8
+ /**
9
+ * Check if the error should be logged
10
+ */
11
+ shouldLog(error: unknown, ctx?: HttpContext): boolean;
12
+ /**
13
+ * Log an error (fire-and-forget, non-blocking)
14
+ */
15
+ logAsync(error: unknown, ctx?: HttpContext): void;
16
+ /**
17
+ * Log an error (blocking, returns the created log)
18
+ */
19
+ log(error: unknown, ctx?: HttpContext): Promise<ErrorLog | null>;
20
+ }
21
+ /**
22
+ * Static helper for easy integration with exception handler
23
+ */
24
+ export declare const AuditErrorLogger: {
25
+ instance: ErrorLogger | null;
26
+ init(config: AuditConfig): void;
27
+ /**
28
+ * Log error asynchronously (non-blocking)
29
+ */
30
+ logAsync(error: unknown, ctx?: HttpContext): void;
31
+ /**
32
+ * Log error and wait for result
33
+ */
34
+ log(error: unknown, ctx?: HttpContext): Promise<ErrorLog | null>;
35
+ };
@@ -0,0 +1,106 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Error Logger
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Logic for logging application errors. Designed to be integrated with
7
+ | the AdonisJS exception handler.
8
+ |
9
+ */
10
+ import { getUserIdFromContext, getErrorStatusCode, isHttpError } from './types.js';
11
+ import ErrorLog from './models/error_log.js';
12
+ export class ErrorLogger {
13
+ config;
14
+ #excludedStatusCodes;
15
+ constructor(config) {
16
+ this.config = config;
17
+ // Pre-compute set for O(1) lookup
18
+ this.#excludedStatusCodes = new Set(config.errorLog.excludeStatusCodes);
19
+ }
20
+ /**
21
+ * Check if the error should be logged
22
+ */
23
+ shouldLog(error, ctx) {
24
+ if (!this.config.enabled || !this.config.errorLog.enabled) {
25
+ return false;
26
+ }
27
+ // Check if status code is excluded (O(1) lookup)
28
+ if (ctx) {
29
+ const statusCode = getErrorStatusCode(error) ?? ctx.response.getStatus();
30
+ if (this.#excludedStatusCodes.has(statusCode)) {
31
+ return false;
32
+ }
33
+ }
34
+ return true;
35
+ }
36
+ /**
37
+ * Log an error (fire-and-forget, non-blocking)
38
+ */
39
+ logAsync(error, ctx) {
40
+ if (!this.shouldLog(error, ctx)) {
41
+ return;
42
+ }
43
+ setImmediate(() => {
44
+ this.#createLog(error, ctx).catch((err) => {
45
+ console.error('[ErrorLogger] Failed to log error:', err);
46
+ });
47
+ });
48
+ }
49
+ /**
50
+ * Log an error (blocking, returns the created log)
51
+ */
52
+ async log(error, ctx) {
53
+ if (!this.shouldLog(error, ctx)) {
54
+ return null;
55
+ }
56
+ return this.#createLog(error, ctx);
57
+ }
58
+ async #createLog(error, ctx) {
59
+ const err = isHttpError(error) ? error : new Error(String(error));
60
+ const entry = {
61
+ errorType: err.constructor?.name ?? 'Error',
62
+ message: err.message,
63
+ stack: this.config.errorLog.includeStack ? (err.stack ?? null) : null,
64
+ url: ctx?.request.url(true) ?? null,
65
+ method: ctx?.request.method() ?? null,
66
+ statusCode: err.status ?? ctx?.response.getStatus() ?? 500,
67
+ userId: ctx ? getUserIdFromContext(ctx) : null,
68
+ context: ctx
69
+ ? {
70
+ params: ctx.params,
71
+ query: ctx.request.qs(),
72
+ }
73
+ : null,
74
+ };
75
+ return ErrorLog.create(entry);
76
+ }
77
+ }
78
+ /**
79
+ * Static helper for easy integration with exception handler
80
+ */
81
+ export const AuditErrorLogger = {
82
+ instance: null,
83
+ init(config) {
84
+ this.instance = new ErrorLogger(config);
85
+ },
86
+ /**
87
+ * Log error asynchronously (non-blocking)
88
+ */
89
+ logAsync(error, ctx) {
90
+ if (!this.instance) {
91
+ console.warn('[AuditErrorLogger] Not initialized. Call AuditErrorLogger.init() first.');
92
+ return;
93
+ }
94
+ this.instance.logAsync(error, ctx);
95
+ },
96
+ /**
97
+ * Log error and wait for result
98
+ */
99
+ async log(error, ctx) {
100
+ if (!this.instance) {
101
+ console.warn('[AuditErrorLogger] Not initialized. Call AuditErrorLogger.init() first.');
102
+ return null;
103
+ }
104
+ return this.instance.log(error, ctx);
105
+ },
106
+ };