npm package: @notel/logger
Description: A structured semantic logging framework for Next.js with Sitecore integration and Splunk output support
Author: Quinagh Applications
Latest version: 0.0.2

# @notel/logger

A powerful structured/semantic logging framework for Next.js applications with runtime configuration, namespace filtering, log rotation, Sitecore integration, and Splunk-compatible output.

## Features

- **Runtime Configuration**: Change log levels, namespaces, and settings at runtime without restarting your application
- **Namespace Filtering**: Fine-grained control over what gets logged with wildcard support (`app.*`) and exclusions (`-debug.*`)
- **Log Rotation**: Automatic log rotation based on file size or schedule (cron)
- **Common Context Properties**: Inject application name, ID, environment, and custom properties into all log entries
- **Sitecore Integration**: Fetch and hot-reload logger configuration from Sitecore GraphQL endpoints
- **Splunk-Compatible Output**: Built-in formatters for Splunk ingestion
- **Multiple Transports**: Console, file, and rotating file transports
- **TypeScript First**: Full TypeScript support with comprehensive type definitions
- **Next.js Optimized**: Seamless integration with Next.js App Router and API routes

## Installation

```bash
npm install @notel/logger
```

## Quick Start

### Basic Usage

```typescript
import { NotelLogger, LogLevel } from '@notel/logger';

const logger = new NotelLogger('app', {
  level: LogLevel.INFO,
  context: {
    applicationName: 'MyApp',
    applicationId: 'app-001',
    environment: 'production',
  },
});

logger.info('Application started');
logger.debug('Debug info', { userId: 123 });
logger.error('Error occurred', new Error('Something went wrong'));
```

### Next.js Integration

**lib/logger.ts**:
```typescript
import { initializeLogger, LogLevel } from '@notel/logger';

export async function setupLogger() {
  await initializeLogger(undefined, {
    level: LogLevel.INFO,
    context: {
      applicationName: 'NextJS App',
      environment: process.env.NODE_ENV,
    },
    transports: [
      {
        type: 'console',
        level: LogLevel.DEBUG,
      },
      {
        type: 'rotating-file',
        rotation: {
          logDir: './logs',
          filename: 'app.log',
          maxSize: 10 * 1024 * 1024, // 10MB
          maxFiles: 5,
          schedule: '0 0 * * *', // Daily rotation
        },
      },
    ],
  });
}
```

**app/layout.tsx**:
```typescript
import { setupLogger } from '../lib/logger';

setupLogger();

export default function RootLayout({ children }) {
  return <html><body>{children}</body></html>;
}
```

**app/api/users/route.ts**:
```typescript
import { getLogger } from '@notel/logger';

export async function GET() {
  const logger = getLogger('api.users');

  logger.info('Fetching users');

  try {
    const users = await fetchUsers();
    logger.info('Users fetched', { count: users.length });
    return Response.json({ users });
  } catch (error) {
    logger.error('Failed to fetch users', error);
    return Response.json({ error: 'Failed' }, { status: 500 });
  }
}
```

## Core Concepts

### Log Levels

Five log levels in order of severity:
- `DEBUG`: Detailed debugging information
- `INFO`: General informational messages
- `WARN`: Warning messages
- `ERROR`: Error messages
- `FATAL`: Fatal error messages

### Namespaces

Namespaces organize your logs hierarchically:

```typescript
const rootLogger = new NotelLogger('root');
const dbLogger = rootLogger.child('database');
const userService = rootLogger.child('api.users');

dbLogger.info('Connected');          // namespace: root.database
userService.info('User created');    // namespace: root.api.users
```

### Namespace Filtering

Control which namespaces are logged:

```typescript
const logger = new NotelLogger('root', {
  // Include these namespaces
  namespaces: ['app.*', 'service.users', 'api.*'],

  // Exclude these namespaces (prefix with '-')
  excludeNamespaces: ['-app.debug', '-test.*'],
});
```

**Examples**:
- `app.*` matches `app.service`, `app.controller` but NOT `app.service.users`
- `app.**` matches `app.service`, `app.service.users`, `app.service.users.create` (all levels)
- `-app.debug` excludes exactly `app.debug`
- `-test.*` excludes all namespaces starting with `test.`

### Context Properties

Add common properties to all log entries:

```typescript
const logger = new NotelLogger('app', {
  context: {
    applicationName: 'MyApp',
    applicationId: 'app-001',
    environment: 'production',
    version: '1.0.0',
    region: 'us-east-1',
  },
});

// All logs will include these context properties
logger.info('User logged in', { userId: 123 });
// Output includes: applicationName, applicationId, environment, version, region, userId
```

## Transports

### Console Transport

Outputs logs to stdout/stderr with colored formatting:

```typescript
import { ConsoleTransport, LogLevel } from '@notel/logger';

const logger = new NotelLogger('app', {
  transports: [
    {
      type: 'console',
      level: LogLevel.DEBUG, // Only log DEBUG and above to console
    },
  ],
});
```

### File Transport

Writes logs to a file:

```typescript
const logger = new NotelLogger('app', {
  transports: [
    {
      type: 'file',
      rotation: {
        logDir: './logs',
        filename: 'app.log',
      },
    },
  ],
});
```

### Rotating File Transport

Automatically rotates logs based on size or schedule:

```typescript
import { RotatingFileTransport } from '@notel/logger';

const logger = new NotelLogger('app', {
  transports: [
    {
      type: 'rotating-file',
      rotation: {
        logDir: './logs',
        filename: 'app.log',
        maxSize: 10 * 1024 * 1024,  // Rotate at 10MB
        maxFiles: 5,                 // Keep 5 most recent files
        schedule: '0 0 * * *',       // Also rotate daily at midnight (cron format)
      },
    },
  ],
});
```

**Cron Schedule Examples**:
- `'0 0 * * *'` - Daily at midnight
- `'0 * * * *'` - Every hour
- `'0 0 * * 0'` - Weekly (Sunday at midnight)
- `'0 0 1 * *'` - Monthly (1st of month at midnight)

## Formatters

### Splunk Formatter

Outputs logs in Splunk-compatible JSON format:

```typescript
import { SplunkFormatter } from '@notel/logger';

const logger = new NotelLogger('app', {
  transports: [
    {
      type: 'file',
      formatter: new SplunkFormatter(),
      rotation: {
        logDir: './logs',
        filename: 'splunk.log',
      },
    },
  ],
});
```

Output format:
```json
{
  "time": 1699564800.123,
  "event": {
    "message": "User logged in",
    "level": "info",
    "namespace": "app.auth",
    "userId": 123,
    "applicationName": "MyApp",
    "environment": "production"
  },
  "source": "app.auth",
  "sourcetype": "_json"
}
```

### JSON Formatter

Simple one-JSON-object-per-line format:

```typescript
import { JsonFormatter } from '@notel/logger';

const logger = new NotelLogger('app', {
  transports: [
    {
      type: 'file',
      formatter: new JsonFormatter(),
      rotation: { logDir: './logs', filename: 'app.log' },
    },
  ],
});
```

### Text Formatter

Human-readable format with colors:

```typescript
import { TextFormatter } from '@notel/logger';

const logger = new NotelLogger('app', {
  transports: [
    {
      type: 'console',
      formatter: new TextFormatter(true), // true = use colors
    },
  ],
});
```

## Runtime Configuration

Update logger configuration without restarting:

```typescript
const logger = new NotelLogger('app', {
  level: LogLevel.INFO,
  namespaces: ['app.*'],
});

// Later, change configuration
logger.configure({
  level: LogLevel.DEBUG,          // Now log debug messages
  namespaces: ['app.*', 'api.*'], // Add api.* namespace
  excludeNamespaces: ['-app.test'], // Exclude app.test
});

// Or enable/disable logging entirely
logger.setEnabled(false); // Stop logging
logger.setEnabled(true);  // Resume logging
```

## Sitecore Integration

Fetch and auto-reload logger configuration from Sitecore:

### Setup Sitecore Item

Create a Sitecore item with these fields:
- `logLevel` (Single-Line Text): "debug", "info", "warn", "error", or "fatal"
- `namespaces` (Single-Line Text): Comma-separated list, e.g., "app.*,service.*"
- `excludeNamespaces` (Single-Line Text): Comma-separated list, e.g., "-app.debug,-test.*"
- `applicationName` (Single-Line Text): Your application name
- `applicationId` (Single-Line Text): Unique application identifier
- `environment` (Single-Line Text): Environment name (dev, staging, prod, etc.)
- `enabled` (Checkbox): Enable/disable logging

### Initialize Logger with Sitecore Provider

```typescript
import { initializeLogger, createSitecoreProvider, LogLevel } from '@notel/logger';

const sitecoreProvider = createSitecoreProvider(
  'https://your-sitecore.com/api/graphql',
  'your-api-key',
  '/sitecore/content/settings/logger-config',
  {
    pollInterval: 60000, // Check for updates every 60 seconds
    fallbackConfig: {
      level: LogLevel.INFO,
      context: {
        applicationName: 'MyApp',
        environment: 'production',
      },
    },
  }
);

await initializeLogger(sitecoreProvider);
```

### Benefits

- **Centralized Management**: Control logging across all applications from Sitecore
- **No Deployment**: Change log levels without code changes or redeployments
- **Hot Reload**: Configuration updates apply automatically (within poll interval)
- **Environment-Specific**: Different Sitecore items for dev/staging/production
- **Audit Trail**: Sitecore tracks all configuration changes

## Environment Variables

Configure logger via environment variables:

```bash
# Log level
LOG_LEVEL=info

# Namespaces (comma-separated)
LOG_NAMESPACES=app.*,service.*,api.*

# Exclude namespaces (comma-separated)
LOG_EXCLUDE_NAMESPACES=-app.debug,-test.*

# Context properties
APP_NAME="My Application"
APP_ID=app-001
ENVIRONMENT=production
NODE_ENV=production

# Enable/disable logging
LOG_ENABLED=true
```

Use with EnvConfigProvider:

```typescript
import { initializeLogger, EnvConfigProvider } from '@notel/logger';

await initializeLogger(new EnvConfigProvider());
```

## API Reference

### NotelLogger

Main logger class.

```typescript
class NotelLogger implements Logger {
  constructor(namespace: string, config?: LoggerConfig);

  child(namespace: string): Logger;
  debug(message: string, data?: Record<string, any>): void;
  info(message: string, data?: Record<string, any>): void;
  warn(message: string, data?: Record<string, any>): void;
  error(message: string, error?: Error, data?: Record<string, any>): void;
  fatal(message: string, error?: Error, data?: Record<string, any>): void;
  configure(config: Partial<LoggerConfig>): void;
  getConfig(): LoggerConfig;
  setEnabled(enabled: boolean): void;
  close(): void;
}
```

### Next.js Integration Functions

```typescript
// Initialize logger on app startup
function initializeLogger(
  provider?: ConfigProvider,
  initialConfig?: LoggerConfig
): Promise<NotelLogger>;

// Get logger instance (creates default if not initialized)
function getLogger(namespace?: string): NotelLogger;

// Shutdown and cleanup
function shutdownLogger(): void;

// API route wrapper with automatic logging
function withLogging(
  handler: Function,
  namespace?: string
): Function;
```

### Configuration Types

```typescript
interface LoggerConfig {
  level?: LogLevel;
  namespaces?: string[];
  excludeNamespaces?: string[];
  context?: LogContext;
  transports?: TransportConfig[];
  enabled?: boolean;
}

interface LogContext {
  applicationName?: string;
  applicationId?: string;
  environment?: string;
  hostname?: string;
  pid?: number;
  [key: string]: any;
}

interface TransportConfig {
  type: 'console' | 'file' | 'rotating-file';
  level?: LogLevel;
  rotation?: RotationConfig;
  formatter?: Formatter;
}

interface RotationConfig {
  maxSize?: number;      // Bytes
  schedule?: string;     // Cron expression
  maxFiles?: number;     // Number of files to keep
  logDir?: string;       // Directory path
  filename?: string;     // Base filename
}
```

## Examples

See the `/examples` directory for complete working examples:

- **basic-usage**: Simple examples of all core features
- **nextjs-app**: Full Next.js application with logging
- **sitecore-integration**: Next.js app with Sitecore configuration provider

## Best Practices

1. **Use Namespaces**: Organize logs hierarchically for easy filtering
2. **Structured Data**: Include relevant data objects rather than string concatenation
3. **Appropriate Levels**: Use the right log level for each message
4. **Context Properties**: Set application-wide context once, not in every log call
5. **Error Handling**: Always pass Error objects to `error()` and `fatal()` for stack traces
6. **Production Config**: Use higher log levels (INFO/WARN) in production
7. **Log Rotation**: Configure rotation to prevent disk space issues
8. **Sitecore Config**: Use Sitecore for production to change behavior without deployments

## Performance Considerations

- Disabled namespaces are filtered early (minimal overhead)
- File writes are asynchronous (non-blocking)
- JSON formatting is fast (native JSON.stringify)
- Context properties are shallow-copied (not deep-cloned)

## License

MIT

## Contributing

Contributions welcome! Please open an issue or PR.