Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .changeset/late-animals-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@asgardeo/javascript': patch
'@asgardeo/nextjs': patch
---

Move to a session cookie to give the ability to do route level scope based authorization.
150 changes: 150 additions & 0 deletions docs/developer/LOGGER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Logger Utility

A universal logging utility that works seamlessly in both browser and Node.js environments with beautiful formatting, colors, and flexible configuration.

## Features

- 🌐 **Universal**: Works in both browser and Node.js environments
- 🎨 **Beautiful Output**: Colored terminal output (Node.js) and styled console output (browser)
- � **Package-Aware**: Support for package-specific and component-specific loggers
- �📊 **Log Levels**: DEBUG, INFO, WARN, ERROR, SILENT
- 🏷️ **Prefixes**: Customizable prefixes with support for nested component loggers
- ⏰ **Timestamps**: Optional timestamp inclusion
- 🔧 **Configurable**: Flexible configuration options
- 🎯 **Type Safe**: Full TypeScript support

## Basic Usage

```typescript
import logger, { LogLevel } from '@asgardeo/javascript';

// Basic logging with package-specific prefix
logger.info('Application started');
// Output: 🛡️ Asgardeo - @asgardeo/javascript [INFO] Application started

logger.warn('This is a warning');
logger.error('Something went wrong');
logger.debug('Debug information'); // Only shown if level is DEBUG

// Configure log level
logger.setLevel(LogLevel.DEBUG);
logger.debug('Now debug messages will show');

// Set to production level
logger.setLevel(LogLevel.WARN); // Only WARN and ERROR will show
```

## Named Function Exports

```typescript
import { info, warn, error, debug } from '@asgardeo/javascript';

info('Quick info message');
warn('Quick warning');
error('Quick error');
debug('Quick debug');
```

## Package-Specific Loggers

```typescript
import { createPackageLogger, createPackageComponentLogger } from '@asgardeo/javascript';

// Create logger for specific package
const nextjsLogger = createPackageLogger('@asgardeo/nextjs');
nextjsLogger.info('Next.js package message');
// Output: 🛡️ Asgardeo - @asgardeo/nextjs [INFO] Next.js package message

const reactLogger = createPackageLogger('@asgardeo/react');
reactLogger.error('React package error');
// Output: 🛡️ Asgardeo - @asgardeo/react [ERROR] React package error

// Create logger for package + component
const nextAuthLogger = createPackageComponentLogger('@asgardeo/nextjs', 'Authentication');
nextAuthLogger.info('User signed in');
// Output: 🛡️ Asgardeo - @asgardeo/nextjs - Authentication [INFO] User signed in
```

## Custom Logger Configuration

```typescript
import { createLogger, LogLevel } from '@asgardeo/javascript';

const customLogger = createLogger({
level: LogLevel.DEBUG,
prefix: '🛡️ Asgardeo - MyCustomApp',
timestamps: true,
showLevel: true,
});

customLogger.info('Custom configured message');
// Output: [2025-01-10T10:30:45.123Z] 🛡️ Asgardeo - MyCustomApp [INFO] Custom configured message
```

## Component-Specific Loggers

```typescript
import { createComponentLogger } from '@asgardeo/javascript';

// Create loggers for different components (uses default package prefix)
const authLogger = createComponentLogger('Authentication');
const apiLogger = createComponentLogger('API');
const uiLogger = createComponentLogger('UI');

authLogger.info('User signed in successfully');
// Output: 🛡️ Asgardeo - @asgardeo/javascript - Authentication [INFO] User signed in successfully

apiLogger.error('API request failed');
// Output: 🛡️ Asgardeo - @asgardeo/javascript - API [ERROR] API request failed

uiLogger.debug('Rendering component');
// Output: 🛡️ Asgardeo - @asgardeo/javascript - UI [DEBUG] Rendering component
```

## Real-World Package Examples

### Next.js Package Usage

```typescript
import { createPackageComponentLogger } from '@asgardeo/javascript';

const authLogger = createPackageComponentLogger('@asgardeo/nextjs', 'Authentication');
const sessionLogger = createPackageComponentLogger('@asgardeo/nextjs', 'SessionManager');

export class NextAuthService {
async signIn(credentials: Credentials): Promise<User> {
authLogger.info('Starting Next.js sign-in process', { username: credentials.username });

try {
sessionLogger.debug('Creating session cookie');
const user = await this.performSignIn(credentials);
authLogger.info('Next.js sign-in successful', { userId: user.id });
return user;
} catch (error) {
authLogger.error('Next.js sign-in failed', { error: error.message, username: credentials.username });
throw error;
}
}
}
```

### Multi-Package Application

```typescript
// In @asgardeo/nextjs package
import { createPackageComponentLogger } from '@asgardeo/javascript';
const nextLogger = createPackageComponentLogger('@asgardeo/nextjs', 'Provider');

// In @asgardeo/react package
import { createPackageComponentLogger } from '@asgardeo/javascript';
const reactLogger = createPackageComponentLogger('@asgardeo/react', 'Hook');

// In @asgardeo/node package
import { createPackageComponentLogger } from '@asgardeo/javascript';
const nodeLogger = createPackageComponentLogger('@asgardeo/node', 'Client');

// Each will have distinct, identifiable output:
// 🛡️ Asgardeo - @asgardeo/nextjs - Provider [INFO] ...
// 🛡️ Asgardeo - @asgardeo/react - Hook [INFO] ...
// 🛡️ Asgardeo - @asgardeo/node - Client [INFO] ...
```
15 changes: 15 additions & 0 deletions packages/javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,19 @@ export {default as processOpenIDScopes} from './utils/processOpenIDScopes';
export {default as withVendorCSSClassPrefix} from './utils/withVendorCSSClassPrefix';
export {default as transformBrandingPreferenceToTheme} from './utils/transformBrandingPreferenceToTheme';

export {
default as logger,
createLogger,
createComponentLogger,
createPackageLogger,
createPackageComponentLogger,
LogLevel,
configure as configureLogger,
debug,
info,
warn,
error,
} from './utils/logger';
export type {LoggerConfig} from './utils/logger';

export {default as StorageManager} from './StorageManager';
153 changes: 153 additions & 0 deletions packages/javascript/src/utils/__tests__/logger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/**
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
*
* WSO2 LLC. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
* in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import logger, {createLogger, createComponentLogger, LogLevel} from '../logger';

describe('Logger', () => {
beforeEach(() => {
// Reset console methods before each test
jest.spyOn(console, 'log').mockImplementation();
jest.spyOn(console, 'info').mockImplementation();
jest.spyOn(console, 'warn').mockImplementation();
jest.spyOn(console, 'error').mockImplementation();
jest.spyOn(console, 'debug').mockImplementation();
});

afterEach(() => {
jest.restoreAllMocks();
});

describe('Basic logging', () => {
it('should log info messages', () => {
logger.info('Test info message');
expect(console.info).toHaveBeenCalled();
});

it('should log warning messages', () => {
logger.warn('Test warning message');
expect(console.warn).toHaveBeenCalled();
});

it('should log error messages', () => {
logger.error('Test error message');
expect(console.error).toHaveBeenCalled();
});

it('should log debug messages when level is DEBUG', () => {
logger.setLevel(LogLevel.DEBUG);
logger.debug('Test debug message');
expect(console.debug).toHaveBeenCalled();
});

it('should not log debug messages when level is INFO', () => {
logger.setLevel(LogLevel.INFO);
logger.debug('Test debug message');
expect(console.debug).not.toHaveBeenCalled();
});
});

describe('Log levels', () => {
it('should respect log level filtering', () => {
logger.setLevel(LogLevel.WARN);

logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');

expect(console.debug).not.toHaveBeenCalled();
expect(console.info).not.toHaveBeenCalled();
expect(console.warn).toHaveBeenCalled();
expect(console.error).toHaveBeenCalled();
});

it('should silence all logs when level is SILENT', () => {
logger.setLevel(LogLevel.SILENT);

logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');

expect(console.debug).not.toHaveBeenCalled();
expect(console.info).not.toHaveBeenCalled();
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).not.toHaveBeenCalled();
});
});

describe('Custom loggers', () => {
it('should create logger with custom configuration', () => {
const customLogger = createLogger({
level: LogLevel.DEBUG,
prefix: 'Custom',
timestamps: false,
showLevel: false,
});

expect(customLogger.getLevel()).toBe(LogLevel.DEBUG);
expect(customLogger.getConfig().prefix).toBe('Custom');
expect(customLogger.getConfig().timestamps).toBe(false);
expect(customLogger.getConfig().showLevel).toBe(false);
});

it('should create component logger with nested prefix', () => {
const componentLogger = createComponentLogger('Authentication');

componentLogger.info('Test message');

expect(console.info).toHaveBeenCalled();
// The exact format depends on environment detection
});

it('should create child logger', () => {
const parentLogger = createLogger({prefix: 'Parent'});
const childLogger = parentLogger.child('Child');

expect(childLogger.getConfig().prefix).toBe('Parent - Child');
});
});

describe('Configuration', () => {
it('should update configuration', () => {
const testLogger = createLogger({level: LogLevel.INFO});

testLogger.configure({
level: LogLevel.DEBUG,
prefix: 'Updated',
});

expect(testLogger.getLevel()).toBe(LogLevel.DEBUG);
expect(testLogger.getConfig().prefix).toBe('Updated');
});
});

describe('Custom formatter', () => {
it('should use custom formatter when provided', () => {
const mockFormatter = jest.fn();
const customLogger = createLogger({
formatter: mockFormatter,
});

customLogger.info('Test message', {data: 'test'});

expect(mockFormatter).toHaveBeenCalledWith(LogLevel.INFO, 'Test message', {data: 'test'});
expect(console.info).not.toHaveBeenCalled();
});
});
});
Loading
Loading