Skip to navigation

Implement logging in web api documents ai

Business Value  

  • Structured Logging: Consistent log format across all services for easier parsing and analysis  

  • Log Levels: Proper severity levels (debug, info, warn, error) for better filtering and alerting  

  • Centralized Logging: Integration with Azure Monitor and Application Insights  

  • Performance: Better performance compared to console.log in production environments  

  • Debugging: Child loggers with request context for better traceability  

  • Standards Compliance: Alignment with organizational logging standards  

 


Current State

Existing Console.log Usage

The DocumentAI service currently uses console.log statements throughout the codebase:

// Example patterns currently in use
console.log('Processing document:', documentId);
console.error('Failed to process document:', error);
console.warn('Missing optional field:', fieldName);
console.debug('Debug information:', data);
// In controllers
console.log('Received request:', req.body);
// In services
console.log('Calling OpenAI API...');
console.error('OpenAI API failed:', error.message);
// In utilities
console.log('Validating document format');

Issues with Current Approach

  1. No structured logging format - difficult to parse logs programmatically

  2. No log level filtering - all logs output at same level

  3. No request correlation - cannot trace logs across a request lifecycle

  4. Poor integration with Azure Monitor and Application Insights

  5. Inconsistent log formatting across the application

  6. No child logger support for request context

  7. Console output not optimized for production environments


Future State

Framework Logger Implementation

All logging will use the framework logger pattern established in fluxx-file-intake:

import { app } from './internal/app.js';
// Instead of: console.log('Processing document:', documentId);
app.log.info(`Processing document: ${documentId}`);
// Instead of: console.error('Failed to process:', error);
app.log.error('Failed to process document', error);
// Instead of: console.warn('Missing field:', field);
app.log.warn(`Missing optional field: ${field}`);
// Instead of: console.debug('Debug data:', data);
app.log.debug(`Debug information: ${JSON.stringify(data)}`);

Log Levels

  • debug: Detailed diagnostic information for troubleshooting

  • info: General informational messages about application flow

  • warn: Warning messages for potentially problematic situations

  • error: Error messages for failures and exceptions


Design

Architecture Pattern

Follow the exact pattern established in the fluxx-file-intake repository:

1. Logger Type Definition

Define a Logger interface that picks specific methods from the framework logger.

2. App Container

Central app container that holds the logger instance and makes it available throughout the application.

3. Missing Logger Placeholder

Placeholder that throws errors if logger is accessed before initialization.

4. Logger Registration

Function to register the framework logger into the app container during startup.

5. Usage Pattern

Import and use app.log throughout the codebase for all logging operations.

Component Architecture

┌─────────────────────────────────────────────────────────────┐
│                    DocumentAI Service                        │
├─────────────────────────────────────────────────────────────┤
│                                                               │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              app.log.info/warn/error/debug           │   │
│  │              Unified Logging API                      │   │
│  └────────────────────┬─────────────────────────────────┘   │
│                       │                                      │
│                       │                                      │
│  ┌────────────────────▼─────────────────────────────────┐   │
│  │         Framework Logger (Pino-based)                │   │
│  │         - Structured JSON output                     │   │
│  │         - Log level filtering                        │   │
│  │         - Child logger support                       │   │
│  └────────────────────┬─────────────────────────────────┘   │
│                       │                                      │
│  ┌────────────────────▼─────────────────────────────────┐   │
│  │         Azure Application Insights                   │   │
│  │         Azure Monitor Logs                           │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

Implementation Steps

Phase 1: Setup Logger Infrastructure

Task 1.1: Create Logger Type Definition

File: src/internal/Logger.d.ts

import { Logger as FrameworkLogger } from 'framework-nodejs-extensions';
export type Logger = Pick<FrameworkLogger, 'debug' | 'info' | 'warn' | 'error'>;

Rationale: Define a TypeScript interface that extracts only the logging methods we need from the framework logger. This provides type safety and clear contract.

Task 1.2: Create Missing Logger Placeholder

File: src/internal/frameworkAdapter/missingLogger.ts

import { Logger } from '../Logger.d.js';
const throwMissingLogger = () => {
    throw Error('Attempted to use the application log before one was registered');
};
export const missingLogger: Logger = {
    debug: throwMissingLogger,
    error: throwMissingLogger,
    info: throwMissingLogger,
    warn: throwMissingLogger,
};

Rationale: Provides fail-fast behavior if code attempts to log before the logger is properly initialized. This prevents silent failures and makes initialization errors obvious.

Task 1.3: Create Register Logger Function

File: src/internal/frameworkAdapter/registerLogger.ts

import { app } from '../app.js';
import { Logger } from '../Logger.d.js';
export const registerLogger = (frameworkApp: FrameworkAppWithLogger): void => {
    if (!frameworkApp.server) {
        throw Error('The logger registration attempted to register a logger from an app with no "server" assigned');
    }
    if (!frameworkApp.server.log) {
        throw Error('The logger registration attempted to register a logger from an app & server with no "log" assigned');
    }
    app.log = frameworkApp.server.log;
};
interface FrameworkAppWithLogger {
    server?: { log?: Logger }
}

Rationale: Safely extracts the framework logger and assigns it to our app container with proper validation to ensure the framework is fully initialized.

Task 1.4: Create App Container

File: src/internal/app.ts

import { Logger } from './Logger.d.js';
import { missingLogger } from './frameworkAdapter/missingLogger.js';
import { registerLogger } from './frameworkAdapter/registerLogger.js';
// A holder for all the important parts of the app that are attached to the
// frameworkApp in a DI container or as a property such as the logger
export const app: App = {
    log: missingLogger,
    registerLogger,
};
interface App {
    log: Logger;
    registerLogger: typeof registerLogger;
}

Rationale: Central container that provides application-wide access to the logger. Uses the missing logger as default until properly initialized.


Phase 2: Update Application Entry Point

Task 2.1: Initialize Logger in index.ts

File: src/index.ts

Add after framework initialization:

import { DevFramework } from 'framework-nodejs-core';
import { app } from './internal/app.js';
// Initialize the framework
const frameworkApp = await DevFramework.create();
// Register the logger (add this line)
app.registerLogger(frameworkApp);
// Log application startup
app.log.info('DocumentAI service starting...');
// Continue with rest of initialization...

Rationale: Register the logger as early as possible in the application lifecycle so it’s available for all subsequent initialization steps.


Phase 3: Migrate Console.log Statements

Task 3.1: Create Migration Checklist

Identify all files with console.log usage:

# Run this command to find all console.log usage
grep -r "console\." src/ --include="*.ts"

Common patterns to migrate:

| Current Pattern | New Pattern | Log Level |

|----------------|-------------|-----------|

| console.log('Info message') | app.log.info('Info message') | info |

| console.error('Error:', error) | app.log.error('Error message', error) | error |

| console.warn('Warning') | app.log.warn('Warning message') | warn |

| console.debug('Debug') | app.log.debug('Debug message') | debug |

| console.log('Starting...') | app.log.info('Starting...') | info |

Task 3.2: Migrate Controllers

Example transformation in a controller file:

Before:

export class DocumentController {
    async processDocument(req: Request, res: Response) {
        console.log('Received document processing request:', req.body);
       
        try {
            console.log('Processing document ID:', documentId);
            // ... processing logic
            console.log('Document processed successfully');
            return result;
        } catch (error) {
            console.error('Failed to process document:', error);
            throw error;
        }
    }
}

After:

import { app } from '../internal/app.js';
export class DocumentController {
    async processDocument(req: Request, res: Response) {
        app.log.info('Received document processing request', { body: req.body });
       
        try {
            app.log.info(`Processing document ID: ${documentId}`);
            // ... processing logic
            app.log.info('Document processed successfully');
            return result;
        } catch (error) {
            app.log.error('Failed to process document', error);
            throw error;
        }
    }
}

Task 3.3: Migrate Services

Example transformation in OpenAI service:

Before:

export class OpenAIService {
    async callOpenAI(prompt: string) {
        console.log('Calling OpenAI API with prompt:', prompt);
       
        try {
            const response = await this.client.chat.completions.create({
                model: this.model,
                messages: [{ role: 'user', content: prompt }],
            });
            console.log('OpenAI API response received');
            return response;
        } catch (error) {
            console.error('OpenAI API call failed:', error);
            throw error;
        }
    }
}

After:

import { app } from '../internal/app.js';
export class OpenAIService {
    async callOpenAI(prompt: string) {
        app.log.info('Calling OpenAI API', { promptLength: prompt.length });
       
        try {
            const response = await this.client.chat.completions.create({
                model: this.model,
                messages: [{ role: 'user', content: prompt }],
            });
            app.log.info('OpenAI API response received');
            return response;
        } catch (error) {
            app.log.error('OpenAI API call failed', error);
            throw error;
        }
    }
}

Task 3.4: Migrate Form Recognizer Service

Before:

export class FormRecognizerService {
    async analyzeDocument(documentUrl: string) {
        console.log('Analyzing document:', documentUrl);
        console.debug('Using model:', this.modelId);
       
        try {
            const poller = await this.client.beginAnalyzeDocument(this.modelId, documentUrl);
            console.log('Analysis started, polling for results...');
            const result = await poller.pollUntilDone();
            console.log('Document analysis completed');
            return result;
        } catch (error) {
            console.error('Document analysis failed:', error.message);
            throw error;
        }
    }
}

After:

import { app } from '../internal/app.js';
export class FormRecognizerService {
    async analyzeDocument(documentUrl: string) {
        app.log.info(`Analyzing document: ${documentUrl}`);
        app.log.debug(`Using model: ${this.modelId}`);
       
        try {
            const poller = await this.client.beginAnalyzeDocument(this.modelId, documentUrl);
            app.log.info('Analysis started, polling for results...');
            const result = await poller.pollUntilDone();
            app.log.info('Document analysis completed');
            return result;
        } catch (error) {
            app.log.error('Document analysis failed', error);
            throw error;
        }
    }
}

Task 3.5: Migrate Utility Functions

Before:

export const validateDocumentFormat = (document: Document): boolean => {
    console.log('Validating document format');
   
    if (!document.type) {
        console.warn('Document type is missing');
        return false;
    }
   
    if (!SUPPORTED_TYPES.includes(document.type)) {
        console.error('Unsupported document type:', document.type);
        return false;
    }
   
    console.log('Document format valid');
    return true;
};

After:

import { app } from '../internal/app.js';
export const validateDocumentFormat = (document: Document): boolean => {
    app.log.debug('Validating document format');
   
    if (!document.type) {
        app.log.warn('Document type is missing');
        return false;
    }
   
    if (!SUPPORTED_TYPES.includes(document.type)) {
        app.log.error(`Unsupported document type: ${document.type}`);
        return false;
    }
   
    app.log.debug('Document format valid');
    return true;
};

Task 3.6: Update Middleware

Before:

export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
    console.log(`${req.method} ${req.path}`);
    console.log('Headers:', req.headers);
    next();
};

After:

import { app } from '../internal/app.js';
export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
    app.log.info(`${req.method} ${req.path}`);
    app.log.debug('Request headers', { headers: req.headers });
    next();
};

Phase 4: Remove All Console.log Statements

Task 4.1: Add ESLint Rule

File: eslint.config.js or .eslintrc.json

{
  "rules": {
    "no-console": ["error", {
      "allow": []
    }]
  }
}

Rationale: Prevent future console.log usage by enforcing the rule at the linting level.

Task 4.2: Verify No Console.log Remains

# This should return 0 results after migration
grep -r "console\." src/ --include="*.ts"
npm run lint

Testing Strategy

Unit Tests

Test 1: Missing Logger Throws Error

File: test/unit/internal/frameworkAdapter/missingLogger.test.ts

import { expect, it } from 'vitest';
import { missingLogger as subject } from '../../../../src/internal/frameworkAdapter/missingLogger.js';
it('must throw when trying to log at debug level', async () => {
    expect(subject.debug).toThrowError(/log/i);
    expect(subject.debug).toThrowError(/registered/i);
});
it('must throw when trying to log at info level', async () => {
    expect(subject.info).toThrowError(/log/i);
    expect(subject.info).toThrowError(/registered/i);
});
it('must throw when trying to log at warn level', async () => {
    expect(subject.warn).toThrowError(/log/i);
    expect(subject.warn).toThrowError(/registered/i);
});
it('must throw when trying to log at error level', async () => {
    expect(subject.error).toThrowError(/log/i);
    expect(subject.error).toThrowError(/registered/i);
});

Purpose: Verify that attempting to use the logger before registration throws descriptive errors.

Test 2: Logger Registration Success

File: test/unit/internal/frameworkAdapter/registerLogger.test.ts

import { afterEach, expect, it, vi } from 'vitest';
import { mock } from 'vitest-mock-extended';
import { registerLogger as subject } from '../../../../src/internal/frameworkAdapter/registerLogger.js';
import { app } from '../../../../src/internal/app.js';
afterEach(() => {
    vi.restoreAllMocks();
});
it('must register Logger from the framework app.server if it is available', async () => {
    const mockFrameworkLogger = mock<typeof app.log>();
    subject({ server: { log: mockFrameworkLogger } });
    expect(app.log).toBe(mockFrameworkLogger);
});
it('must throw if the app.server.log is undefined', async () => {
    expect(() => subject({ server: {} })).toThrowError();
});
it('must throw if the app.server is undefined', async () => {
    expect(() => subject({})).toThrowError();
});

Purpose: Verify that logger registration correctly assigns the framework logger and validates inputs.

Test 3: Logger Usage in Services

File: test/unit/services/openAIService.test.ts

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { OpenAIService } from '../../../src/services/openAIService.js';
import { app } from '../../../src/internal/app.js';
describe('OpenAIService', () => {
    beforeEach(() => {
        vi.spyOn(app.log, 'info').mockImplementation(() => {});
        vi.spyOn(app.log, 'error').mockImplementation(() => {});
    });
    it('should log info when calling OpenAI API', async () => {
        const service = new OpenAIService();
        await service.callOpenAI('test prompt');
        expect(app.log.info).toHaveBeenCalledWith(
            expect.stringContaining('Calling OpenAI API'),
            expect.any(Object)
        );
    });
    it('should log error when OpenAI API fails', async () => {
        const service = new OpenAIService();
        const error = new Error('API Error');
       
        // Mock API to throw error
        vi.spyOn(service.client.chat.completions, 'create').mockRejectedValue(error);
        await expect(service.callOpenAI('test')).rejects.toThrow();
        expect(app.log.error).toHaveBeenCalledWith('OpenAI API call failed', error);
    });
});

Purpose: Verify that services correctly use the logger and log at appropriate levels.

Test 4: Logger Usage in Controllers

File: test/unit/controllers/documentController.test.ts

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { DocumentController } from '../../../src/controllers/documentController.js';
import { app } from '../../../src/internal/app.js';
describe('DocumentController', () => {
    let logInfoSpy: ReturnType<typeof vi.spyOn>;
    let logErrorSpy: ReturnType<typeof vi.spyOn>;
    beforeEach(() => {
        logInfoSpy = vi.spyOn(app.log, 'info').mockImplementation(() => {});
        logErrorSpy = vi.spyOn(app.log, 'error').mockImplementation(() => {});
    });
    it('should log request received at info level', async () => {
        const controller = new DocumentController();
        const mockRequest = { body: { documentId: '123' } };
        await controller.processDocument(mockRequest, mockResponse);
        expect(logInfoSpy).toHaveBeenCalledWith(
            'Received document processing request',
            expect.any(Object)
        );
    });
    it('should log errors at error level', async () => {
        const controller = new DocumentController();
        const error = new Error('Processing failed');
       
        // Mock service to throw error
        vi.spyOn(controller.service, 'process').mockRejectedValue(error);
        await expect(controller.processDocument(mockRequest, mockResponse)).rejects.toThrow();
        expect(logErrorSpy).toHaveBeenCalledWith('Failed to process document', error);
    });
});

Purpose: Verify that controllers log requests and errors appropriately.

Test 5: No Console.log Usage

File: test/unit/noConsoleLogs.test.ts

import { describe, it, expect } from 'vitest';
import { execSync } from 'child_process';
describe('Console.log Usage', () => {
    it('should not contain any console.log statements in source code', () => {
        try {
            // This will throw if grep finds matches
            execSync('grep -r "console\\." src/ --include="*.ts"', { encoding: 'utf-8' });
            expect.fail('Found console.log statements in source code');
        } catch (error) {
            // grep returns non-zero exit code when no matches found (expected)
            if (error.status === 1) {
                expect(true).toBe(true);
            } else {
                throw error;
            }
        }
    });
    it('should have no-console ESLint rule enabled', () => {
        const eslintConfig = require('../../../eslint.config.ts');
        const noConsoleRule = eslintConfig.rules['no-console'];
       
        expect(noConsoleRule).toBeDefined();
        expect(noConsoleRule[0]).toBe('error');
    });
});

Purpose: Automated verification that no console.log statements exist after migration.

Integration Tests

Test 6: Logger Integration with Framework

File: test/integration/logger.test.ts

import { describe, it, expect, beforeAll } from 'vitest';
import { DevFramework } from 'framework-nodejs-core';
import { app } from '../../src/internal/app.js';
describe('Logger Integration', () => {
    beforeAll(async () => {
        const frameworkApp = await DevFramework.create();
        app.registerLogger(frameworkApp);
    });
    it('should have a registered logger after initialization', () => {
        expect(app.log).toBeDefined();
        expect(app.log.info).toBeDefined();
        expect(app.log.error).toBeDefined();
        expect(app.log.warn).toBeDefined();
        expect(app.log.debug).toBeDefined();
    });
    it('should not throw when logging at any level', () => {
        expect(() => app.log.info('Test info')).not.toThrow();
        expect(() => app.log.warn('Test warn')).not.toThrow();
        expect(() => app.log.error('Test error')).not.toThrow();
        expect(() => app.log.debug('Test debug')).not.toThrow();
    });
    it('should support logging with objects', () => {
        expect(() => app.log.info('Test with object', { key: 'value' })).not.toThrow();
    });
});

Purpose: Verify that the logger integrates correctly with the framework and supports all required operations.

Contract Tests

Test 7: Logger Output Format

File: test/contract/logger-format.test.ts

import { describe, it, expect, beforeAll } from 'vitest';
import { DevFramework } from 'framework-nodejs-core';
import { app } from '../../src/internal/app.js';
describe('Logger Output Format', () => {
    beforeAll(async () => {
        const frameworkApp = await DevFramework.create();
        app.registerLogger(frameworkApp);
    });
    it('should produce structured JSON logs', () => {
        // Capture log output
        const logSpy = vi.spyOn(process.stdout, 'write');
       
        app.log.info('Test message');
       
        const logOutput = logSpy.mock.calls[0][0].toString();
        const logJson = JSON.parse(logOutput);
       
        expect(logJson).toHaveProperty('level');
        expect(logJson).toHaveProperty('time');
        expect(logJson).toHaveProperty('msg');
        expect(logJson.msg).toBe('Test message');
       
        logSpy.mockRestore();
    });
    it('should include error stack traces in error logs', () => {
        const logSpy = vi.spyOn(process.stdout, 'write');
        const testError = new Error('Test error');
       
        app.log.error('Error occurred', testError);
       
        const logOutput = logSpy.mock.calls[0][0].toString();
        const logJson = JSON.parse(logOutput);
       
        expect(logJson).toHaveProperty('err');
        expect(logJson.err).toHaveProperty('stack');
        expect(logJson.err.message).toBe('Test error');
       
        logSpy.mockRestore();
    });
});

Purpose: Verify that the logger produces correctly formatted structured logs.


Acceptance Criteria

Functional Requirements

  • All console.log statements replaced with app.log.info

  • All console.error statements replaced with app.log.error

  • All console.warn statements replaced with app.log.warn

  • All console.debug statements replaced with app.log.debug

  • Logger initialized in application entry point (index.ts)

  • Logger available throughout application via app.log

  • Structured JSON logs produced in all environments

  • Error objects logged with full stack traces

Technical Requirements

  • Logger.d.ts type definition created

  • missingLogger.ts placeholder created

  • registerLogger.ts function created

  • app.ts container updated with logger

  • No direct console.* usage in source code

  • TypeScript type safety maintained for logger

  • Logger registered before any logging operations

  • Proper error handling for missing logger

Code Quality Requirements

  • ESLint no-console rule enabled and enforced

  • All files import app.log where logging is needed

  • Log messages are clear and descriptive

  • Appropriate log levels used (debug for verbose, info for general, warn for issues, error for failures)

  • Sensitive data not logged (passwords, tokens, PII)

  • Log messages follow consistent format and style

Testing Requirements

  • Unit tests for missingLogger implementation

  • Unit tests for registerLogger function

  • Unit tests verify services use logger correctly

  • Unit tests verify controllers use logger correctly

  • Integration tests verify logger framework integration

  • Contract tests verify structured log format

  • Automated test to verify no console.log usage

  • All tests pass in CI/CD pipeline

  • Test coverage > 80% for logger modules

Documentation Requirements

  • Code comments explain logger initialization

  • README updated with logging guidelines

  • Examples of proper log usage documented

  • Migration completed and documented

  • Team trained on new logging pattern

Deployment Requirements

  • Application starts successfully with new logger

  • Logs appear in Azure Application Insights

  • Log levels filter correctly in production

  • No performance degradation from logging

  • Rollback plan documented


Non-Functional Requirements

Performance

  • Logger operations should add < 1ms overhead per log statement

  • Structured logging should not significantly impact throughput

  • Application startup time should not increase > 100ms

Reliability

  • Application should fail fast with clear error if logger not initialized

  • Logger should handle errors gracefully without crashing application

  • Logging failures should not prevent application operation

Observability

  • All logs should include timestamp, level, and message

  • Error logs should include stack traces

  • Logs should be parseable by Azure Monitor

  • Log correlation IDs should be preserved across async operations

Maintainability

  • Log statements should be self-documenting

  • Log levels should be consistent across codebase

  • Adding logging should be straightforward for new developers

  • Logger interface should remain stable across updates


Migration Patterns

Pattern 1: Simple Info Logs

// Before
console.log('Operation completed');
// After
app.log.info('Operation completed');

Pattern 2: Logs with Variables

// Before
console.log('Processing user:', userId);
// After
app.log.info(`Processing user: ${userId}`);

Pattern 3: Error Logging

// Before
console.error('Failed to save:', error);
// After
app.log.error('Failed to save document', error);

Pattern 4: Structured Logging with Context

// Before
console.log('User action:', action, 'by user:', userId);
// After
app.log.info('User action completed', { action, userId });

Pattern 5: Debug Logging

// Before
console.debug('Cache state:', cacheState);
// After
app.log.debug(`Cache state: ${JSON.stringify(cacheState)}`);

Pattern 6: Warning Logs

// Before
console.warn('Deprecated API used');
// After
app.log.warn('Deprecated API used - please migrate to v2');

Risks and Mitigation

Risk 1: Missing Console.log Statements

Impact: Medium - Some logs might be missed during migration

Mitigation:

  • Use grep/ripgrep to find all console.* usage

  • Enable ESLint no-console rule to prevent regressions

  • Code review checklist includes verification

  • Automated test to detect console.log usage

Risk 2: Incorrect Log Levels

Impact: Low - Logs at wrong severity level

Mitigation:

  • Document log level guidelines clearly

  • Code review focuses on appropriate log levels

  • Team training on when to use each level

Risk 3: Performance Impact

Impact: Low - Increased logging overhead

Mitigation:

  • Benchmark logging performance

  • Use appropriate log levels (avoid excessive debug logs)

  • Configure log levels per environment

Risk 4: Breaking Changes During Migration

Impact: Medium - Application errors during deployment

Mitigation:

  • Test thoroughly in development environment

  • Gradual migration file by file

  • Maintain backward compatibility during transition

  • Feature flag for new logger if needed


Rollout Plan

Phase 1: Infrastructure (Week 1)

  1. Create logger infrastructure files

  2. Update application entry point

  3. Deploy to development environment

  4. Verify logger initialization

  5. Run integration tests

Phase 2: Core Services Migration (Week 2)

  1. Migrate OpenAI service

  2. Migrate Form Recognizer service

  3. Migrate storage services

  4. Test services individually

  5. Deploy to QA environment

Phase 3: Controllers and Utilities (Week 3)

  1. Migrate controllers

  2. Migrate utility functions

  3. Migrate middleware

  4. Enable ESLint no-console rule

  5. Deploy to UAT environment

Phase 4: Cleanup and Production (Week 4)

  1. Verify no console.log statements remain

  2. Complete all tests

  3. Update documentation

  4. Deploy to production

  5. Monitor logs in Azure Application Insights


Success Metrics

Technical Metrics

  • Zero console.log statements in source code

  • 100% of log statements use framework logger

  • All log levels used appropriately (info, warn, error, debug)

  • Log output appears in Azure Application Insights

  • No logging-related runtime errors

Quality Metrics

  • Test coverage > 80% for logger modules

  • All unit tests passing

  • All integration tests passing

  • ESLint no-console rule enforced

  • Code review approval on all changes

Operational Metrics

  • Application starts successfully with new logger

  • Logs searchable in Azure Monitor

  • Log queries work with structured format

  • No performance degradation (< 5% overhead)

  • Mean time to find logs reduced by 50%

Team Metrics

  • All developers trained on new logging pattern

  • Documentation complete and reviewed

  • Zero questions about how to log after training

  • Consistent log usage across all new code



Checklist for Developers

During migration, verify each file:

  • Replace all console.log with app.log.info

  • Replace all console.error with app.log.error

  • Replace all console.warn with app.log.warn

  • Replace all console.debug with app.log.debug

  • Import app from ./internal/app.js

  • Use appropriate log level for message severity

  • Include context objects for structured logging

  • Do not log sensitive data (passwords, tokens, PII)

  • Test that logging works correctly

  • Verify ESLint passes with no console warnings


Story Points: 8

Complexity: Medium

Effort: 2 weeks

Dependencies: Framework-nodejs-logging, Framework-nodejs-extensions


Definition of Done

  • All acceptance criteria met

  • All console.log statements migrated

  • Code reviewed and approved

  • All tests passing (unit, integration, contract)

  • ESLint no-console rule enabled

  • Deployed to all environments

  • Documentation updated

  • Team training completed

  • Logs visible in Azure Application Insights

  • Post-deployment validation successful

  • Stakeholder sign-off received