mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
feat(logging): implement core logging library with structured logging service
- Added Core Logging library providing centralized logging functionality. - Implemented LoggingService with multiple log levels and configurable sinks. - Created ConsoleLogSink for logging to the browser console. - Introduced LoggerApi for context-aware logging. - Added support for custom sinks and logging configuration during app initialization. - Enhanced FilterService and FilterMenuButtonComponent with logging capabilities. - Updated ESLint and Jest configurations for the new logging library. - Documented the logging library API and usage in README.
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import { HTTP_INTERCEPTORS, provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
|
||||
import {
|
||||
HTTP_INTERCEPTORS,
|
||||
provideHttpClient,
|
||||
withInterceptorsFromDi,
|
||||
} from '@angular/common/http';
|
||||
import {
|
||||
ErrorHandler,
|
||||
Injector,
|
||||
@@ -24,7 +28,10 @@ import { environment } from '../environments/environment';
|
||||
import { AppSwaggerModule } from './app-swagger.module';
|
||||
import { AppDomainModule } from './app-domain.module';
|
||||
import { UiModalModule } from '@ui/modal';
|
||||
import { NotificationsHubModule, NOTIFICATIONS_HUB_OPTIONS } from '@hub/notifications';
|
||||
import {
|
||||
NotificationsHubModule,
|
||||
NOTIFICATIONS_HUB_OPTIONS,
|
||||
} from '@hub/notifications';
|
||||
import { SignalRHubOptions } from '@core/signalr';
|
||||
import { CoreBreadcrumbModule } from '@core/breadcrumb';
|
||||
import { UiCommonModule } from '@ui/common';
|
||||
@@ -36,7 +43,11 @@ import { HttpErrorInterceptor } from './interceptors';
|
||||
import { CoreLoggerModule, LOG_PROVIDER } from '@core/logger';
|
||||
import { IsaLogProvider } from './providers';
|
||||
import { IsaErrorHandler } from './providers/isa.error-handler';
|
||||
import { ScanAdapterModule, ScanAdapterService, ScanditScanAdapterModule } from '@adapter/scan';
|
||||
import {
|
||||
ScanAdapterModule,
|
||||
ScanAdapterService,
|
||||
ScanditScanAdapterModule,
|
||||
} from '@adapter/scan';
|
||||
import { RootStateService } from './store/root-state.service';
|
||||
import * as Commands from './commands';
|
||||
import { PreviewComponent } from './preview';
|
||||
@@ -45,11 +56,22 @@ import { ShellModule } from '@shared/shell';
|
||||
import { MainComponent } from './main.component';
|
||||
import { IconModule } from '@shared/components/icon';
|
||||
import { NgIconsModule } from '@ng-icons/core';
|
||||
import { matClose, matWifi, matWifiOff } from '@ng-icons/material-icons/baseline';
|
||||
import {
|
||||
matClose,
|
||||
matWifi,
|
||||
matWifiOff,
|
||||
} from '@ng-icons/material-icons/baseline';
|
||||
import { NetworkStatusService } from './services/network-status.service';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { provideMatomo } from 'ngx-matomo-client';
|
||||
import { withRouter, withRouteData } from 'ngx-matomo-client';
|
||||
import {
|
||||
provideLogging,
|
||||
withLogLevel,
|
||||
LogLevel,
|
||||
withSink,
|
||||
ConsoleLogSink,
|
||||
} from '@isa/core/logging';
|
||||
|
||||
registerLocaleData(localeDe, localeDeExtra);
|
||||
registerLocaleData(localeDe, 'de', localeDeExtra);
|
||||
@@ -103,7 +125,13 @@ export function _appInitializerFactory(config: Config, injector: Injector) {
|
||||
'⚡<br><br><b>Fehler bei der Initialisierung</b><br><br>Bitte prüfen Sie die Netzwerkverbindung (WLAN).<br><br>';
|
||||
|
||||
const reload = document.createElement('button');
|
||||
reload.classList.add('bg-brand', 'text-white', 'p-2', 'rounded', 'cursor-pointer');
|
||||
reload.classList.add(
|
||||
'bg-brand',
|
||||
'text-white',
|
||||
'p-2',
|
||||
'rounded',
|
||||
'cursor-pointer',
|
||||
);
|
||||
reload.innerHTML = 'App neu laden';
|
||||
reload.onclick = () => window.location.reload();
|
||||
statusElement.appendChild(reload);
|
||||
@@ -167,7 +195,10 @@ export function _notificationsHubOptionsFactory(
|
||||
],
|
||||
providers: [
|
||||
provideAppInitializer(() => {
|
||||
const initializerFn = _appInitializerFactory(inject(Config), inject(Injector));
|
||||
const initializerFn = _appInitializerFactory(
|
||||
inject(Config),
|
||||
inject(Injector),
|
||||
);
|
||||
return initializerFn();
|
||||
}),
|
||||
{
|
||||
@@ -196,6 +227,7 @@ export function _notificationsHubOptionsFactory(
|
||||
withRouter(),
|
||||
withRouteData(),
|
||||
),
|
||||
provideLogging(withLogLevel(LogLevel.Debug), withSink(ConsoleLogSink)),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
@@ -5,6 +5,186 @@
|
||||
- **Readability First**: Write code that is easy to read and understand.
|
||||
- **Consistency**: Follow the same patterns and conventions throughout the codebase.
|
||||
- **Clean Code**: Avoid unnecessary complexity and keep functions small and focused.
|
||||
- **SOLID Principles**: Follow SOLID design principles to create more maintainable, flexible, and scalable code.
|
||||
|
||||
## SOLID Design Principles
|
||||
|
||||
SOLID is an acronym for five design principles that help make software designs more understandable, flexible, and maintainable:
|
||||
|
||||
- **Single Responsibility Principle (SRP)**: A class should have only one reason to change, meaning it should have only one job or responsibility.
|
||||
|
||||
```typescript
|
||||
// Good - Each class has a single responsibility
|
||||
class UserAuthentication {
|
||||
authenticate(username: string, password: string): boolean {
|
||||
// Authentication logic
|
||||
}
|
||||
}
|
||||
|
||||
class UserRepository {
|
||||
findById(id: string): User {
|
||||
// Database access logic
|
||||
}
|
||||
}
|
||||
|
||||
// Bad - Class has multiple responsibilities
|
||||
class UserManager {
|
||||
authenticate(username: string, password: string): boolean {
|
||||
// Authentication logic
|
||||
}
|
||||
|
||||
findById(id: string): User {
|
||||
// Database access logic
|
||||
}
|
||||
|
||||
sendEmail(user: User, message: string): void {
|
||||
// Email sending logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Open/Closed Principle (OCP)**: Software entities should be open for extension but closed for modification.
|
||||
|
||||
```typescript
|
||||
// Good - Open for extension
|
||||
interface PaymentProcessor {
|
||||
processPayment(amount: number): void;
|
||||
}
|
||||
|
||||
class CreditCardProcessor implements PaymentProcessor {
|
||||
processPayment(amount: number): void {
|
||||
// Credit card processing logic
|
||||
}
|
||||
}
|
||||
|
||||
class PayPalProcessor implements PaymentProcessor {
|
||||
processPayment(amount: number): void {
|
||||
// PayPal processing logic
|
||||
}
|
||||
}
|
||||
|
||||
// New payment methods can be added without modifying existing code
|
||||
```
|
||||
|
||||
- **Liskov Substitution Principle (LSP)**: Objects of a superclass should be replaceable with objects of subclasses without affecting the correctness of the program.
|
||||
|
||||
```typescript
|
||||
// Good - Derived classes can substitute base class
|
||||
class Rectangle {
|
||||
constructor(
|
||||
protected width: number,
|
||||
protected height: number,
|
||||
) {}
|
||||
|
||||
setWidth(width: number): void {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
setHeight(height: number): void {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
getArea(): number {
|
||||
return this.width * this.height;
|
||||
}
|
||||
}
|
||||
|
||||
class Square extends Rectangle {
|
||||
constructor(size: number) {
|
||||
super(size, size);
|
||||
}
|
||||
|
||||
// Preserve behavior when overriding methods
|
||||
setWidth(width: number): void {
|
||||
super.setWidth(width);
|
||||
super.setHeight(width);
|
||||
}
|
||||
|
||||
setHeight(height: number): void {
|
||||
super.setWidth(height);
|
||||
super.setHeight(height);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Interface Segregation Principle (ISP)**: Clients should not be forced to depend on interfaces they do not use.
|
||||
|
||||
```typescript
|
||||
// Good - Segregated interfaces
|
||||
interface Printable {
|
||||
print(): void;
|
||||
}
|
||||
|
||||
interface Scannable {
|
||||
scan(): void;
|
||||
}
|
||||
|
||||
class AllInOnePrinter implements Printable, Scannable {
|
||||
print(): void {
|
||||
// Printing logic
|
||||
}
|
||||
|
||||
scan(): void {
|
||||
// Scanning logic
|
||||
}
|
||||
}
|
||||
|
||||
class BasicPrinter implements Printable {
|
||||
print(): void {
|
||||
// Printing logic
|
||||
}
|
||||
}
|
||||
|
||||
// Bad - Fat interface
|
||||
interface OfficeMachine {
|
||||
print(): void;
|
||||
scan(): void;
|
||||
fax(): void;
|
||||
staple(): void;
|
||||
}
|
||||
|
||||
// Classes must implement methods they don't need
|
||||
```
|
||||
|
||||
- **Dependency Inversion Principle (DIP)**: High-level modules should not depend on low-level modules. Both should depend on abstractions.
|
||||
|
||||
```typescript
|
||||
// Good - Depending on abstractions
|
||||
interface Logger {
|
||||
log(message: string): void;
|
||||
}
|
||||
|
||||
class ConsoleLogger implements Logger {
|
||||
log(message: string): void {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
class FileLogger implements Logger {
|
||||
log(message: string): void {
|
||||
// File logging logic
|
||||
}
|
||||
}
|
||||
|
||||
class UserService {
|
||||
constructor(private logger: Logger) {}
|
||||
|
||||
createUser(user: User): void {
|
||||
// Create user logic
|
||||
this.logger.log(`User created: ${user.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
// The UserService depends on the abstraction (Logger interface)
|
||||
// not on concrete implementations
|
||||
```
|
||||
|
||||
Following these principles improves code quality by:
|
||||
|
||||
- Reducing coupling between components
|
||||
- Making the system more modular and easier to maintain
|
||||
- Facilitating testing and extension
|
||||
- Promoting code reuse
|
||||
|
||||
## Extended Guidelines for Angular and TypeScript
|
||||
|
||||
@@ -13,16 +193,24 @@ This section extends the core code style principles with Angular-specific and ad
|
||||
### Angular Enhancements
|
||||
|
||||
- **Change Detection**: Use the OnPush strategy by default for better performance.
|
||||
- **Lifecycle Hooks**: Explicitly implement Angular lifecycle interfaces.
|
||||
- **Lifecycle Hooks**: Explicitly implement Angular lifecycle interfaces (OnInit, OnDestroy, etc.).
|
||||
- **Template Management**: Keep templates concise and use the async pipe to handle observables.
|
||||
- **Component Structure**: Follow best practices for component modularization to enhance readability and testability.
|
||||
- **Naming Conventions**: Follow Angular's official naming conventions for selectors, files, and component classes.
|
||||
- **File Organization**: Structure files according to features and follow the recommended folder structure.
|
||||
- **Control Flow**: Use modern control flow syntax (@if, @for) instead of structural directives (*ngIf, *ngFor).
|
||||
- **Signals**: Prefer signals over RxJS for simpler state management within components.
|
||||
|
||||
### TypeScript Enhancements
|
||||
|
||||
- **Strict Type Checking**: Enable strict mode (`strict: true`) and avoid excessive use of `any`.
|
||||
- **Interfaces vs. Types**: Prefer interfaces for object definitions and use type aliases for unions and intersections.
|
||||
- **Generics**: Use meaningful type parameter names and constrain generics when applicable.
|
||||
- **Documentation**: Employ JSDoc comments functions and generic parameters to improve code clarity.
|
||||
- **Documentation**: Employ JSDoc comments for functions and generic parameters to improve code clarity.
|
||||
- **Non-Nullability**: Use the non-null assertion operator (!) sparingly and only when you're certain a value cannot be null.
|
||||
- **Type Guards**: Implement custom type guards to handle type narrowing safely.
|
||||
- **Immutability**: Favor immutable data structures and use readonly modifiers when applicable.
|
||||
- **Exhaustiveness Checking**: Use exhaustiveness checking for switch statements handling union types.
|
||||
|
||||
## TypeScript Guidelines
|
||||
|
||||
@@ -48,18 +236,18 @@ This section extends the core code style principles with Angular-specific and ad
|
||||
|
||||
- Prefer `interface` over `type` for object definitions
|
||||
- Use `type` for unions, intersections, and mapped types
|
||||
- Follow Angular's naming convention: `IComponentProps` for props interfaces
|
||||
- Follow Angular's naming convention: Don't prefix interfaces with 'I' (use `ComponentProps` not `IComponentProps`)
|
||||
- Extend interfaces instead of repeating properties
|
||||
- Use readonly modifiers where appropriate
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
interface IBaseProps {
|
||||
interface BaseProps {
|
||||
readonly id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface IUserProps extends IBaseProps {
|
||||
interface UserProps extends BaseProps {
|
||||
email: string;
|
||||
}
|
||||
|
||||
@@ -75,9 +263,49 @@ This section extends the core code style principles with Angular-specific and ad
|
||||
|
||||
- **Enums and Constants**:
|
||||
|
||||
- Use `const enum` for better performance
|
||||
- Only use regular `enum` when runtime access is required
|
||||
- Prefer union types for simple string literals
|
||||
- Prefer this order of implementation (from most to least preferred):
|
||||
|
||||
1. `const enum` for better compile-time performance
|
||||
2. Object literals with `as const` for runtime flexibility
|
||||
3. Regular `enum` only when necessary for runtime access
|
||||
|
||||
- **When to use each approach**:
|
||||
- Use `const enum` for internal application enumerations that don't need runtime access
|
||||
- Use `const object as const` when values need to be inspected at runtime or exported in an API
|
||||
- Use regular `enum` only when runtime enumeration object access is required
|
||||
|
||||
```typescript
|
||||
// Good - const enum (preferred for most cases)
|
||||
// Advantages: Tree-shakable, type-safe, disappears at compile time
|
||||
export const enum ConstEnumStates {
|
||||
NotSet = 'not-set',
|
||||
Success = 'success',
|
||||
}
|
||||
|
||||
// Good - const object with 'as const' assertion
|
||||
// Advantages: Runtime accessible, works well with API boundaries
|
||||
export const ConstStates = {
|
||||
NotSet: 'not-set',
|
||||
Success: 'success',
|
||||
} as const;
|
||||
|
||||
// Types can be extracted from const objects
|
||||
type ConstStatesType = (typeof ConstStates)[keyof typeof ConstStates];
|
||||
|
||||
// Least preferred - regular enum
|
||||
// Only use when you need the enum object at runtime
|
||||
export enum States {
|
||||
NotSet = 'not-set',
|
||||
Success = 'success',
|
||||
}
|
||||
```
|
||||
|
||||
- Use union types as an alternative for simple string literals
|
||||
|
||||
```typescript
|
||||
// Alternative approach using union types
|
||||
export type StatusType = 'not-set' | 'success';
|
||||
```
|
||||
|
||||
- **Functions and Methods**:
|
||||
|
||||
@@ -94,7 +322,7 @@ This section extends the core code style principles with Angular-specific and ad
|
||||
* @param id - The user's unique identifier
|
||||
* @param includeDetails - Whether to include additional user details
|
||||
*/
|
||||
const getUser = (id: string, includeDetails = false): Promise<IUser> => {
|
||||
const getUser = (id: string, includeDetails = false): Promise<User> => {
|
||||
// ...implementation
|
||||
};
|
||||
|
||||
@@ -113,12 +341,12 @@ Example:
|
||||
|
||||
```typescript
|
||||
// Good
|
||||
interface IUserProps {
|
||||
interface UserProps {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface IAdminProps extends IUserProps {
|
||||
interface AdminProps extends UserProps {
|
||||
permissions: string[];
|
||||
}
|
||||
|
||||
@@ -127,7 +355,7 @@ const enum UserRole {
|
||||
User = 'USER',
|
||||
}
|
||||
|
||||
const getUser = <T extends IUserProps>(id: string): Promise<T> => {
|
||||
const getUser = <T extends UserProps>(id: string): Promise<T> => {
|
||||
// ...implementation
|
||||
};
|
||||
|
||||
@@ -170,20 +398,116 @@ function getUser(id) {
|
||||
subscription: Subscription;
|
||||
|
||||
ngOnInit() {
|
||||
this.subscription = this.userService.getUsers().subscribe((users) => (this.users = users));
|
||||
this.subscription = this.userService
|
||||
.getUsers()
|
||||
.subscribe((users) => (this.users = users));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Templates**
|
||||
- **Templates and Control Flow**:
|
||||
|
||||
- Use new control flow syntax - instead if \*ngIf use the @if syntax
|
||||
- Use modern control flow syntax (`@if`, `@for`, `@switch`) instead of structural directives (`*ngIf`, `*ngFor`, `*ngSwitch`).
|
||||
|
||||
```html
|
||||
<!-- Good - Modern control flow syntax -->
|
||||
<div>
|
||||
@if (user) {
|
||||
<h1>Welcome, {{ user.name }}!</h1>
|
||||
} @else if (isLoading) {
|
||||
<h1>Loading user data...</h1>
|
||||
} @else {
|
||||
<h1>Please log in</h1>
|
||||
}
|
||||
|
||||
<ul>
|
||||
@for (item of items; track item.id) {
|
||||
<li>{{ item.name }}</li>
|
||||
} @empty {
|
||||
<li>No items available</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@switch (userRole) { @case ('admin') {
|
||||
<app-admin-dashboard />
|
||||
} @case ('manager') {
|
||||
<app-manager-dashboard />
|
||||
} @default {
|
||||
<app-user-dashboard />
|
||||
} }
|
||||
</div>
|
||||
|
||||
<!-- Bad - Old structural directives -->
|
||||
<div>
|
||||
<h1 *ngIf="user">Welcome, {{ user.name }}!</h1>
|
||||
<h1 *ngIf="!user && isLoading">Loading user data...</h1>
|
||||
<h1 *ngIf="!user && !isLoading">Please log in</h1>
|
||||
|
||||
<ul>
|
||||
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
|
||||
<li *ngIf="!items || items.length === 0">No items available</li>
|
||||
</ul>
|
||||
|
||||
<app-admin-dashboard *ngIf="userRole === 'admin'"></app-admin-dashboard>
|
||||
<app-manager-dashboard
|
||||
*ngIf="userRole === 'manager'"
|
||||
></app-manager-dashboard>
|
||||
<app-user-dashboard
|
||||
*ngIf="userRole !== 'admin' && userRole !== 'manager'"
|
||||
></app-user-dashboard>
|
||||
</div>
|
||||
```
|
||||
|
||||
- When using `@for`, always specify the `track` expression to optimize rendering performance:
|
||||
- Use a unique identifier property (like `id` or `uuid`) when available
|
||||
- Only use `$index` for static collections that never change
|
||||
- Avoid using non-unique properties that could result in DOM mismatches
|
||||
- Leverage contextual variables in `@for` blocks:
|
||||
- `$index` - Current item index
|
||||
- `$first` - Boolean indicating if this is the first item
|
||||
- `$last` - Boolean indicating if this is the last item
|
||||
- `$even` - Boolean indicating if this index is even
|
||||
- `$odd` - Boolean indicating if this index is odd
|
||||
- `$count` - Total number of items in the collection
|
||||
|
||||
```html
|
||||
<!-- Good - Using contextual variables -->
|
||||
@for (item of items; track item.id; let i = $index, isLast = $last) {
|
||||
<li [class.last-item]="isLast">{{ i + 1 }}. {{ item.name }}</li>
|
||||
}
|
||||
```
|
||||
|
||||
- Use the `@empty` block with `@for` to handle empty collections gracefully
|
||||
- Store conditional expression results in variables for clearer templates:
|
||||
|
||||
```html
|
||||
<!-- Good - Storing expression result in variable -->
|
||||
@if (user.permissions.canEditSettings; as canEdit) {
|
||||
<button [disabled]="!canEdit">Edit Settings</button>
|
||||
}
|
||||
```
|
||||
|
||||
## Project-Specific Preferences
|
||||
|
||||
- **Frameworks**: Follow best practices for Nx, Hono, and Zod.
|
||||
- **Frameworks**: Follow best practices for Nx, Angular, date-fns, Ngrx, RxJs and Zod.
|
||||
- **Testing**: Use Jest with Spectator for unit tests and follow the Arrange-Act-Assert pattern.
|
||||
- **File Naming**: Use kebab-case for filenames (e.g., `my-component.ts`).
|
||||
- **File Naming**:
|
||||
|
||||
- Use kebab-case for filenames (e.g., `my-component.ts`).
|
||||
- Follow a pattern that describes the symbol's feature then its type: `feature.type.ts`
|
||||
|
||||
```
|
||||
// Good examples
|
||||
user.service.ts
|
||||
auth.guard.ts
|
||||
product-list.component.ts
|
||||
order.model.ts
|
||||
|
||||
// Bad examples
|
||||
service-user.ts
|
||||
userService.ts
|
||||
```
|
||||
|
||||
- **Comments**: Use JSDoc for documenting functions, classes, and modules.
|
||||
|
||||
## Formatting
|
||||
@@ -198,25 +522,10 @@ function getUser(id) {
|
||||
- Use ESLint with the recommended TypeScript and Nx configurations.
|
||||
- Prettier should be used for consistent formatting.
|
||||
|
||||
## Example
|
||||
|
||||
```typescript
|
||||
// Good Example
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const getUser = (id: string): User => {
|
||||
// ...function logic...
|
||||
};
|
||||
|
||||
// Bad Example
|
||||
function getUser(id) {
|
||||
// ...function logic...
|
||||
}
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [Angular Style Guide](https://angular.dev/style-guide#)
|
||||
- [Angular Style Guide](https://angular.dev/style-guide) - Official Angular style guide with best practices for Angular development
|
||||
- [Angular Control Flow](https://angular.dev/guide/templates/control-flow) - Official Angular documentation on the new control flow syntax (@if, @for, @switch)
|
||||
- [TypeScript Style Guide](https://ts.dev/style/) - TypeScript community style guide with patterns and practices
|
||||
- [SOLID Design Principles](https://en.wikipedia.org/wiki/SOLID) - Wikipedia article explaining the SOLID principles in object-oriented design
|
||||
- [Clean Code](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) - Robert C. Martin's seminal book on writing clean, maintainable code
|
||||
|
||||
151
libs/core/logging/README.md
Normal file
151
libs/core/logging/README.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Core Logging
|
||||
|
||||
## Overview
|
||||
|
||||
The Core Logging library provides a centralized logging service for the ISA Frontend application. It offers a structured way to log messages, errors, and other information across the application, with support for different log levels and output destinations.
|
||||
|
||||
## Features
|
||||
|
||||
- Multiple log levels (trace, debug, info, warn, error)
|
||||
- Configurable logging targets (console, remote server, etc.)
|
||||
- Context-aware logging with metadata support
|
||||
- Production/development mode detection
|
||||
- Filtering capabilities based on log level or context
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Log Sinks
|
||||
|
||||
Log sinks are destinations where log messages are sent. The library supports multiple sinks operating simultaneously, allowing you to:
|
||||
|
||||
- Display logs in the console during development
|
||||
- Send critical errors to a monitoring service
|
||||
- Store logs for later analysis
|
||||
|
||||
## API
|
||||
|
||||
### LoggingService
|
||||
|
||||
The main service for logging functionality.
|
||||
|
||||
#### Methods
|
||||
|
||||
| Method | Description |
|
||||
| ---------------------------------------------------------- | --------------------------- |
|
||||
| `trace(message: string, context?: unknown)` | Logs trace information |
|
||||
| `debug(message: string, context?: unknown)` | Logs debug information |
|
||||
| `info(message: string, context?: unknown)` | Logs informational messages |
|
||||
| `warn(message: string, context?: unknown)` | Logs warning messages |
|
||||
| `error(message: string, error?: Error, context?: unknown)` | Logs error messages |
|
||||
|
||||
### Create Custom Sink
|
||||
|
||||
#### Custom Sink Class
|
||||
|
||||
```typescript
|
||||
class MyCustomSink implements Sink {
|
||||
#loggerAPI = inject(LoggerApi);
|
||||
log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: unknown,
|
||||
error?: Error,
|
||||
): void {
|
||||
// ... Do Stuff
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Sink Function
|
||||
|
||||
```typescript
|
||||
const myCustomSinkFn: SinkFn = () => {
|
||||
const loggerAPI = inject(LoggerApi);
|
||||
return (
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: unknown,
|
||||
error?: Error,
|
||||
) => {
|
||||
// ... Do Stuff
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
### LogLevel Enum
|
||||
|
||||
```typescript
|
||||
export const enum LogLevel {
|
||||
Trace = 'trace',
|
||||
Debug = 'debug',
|
||||
Info = 'info',
|
||||
Warn = 'warn',
|
||||
Error = 'error',
|
||||
Off = 'off',
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Global Configuration
|
||||
|
||||
Configure the logging service globally during application initialization:
|
||||
|
||||
```typescript
|
||||
// In your app.config.ts or similar initialization file
|
||||
|
||||
import { provideLogging, withSink, ConsoleLogSink } from '@isa/core/logging';
|
||||
|
||||
const isProduction = environment.production;
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
// ...other providers
|
||||
provideLogging(
|
||||
withLogLevel(isProduction ? LogLevel.Warn : LogLevel.Debug),
|
||||
withSink(ConsoleLogSink),
|
||||
withSink(MyCustomSink),
|
||||
withSinkFn(myCustomSinkFn),
|
||||
),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Context Configuration
|
||||
|
||||
Configure the logging service for a specific context
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
providers: [provideLoggerContext({ component: 'MyComponent', ... })]
|
||||
})
|
||||
export class MyComponent {}
|
||||
#logger = logger();
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
import { logger, LogLevel } from '@isa/core/logging';
|
||||
|
||||
@Component({
|
||||
// ...
|
||||
})
|
||||
export class MyComponent implements OnInit {
|
||||
|
||||
#logger = logger();
|
||||
|
||||
ngOnInit() {
|
||||
this.logger.info('Component initialized', { componentName: 'MyComponent' });
|
||||
}
|
||||
|
||||
processData(data: any) {
|
||||
try {
|
||||
// Process data
|
||||
this.logger.debug('Data processed successfully', { dataId: data.id });
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to process data', error as Error, {
|
||||
dataId: data.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
34
libs/core/logging/eslint.config.mjs
Normal file
34
libs/core/logging/eslint.config.mjs
Normal file
@@ -0,0 +1,34 @@
|
||||
import nx from '@nx/eslint-plugin';
|
||||
import baseConfig from '../../../eslint.config.mjs';
|
||||
|
||||
export default [
|
||||
...baseConfig,
|
||||
...nx.configs['flat/angular'],
|
||||
...nx.configs['flat/angular-template'],
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {
|
||||
'@angular-eslint/directive-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'attribute',
|
||||
prefix: 'core',
|
||||
style: 'camelCase',
|
||||
},
|
||||
],
|
||||
'@angular-eslint/component-selector': [
|
||||
'error',
|
||||
{
|
||||
type: 'element',
|
||||
prefix: 'core',
|
||||
style: 'kebab-case',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.html'],
|
||||
// Override or add rules here
|
||||
rules: {},
|
||||
},
|
||||
];
|
||||
21
libs/core/logging/jest.config.ts
Normal file
21
libs/core/logging/jest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
displayName: 'core-logging',
|
||||
preset: '../../../jest.preset.js',
|
||||
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
|
||||
coverageDirectory: '../../../coverage/libs/core/logging',
|
||||
transform: {
|
||||
'^.+\\.(ts|mjs|js|html)$': [
|
||||
'jest-preset-angular',
|
||||
{
|
||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
||||
stringifyContentPathRegex: '\\.(html|svg)$',
|
||||
},
|
||||
],
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'],
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/build/serializers/no-ng-attributes',
|
||||
'jest-preset-angular/build/serializers/ng-snapshot',
|
||||
'jest-preset-angular/build/serializers/html-comment',
|
||||
],
|
||||
};
|
||||
20
libs/core/logging/project.json
Normal file
20
libs/core/logging/project.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "core-logging",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/core/logging/src",
|
||||
"prefix": "core",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/core/logging/jest.config.ts"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
}
|
||||
}
|
||||
}
|
||||
19
libs/core/logging/src/index.ts
Normal file
19
libs/core/logging/src/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Core Logging Library
|
||||
*
|
||||
* A centralized logging service for the ISA Frontend application.
|
||||
*/
|
||||
|
||||
// Export core logging functionality
|
||||
export { LogLevel } from './lib/log-level.enum';
|
||||
export { LoggerApi, Sink, SinkFn, LoggerContext } from './lib/logging.types';
|
||||
export { LoggingService } from './lib/logging.service';
|
||||
export { ConsoleLogSink } from './lib/console-log.sink';
|
||||
export {
|
||||
provideLogging,
|
||||
provideLoggerContext,
|
||||
withLogLevel,
|
||||
withSink,
|
||||
withSinkFn,
|
||||
} from './lib/logging.providers';
|
||||
export { logger } from './lib/logger.factory';
|
||||
68
libs/core/logging/src/lib/console-log.sink.ts
Normal file
68
libs/core/logging/src/lib/console-log.sink.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { LogLevel } from './log-level.enum';
|
||||
import { Sink } from './logging.types';
|
||||
|
||||
/**
|
||||
* A sink implementation that outputs logs to the browser console.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ConsoleLogSink implements Sink {
|
||||
/**
|
||||
* Logs a message to the browser console with the appropriate log level.
|
||||
* @param level The log level.
|
||||
* @param message The log message.
|
||||
* @param context Optional context data.
|
||||
* @param error Optional error object.
|
||||
*/
|
||||
log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: unknown,
|
||||
error?: Error,
|
||||
): void {
|
||||
switch (level) {
|
||||
case LogLevel.Trace:
|
||||
if (context) {
|
||||
console.trace(message, context);
|
||||
} else {
|
||||
console.trace(message);
|
||||
}
|
||||
break;
|
||||
case LogLevel.Debug:
|
||||
if (context) {
|
||||
console.debug(message, context);
|
||||
} else {
|
||||
console.debug(message);
|
||||
}
|
||||
break;
|
||||
case LogLevel.Info:
|
||||
if (context) {
|
||||
console.info(message, context);
|
||||
} else {
|
||||
console.info(message);
|
||||
}
|
||||
break;
|
||||
case LogLevel.Warn:
|
||||
if (context) {
|
||||
console.warn(message, context);
|
||||
} else {
|
||||
console.warn(message);
|
||||
}
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
if (error) {
|
||||
if (context) {
|
||||
console.error(message, error, context);
|
||||
} else {
|
||||
console.error(message, error);
|
||||
}
|
||||
} else if (context) {
|
||||
console.error(message, context);
|
||||
} else {
|
||||
console.error(message);
|
||||
}
|
||||
break;
|
||||
// LogLevel.Off - no logs will be sent to this sink
|
||||
}
|
||||
}
|
||||
}
|
||||
11
libs/core/logging/src/lib/log-level.enum.ts
Normal file
11
libs/core/logging/src/lib/log-level.enum.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Defines the available log levels for the logging system.
|
||||
*/
|
||||
export const enum LogLevel {
|
||||
Trace = 'trace',
|
||||
Debug = 'debug',
|
||||
Info = 'info',
|
||||
Warn = 'warn',
|
||||
Error = 'error',
|
||||
Off = 'off',
|
||||
}
|
||||
82
libs/core/logging/src/lib/logger.factory.ts
Normal file
82
libs/core/logging/src/lib/logger.factory.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { LoggingService } from './logging.service';
|
||||
import { LoggerApi } from './logging.types';
|
||||
import { LOGGER_CONTEXT } from './logging.providers';
|
||||
|
||||
/**
|
||||
* Factory function to create a logger instance with optional context.
|
||||
*
|
||||
* This is the primary way for components and services to access the logging
|
||||
* functionality in the application.
|
||||
*
|
||||
* @returns Logger API interface that can be used to log messages.
|
||||
*/
|
||||
export function logger(): LoggerApi {
|
||||
const loggingService = inject(LoggingService);
|
||||
|
||||
// Try to inject context if available
|
||||
const context = inject(LOGGER_CONTEXT, { optional: true });
|
||||
|
||||
// Return an object with methods that forward to the logging service
|
||||
// with the provided context
|
||||
return {
|
||||
trace: (message: string, additionalContext?: unknown): void => {
|
||||
loggingService.trace(message, mergeContexts(context, additionalContext));
|
||||
},
|
||||
debug: (message: string, additionalContext?: unknown): void => {
|
||||
loggingService.debug(message, mergeContexts(context, additionalContext));
|
||||
},
|
||||
info: (message: string, additionalContext?: unknown): void => {
|
||||
loggingService.info(message, mergeContexts(context, additionalContext));
|
||||
},
|
||||
warn: (message: string, additionalContext?: unknown): void => {
|
||||
loggingService.warn(message, mergeContexts(context, additionalContext));
|
||||
},
|
||||
error: (
|
||||
message: string,
|
||||
error?: Error,
|
||||
additionalContext?: unknown,
|
||||
): void => {
|
||||
loggingService.error(
|
||||
message,
|
||||
error,
|
||||
mergeContexts(context, additionalContext),
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges component-level context with message-specific context.
|
||||
* @param baseContext The component-level context.
|
||||
* @param additionalContext The message-specific context.
|
||||
* @returns The merged context.
|
||||
*/
|
||||
function mergeContexts(
|
||||
baseContext?: unknown,
|
||||
additionalContext?: unknown,
|
||||
): unknown {
|
||||
if (!baseContext) {
|
||||
return additionalContext;
|
||||
}
|
||||
|
||||
if (!additionalContext) {
|
||||
return baseContext;
|
||||
}
|
||||
|
||||
// If both contexts are objects, merge them
|
||||
if (
|
||||
typeof baseContext === 'object' &&
|
||||
baseContext !== null &&
|
||||
typeof additionalContext === 'object' &&
|
||||
additionalContext !== null
|
||||
) {
|
||||
return { ...baseContext, ...additionalContext };
|
||||
}
|
||||
|
||||
// If not both objects, return them separately
|
||||
return {
|
||||
baseContext,
|
||||
additionalContext,
|
||||
};
|
||||
}
|
||||
118
libs/core/logging/src/lib/logging.providers.ts
Normal file
118
libs/core/logging/src/lib/logging.providers.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import {
|
||||
APP_INITIALIZER,
|
||||
EnvironmentProviders,
|
||||
InjectionToken,
|
||||
Provider,
|
||||
Type,
|
||||
makeEnvironmentProviders,
|
||||
} from '@angular/core';
|
||||
import { LogLevel } from './log-level.enum';
|
||||
import { LoggingService } from './logging.service';
|
||||
import { LoggerContext, LoggingConfig, Sink, SinkFn } from './logging.types';
|
||||
|
||||
// Injection tokens for the Logger API
|
||||
export const LOGGER_CONFIG = new InjectionToken<LoggingConfig>('LOGGER_CONFIG');
|
||||
export const LOGGER_CONTEXT = new InjectionToken<LoggerContext>(
|
||||
'LOGGER_CONTEXT',
|
||||
);
|
||||
|
||||
/**
|
||||
* Configuration data for the logging system
|
||||
*/
|
||||
interface LoggingConfigData {
|
||||
level?: LogLevel;
|
||||
sinks?: (Sink | SinkFn | Type<Sink>)[];
|
||||
context?: LoggerContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the LoggingService during app initialization
|
||||
*/
|
||||
export function configureLogger(
|
||||
loggingService: LoggingService,
|
||||
config: LoggingConfig,
|
||||
): () => void {
|
||||
return () => {
|
||||
loggingService.configure(config);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to set the log level
|
||||
* @param level The log level to set
|
||||
*/
|
||||
export function withLogLevel(level: LogLevel): LoggingConfigData {
|
||||
return { level };
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to add a sink to the configuration
|
||||
* @param sink The sink to add (instance or class reference)
|
||||
*/
|
||||
export function withSink(sink: Sink | Type<Sink>): LoggingConfigData {
|
||||
return { sinks: [sink] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to add a sink function to the configuration
|
||||
* @param sinkFn The sink function to add
|
||||
*/
|
||||
export function withSinkFn(sinkFn: SinkFn): LoggingConfigData {
|
||||
return { sinks: [sinkFn] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides logging functionality with the given configuration
|
||||
*/
|
||||
export function provideLogging(
|
||||
...configs: LoggingConfigData[]
|
||||
): EnvironmentProviders {
|
||||
// Merge all configurations
|
||||
const mergedConfig: LoggingConfig = {
|
||||
level: LogLevel.Info, // Default level
|
||||
sinks: [],
|
||||
};
|
||||
|
||||
for (const config of configs) {
|
||||
if (config.level !== undefined) {
|
||||
mergedConfig.level = config.level;
|
||||
}
|
||||
|
||||
if (config.sinks) {
|
||||
mergedConfig.sinks.push(...config.sinks);
|
||||
}
|
||||
|
||||
if (config.context) {
|
||||
mergedConfig.context = {
|
||||
...mergedConfig.context,
|
||||
...config.context,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return makeEnvironmentProviders([
|
||||
{
|
||||
provide: LOGGER_CONFIG,
|
||||
useValue: mergedConfig,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: configureLogger,
|
||||
deps: [LoggingService, LOGGER_CONFIG],
|
||||
multi: true,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a context object for logging within a specific component or module
|
||||
* @param context The context object to provide
|
||||
*/
|
||||
export function provideLoggerContext(context: LoggerContext): Provider[] {
|
||||
return [
|
||||
{
|
||||
provide: LOGGER_CONTEXT,
|
||||
useValue: context,
|
||||
},
|
||||
];
|
||||
}
|
||||
173
libs/core/logging/src/lib/logging.service.ts
Normal file
173
libs/core/logging/src/lib/logging.service.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { LogLevel } from './log-level.enum';
|
||||
import { LoggerApi, LoggingConfig, Sink, SinkFn } from './logging.types';
|
||||
|
||||
/**
|
||||
* The main service for logging functionality.
|
||||
* Implements the LoggerApi interface to provide logging methods.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LoggingService implements LoggerApi {
|
||||
private level: LogLevel;
|
||||
private sinks: ((
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: unknown,
|
||||
error?: Error,
|
||||
) => void)[] = [];
|
||||
private globalContext?: Record<string, unknown>;
|
||||
|
||||
constructor() {
|
||||
this.level = LogLevel.Info; // Default level
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the logging service with the provided options.
|
||||
* @param config The logging configuration options.
|
||||
*/
|
||||
configure(config: LoggingConfig): void {
|
||||
this.level = config.level;
|
||||
this.globalContext = config.context;
|
||||
this.sinks = [];
|
||||
|
||||
// Initialize all sinks
|
||||
for (const sink of config.sinks) {
|
||||
if (typeof sink === 'function') {
|
||||
// Check if it's a constructor function (class) or a sink function
|
||||
if (sink.prototype && sink.prototype.log) {
|
||||
// It's a constructor function (class), instantiate it
|
||||
const instance = new (sink as any)();
|
||||
this.sinks.push(instance.log.bind(instance));
|
||||
} else {
|
||||
// It's a SinkFn - explicitly cast to SinkFn
|
||||
this.sinks.push((sink as SinkFn)());
|
||||
}
|
||||
} else {
|
||||
// It's a Sink object
|
||||
this.sinks.push(sink.log.bind(sink));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs trace information.
|
||||
* @param message The log message.
|
||||
* @param context Optional context data.
|
||||
*/
|
||||
trace(message: string, context?: unknown): void {
|
||||
this.log(LogLevel.Trace, message, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs debug information.
|
||||
* @param message The log message.
|
||||
* @param context Optional context data.
|
||||
*/
|
||||
debug(message: string, context?: unknown): void {
|
||||
this.log(LogLevel.Debug, message, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs informational messages.
|
||||
* @param message The log message.
|
||||
* @param context Optional context data.
|
||||
*/
|
||||
info(message: string, context?: unknown): void {
|
||||
this.log(LogLevel.Info, message, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs warning messages.
|
||||
* @param message The log message.
|
||||
* @param context Optional context data.
|
||||
*/
|
||||
warn(message: string, context?: unknown): void {
|
||||
this.log(LogLevel.Warn, message, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs error messages.
|
||||
* @param message The log message.
|
||||
* @param error Optional error object.
|
||||
* @param context Optional context data.
|
||||
*/
|
||||
error(message: string, error?: Error, context?: unknown): void {
|
||||
this.log(LogLevel.Error, message, context, error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method to handle logging with level filtering.
|
||||
* @param level The log level.
|
||||
* @param message The log message.
|
||||
* @param context Optional context data.
|
||||
* @param error Optional error object.
|
||||
*/
|
||||
private log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: unknown,
|
||||
error?: Error,
|
||||
): void {
|
||||
// Check if the current log level should be processed
|
||||
if (!this.shouldLog(level)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge global context with the provided context
|
||||
const mergedContext = this.mergeContext(context);
|
||||
|
||||
// Send to all sinks
|
||||
for (const sink of this.sinks) {
|
||||
sink(level, message, mergedContext, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a message at the specified level should be logged.
|
||||
* @param level The log level to check.
|
||||
* @returns True if the message should be logged, false otherwise.
|
||||
*/
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
const levels: LogLevel[] = [
|
||||
LogLevel.Trace,
|
||||
LogLevel.Debug,
|
||||
LogLevel.Info,
|
||||
LogLevel.Warn,
|
||||
LogLevel.Error,
|
||||
];
|
||||
|
||||
const currentLevelIndex = levels.indexOf(this.level);
|
||||
const messageLevelIndex = levels.indexOf(level);
|
||||
|
||||
// Log the message if its level is higher or equal to the current level
|
||||
return (
|
||||
messageLevelIndex >= currentLevelIndex && this.level !== LogLevel.Off
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the global context with the provided context.
|
||||
* @param context The context provided in the log call.
|
||||
* @returns The merged context object.
|
||||
*/
|
||||
private mergeContext(context?: unknown): unknown {
|
||||
if (!this.globalContext) {
|
||||
return context;
|
||||
}
|
||||
|
||||
if (!context) {
|
||||
return this.globalContext;
|
||||
}
|
||||
|
||||
// Both contexts exist, merge them
|
||||
if (typeof context === 'object' && context !== null) {
|
||||
return { ...this.globalContext, ...context };
|
||||
}
|
||||
|
||||
// Context is not an object, return both separately
|
||||
return {
|
||||
globalContext: this.globalContext,
|
||||
logContext: context,
|
||||
};
|
||||
}
|
||||
}
|
||||
46
libs/core/logging/src/lib/logging.types.ts
Normal file
46
libs/core/logging/src/lib/logging.types.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { LogLevel } from './log-level.enum';
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Represents a destination where log messages are sent.
|
||||
*/
|
||||
export interface Sink {
|
||||
log(level: LogLevel, message: string, context?: unknown, error?: Error): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function implementation of a Sink.
|
||||
*/
|
||||
export type SinkFn = () => (
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: unknown,
|
||||
error?: Error,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* Configuration options for the logging service.
|
||||
*/
|
||||
export interface LoggingConfig {
|
||||
level: LogLevel;
|
||||
sinks: (Sink | SinkFn | Type<Sink>)[];
|
||||
context?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the logger API for logging operations.
|
||||
*/
|
||||
export interface LoggerApi {
|
||||
trace(message: string, context?: unknown): void;
|
||||
debug(message: string, context?: unknown): void;
|
||||
info(message: string, context?: unknown): void;
|
||||
warn(message: string, context?: unknown): void;
|
||||
error(message: string, error?: Error, context?: unknown): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger context for context-aware logging.
|
||||
*/
|
||||
export interface LoggerContext {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
6
libs/core/logging/src/test-setup.ts
Normal file
6
libs/core/logging/src/test-setup.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { setupZoneTestEnv } from 'jest-preset-angular/setup-env/zone';
|
||||
|
||||
setupZoneTestEnv({
|
||||
errorOnUnknownElements: true,
|
||||
errorOnUnknownProperties: true,
|
||||
});
|
||||
28
libs/core/logging/tsconfig.json
Normal file
28
libs/core/logging/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
}
|
||||
17
libs/core/logging/tsconfig.lib.json
Normal file
17
libs/core/logging/tsconfig.lib.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"inlineSources": true,
|
||||
"types": []
|
||||
},
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/test-setup.ts",
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts"
|
||||
],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
16
libs/core/logging/tsconfig.spec.json
Normal file
16
libs/core/logging/tsconfig.spec.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"files": ["src/test-setup.ts"],
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { computed, inject, Injectable, Input, signal } from '@angular/core';
|
||||
import { computed, inject, Injectable, signal } from '@angular/core';
|
||||
import { InputType } from '../types';
|
||||
import { getState, patchState, signalState } from '@ngrx/signals';
|
||||
import { mapToFilter } from './mappings';
|
||||
import { isEqual } from 'lodash';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import {
|
||||
FilterInput,
|
||||
OrderByDirectionSchema,
|
||||
@@ -13,6 +14,8 @@ import { FILTER_ON_COMMIT, FILTER_ON_INIT, QUERY_SETTINGS } from './tokens';
|
||||
|
||||
@Injectable()
|
||||
export class FilterService {
|
||||
#logger = logger();
|
||||
|
||||
#onInit = inject(FILTER_ON_INIT, { optional: true })?.map((fn) => fn(this));
|
||||
#onCommit = inject(FILTER_ON_COMMIT, { optional: true })?.map((fn) =>
|
||||
fn(this),
|
||||
@@ -33,7 +36,9 @@ export class FilterService {
|
||||
orderBy = this.#state.orderBy;
|
||||
|
||||
constructor() {
|
||||
this.#logger.info('FilterService initialized with default state:');
|
||||
this.#onInit?.forEach((initFn) => {
|
||||
this.#logger.info('Executing init function:', initFn);
|
||||
initFn();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { FilterMenuComponent } from './filter-menu.component';
|
||||
import { isaActionFilter } from '@isa/icons';
|
||||
import { FilterService } from '../../core';
|
||||
import { logger, provideLoggerContext } from '@isa/core/logging';
|
||||
|
||||
/**
|
||||
* A button component that toggles the visibility of a filter menu.
|
||||
@@ -23,10 +24,20 @@ import { FilterService } from '../../core';
|
||||
templateUrl: './filter-menu-button.component.html',
|
||||
styleUrls: ['./filter-menu-button.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [IconButtonComponent, OverlayModule, NgIconComponent, FilterMenuComponent],
|
||||
providers: [provideIcons({ isaActionFilter })],
|
||||
imports: [
|
||||
IconButtonComponent,
|
||||
OverlayModule,
|
||||
NgIconComponent,
|
||||
FilterMenuComponent,
|
||||
],
|
||||
providers: [
|
||||
provideIcons({ isaActionFilter }),
|
||||
provideLoggerContext({ component: FilterMenuButtonComponent }),
|
||||
],
|
||||
})
|
||||
export class FilterMenuButtonComponent {
|
||||
#logger = logger();
|
||||
|
||||
scrollStrategy = inject(Overlay).scrollStrategies.block();
|
||||
|
||||
#filter = inject(FilterService);
|
||||
@@ -68,6 +79,7 @@ export class FilterMenuButtonComponent {
|
||||
* Emits `opened` or `closed` events based on the new state.
|
||||
*/
|
||||
toggle() {
|
||||
this.#logger.debug('toggle', this.open());
|
||||
const open = this.open();
|
||||
this.open.set(!open);
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"@isa/catalogue/data-access": ["libs/catalogue/data-access/src/index.ts"],
|
||||
"@isa/common/result": ["libs/common/result/src/index.ts"],
|
||||
"@isa/core/config": ["libs/core/config/src/index.ts"],
|
||||
"@isa/core/logging": ["libs/core/logging/src/index.ts"],
|
||||
"@isa/core/notifications": ["libs/core/notifications/src/index.ts"],
|
||||
"@isa/core/process": ["libs/core/process/src/index.ts"],
|
||||
"@isa/core/scanner": ["libs/core/scanner/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user