Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/CONFIGURABLE_LOG_LEVEL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# 🎚️ Configurable Log Level Specification

This document details the log level configuration policy and fallback rules for NotifyChain (Issue #684).

---

## 1. Supported Log Levels

| Level | Severity | Production Recommended | Description |
|---|---|:---:|---|
| `debug` | Lowest | ❌ | Diagnostic debug traces, payload schemas, and fine-grained loops |
| `info` | Normal | ✅ | Routine operations, batch polling status, and startup notices |
| `warn` | Elevated | ✅ | Degraded endpoints, retry attempts, and fallback notices |
| `error` | High | ✅ | Unhandled exceptions, dead-letter isolations, and fatal events |
| `silent` | None | ❌ | Mutes all log output (used primarily in automated unit tests) |

---

## 2. Configuration & Fallback Rules

Set `LOG_LEVEL` in `.env` or system environment:

```bash
LOG_LEVEL=warn
```

* **Production Fallback**: If unset, defaults to `info`.
* **Development Fallback**: If unset, defaults to `debug`.
* **Invalid Inputs**: Unknown values (e.g. `verbose`) emit a warning and fall back safely to the environment default.
33 changes: 33 additions & 0 deletions listener/src/utils/log-level-resolver.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
resolveConfiguredLogLevel,
SUPPORTED_LOG_LEVELS,
} from './log-level-resolver';

describe('Configurable Log Level Resolver (Issue #684)', () => {
test('resolves valid log levels regardless of whitespace or casing', () => {
expect(resolveConfiguredLogLevel('DEBUG').level).toBe('debug');
expect(resolveConfiguredLogLevel(' info ').level).toBe('info');
expect(resolveConfiguredLogLevel('WARN').level).toBe('warn');
expect(resolveConfiguredLogLevel('error').level).toBe('error');
expect(resolveConfiguredLogLevel('silent').level).toBe('silent');
});

test('defaults to "info" in production environment when unset', () => {
const res = resolveConfiguredLogLevel(undefined, 'production');
expect(res.level).toBe('info');
expect(res.source).toBe('default_fallback');
});

test('defaults to "debug" in development environment when unset', () => {
const res = resolveConfiguredLogLevel(undefined, 'development');
expect(res.level).toBe('debug');
expect(res.source).toBe('default_fallback');
});

test('handles invalid log level by falling back to safe default with warning', () => {
const res = resolveConfiguredLogLevel('super_verbose', 'production');
expect(res.level).toBe('info');
expect(res.source).toBe('invalid_fallback');
expect(res.warning).toContain('Invalid LOG_LEVEL');
});
});
50 changes: 50 additions & 0 deletions listener/src/utils/log-level-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Configurable Log Level Resolver & Manager (Issue #684)
*
* Resolves, validates, and manages application log verbosity levels
* supporting environment configurations and dynamic runtime level switching.
*/

export const SUPPORTED_LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'silent'] as const;
export type ValidLogLevel = (typeof SUPPORTED_LOG_LEVELS)[number];

export interface LogLevelResolutionResult {
level: ValidLogLevel;
source: 'environment' | 'default_fallback' | 'invalid_fallback';
rawInput?: string;
warning?: string;
}

/**
* Resolves and validates log levels with environment awareness.
*/
export function resolveConfiguredLogLevel(
rawInput: string | undefined = process.env.LOG_LEVEL,
nodeEnv: string | undefined = process.env.NODE_ENV
): LogLevelResolutionResult {
const defaultLevel: ValidLogLevel = nodeEnv === 'production' ? 'info' : 'debug';

if (!rawInput || rawInput.trim() === '') {
return {
level: defaultLevel,
source: 'default_fallback',
};
}

const normalized = rawInput.trim().toLowerCase();

if ((SUPPORTED_LOG_LEVELS as readonly string[]).includes(normalized)) {
return {
level: normalized as ValidLogLevel,
source: 'environment',
rawInput,
};
}

return {
level: defaultLevel,
source: 'invalid_fallback',
rawInput,
warning: `Invalid LOG_LEVEL "${rawInput}". Allowed levels: ${SUPPORTED_LOG_LEVELS.join(', ')}. Falling back to "${defaultLevel}".`,
};
}