Initial Commit
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# dependencies (bun install)
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# output
|
||||||
|
out
|
||||||
|
dist
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
3
.npmrc
Normal file
3
.npmrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@techniker-me:registry=https://registry-node.techniker.me
|
||||||
|
package-lock=false
|
||||||
|
save-exact=true
|
||||||
15
README.md
Normal file
15
README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# channeltests-3
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.2.20. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
||||||
5
bunfig.toml
Normal file
5
bunfig.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
[install.lockfile]
|
||||||
|
save = false
|
||||||
|
|
||||||
|
[install.scopes]
|
||||||
|
"@techniker-me" = "https://registry-node.techniker.me"
|
||||||
3
lang/assertUnreachable.ts
Normal file
3
lang/assertUnreachable.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function assertUnreachable(value: never): never {
|
||||||
|
throw new Error(`Unreachable code: ${value}`);
|
||||||
|
}
|
||||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "channeltests-3",
|
||||||
|
"version": "2025.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "5.9.8"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@techniker-me/pcast-api": "2025.0.2",
|
||||||
|
"@techniker-me/rtmp-push": "2025.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
test/config/CommandLine.ts
Normal file
5
test/config/CommandLine.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default class CommandLine {
|
||||||
|
private constructor() {
|
||||||
|
throw new Error('[CommandLine] is a static class that may not be instantiated');
|
||||||
|
}
|
||||||
|
}
|
||||||
0
test/config/TestConfiguration.ts
Normal file
0
test/config/TestConfiguration.ts
Normal file
4
test/config/TestRunner.ts
Normal file
4
test/config/TestRunner.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import PCastAPI from '@techniker-me/pcast-api';
|
||||||
|
import RtmpPush from '@technniker-me/rtmp-push';
|
||||||
|
|
||||||
|
class TestRunner {}
|
||||||
120
test/logger/Logger.ts
Normal file
120
test/logger/Logger.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import IAppender from './appenders/IAppender';
|
||||||
|
import {LoggingLevel, LoggingLevelMapping} from './LoggingLevel';
|
||||||
|
import type {Threshold} from './Threshold';
|
||||||
|
|
||||||
|
export class Logger {
|
||||||
|
private readonly _category: string;
|
||||||
|
private readonly _threshold: Threshold;
|
||||||
|
private readonly _appenders: IAppender[];
|
||||||
|
|
||||||
|
constructor({category, threshold, appenders}: {category: string; threshold: Threshold; appenders: IAppender[]}) {
|
||||||
|
this._category = category;
|
||||||
|
this._threshold = threshold;
|
||||||
|
this._appenders = appenders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value < LoggingLevel.Info) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Info, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value < LoggingLevel.Warning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Warning, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value < LoggingLevel.Error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Error, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fatal(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value < LoggingLevel.Fatal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Fatal, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public debug(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value < LoggingLevel.Debug) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Debug, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public trace(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value < LoggingLevel.Trace) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Trace, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private log(level: LoggingLevel, message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const formattedMessage = this.formatMessage(message, ...optionalParameters);
|
||||||
|
const levelString = LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(level);
|
||||||
|
|
||||||
|
// Safely call appenders with error handling
|
||||||
|
this._appenders.forEach(appender => {
|
||||||
|
try {
|
||||||
|
appender.append(timestamp, level, this._category, `[${levelString}] ${formattedMessage}`);
|
||||||
|
} catch (error) {
|
||||||
|
// Fallback to console if appender fails
|
||||||
|
console.error(`[Logger] Appender failed for category ${this._category}:`, error);
|
||||||
|
console.log(`${timestamp} [${this._category}] [${levelString}] ${formattedMessage}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatMessage(message: string, ...optionalParameters: unknown[]): string {
|
||||||
|
if (optionalParameters.length === 0) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// More efficient parameter substitution
|
||||||
|
let result = message;
|
||||||
|
let paramIndex = 0;
|
||||||
|
|
||||||
|
// Replace all {} placeholders with parameters
|
||||||
|
while (result.includes('{}') && paramIndex < optionalParameters.length) {
|
||||||
|
const paramString = this.parameterToString(optionalParameters[paramIndex]);
|
||||||
|
result = result.replace('{}', paramString);
|
||||||
|
paramIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append remaining parameters if any
|
||||||
|
if (paramIndex < optionalParameters.length) {
|
||||||
|
const remainingParams = optionalParameters.slice(paramIndex)
|
||||||
|
.map(param => this.parameterToString(param))
|
||||||
|
.join(' ');
|
||||||
|
result += ` ${remainingParams}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parameterToString(param: unknown): string {
|
||||||
|
if (param === null) return 'null';
|
||||||
|
if (param === undefined) return 'undefined';
|
||||||
|
if (typeof param === 'object') {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(param);
|
||||||
|
} catch {
|
||||||
|
return String(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
test/logger/LoggerFactory.ts
Normal file
27
test/logger/LoggerFactory.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { ConsoleAppender } from './appenders/ConsoleAppender';
|
||||||
|
import IAppender from './appenders/IAppender';
|
||||||
|
import {Logger} from './Logger';
|
||||||
|
import {LoggingLevel} from './LoggingLevel';
|
||||||
|
import {Threshold} from './Threshold';
|
||||||
|
|
||||||
|
export default class LoggerFactory {
|
||||||
|
private static readonly _loggerForCategory: Map<string, Logger> = new Map();
|
||||||
|
private static readonly _threshold: Threshold = new Threshold({level: LoggingLevel.Debug});
|
||||||
|
private static readonly _appenders: IAppender[] = [new ConsoleAppender()];
|
||||||
|
|
||||||
|
public static getLogger(category: string): Logger {
|
||||||
|
let logger = LoggerFactory._loggerForCategory.get(category);
|
||||||
|
|
||||||
|
if (logger === undefined) {
|
||||||
|
logger = new Logger({category, threshold: LoggerFactory._threshold, appenders: LoggerFactory._appenders});
|
||||||
|
|
||||||
|
LoggerFactory._loggerForCategory.set(category, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
throw new Error('[LoggerFactory] is a static class that may not be instantiated');
|
||||||
|
}
|
||||||
|
}
|
||||||
60
test/logger/LoggingLevel.ts
Normal file
60
test/logger/LoggingLevel.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
export enum LoggingLevel {
|
||||||
|
Off = 0,
|
||||||
|
Fatal = 1,
|
||||||
|
Error = 2,
|
||||||
|
Warning = 3,
|
||||||
|
Info = 4,
|
||||||
|
Debug = 5,
|
||||||
|
Trace = 6,
|
||||||
|
All = 7
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoggingLevelType = 'Off' | 'Fatal' | 'Error' | 'Warning' | 'Info' | 'Debug' | 'Trace' | 'All';
|
||||||
|
|
||||||
|
export class LoggingLevelMapping {
|
||||||
|
public static convertLoggingLevelToLoggingLevelType(level: LoggingLevel): LoggingLevelType {
|
||||||
|
switch (level) {
|
||||||
|
case LoggingLevel.Off:
|
||||||
|
return 'Off';
|
||||||
|
case LoggingLevel.Fatal:
|
||||||
|
return 'Fatal';
|
||||||
|
case LoggingLevel.Error:
|
||||||
|
return 'Error';
|
||||||
|
case LoggingLevel.Warning:
|
||||||
|
return 'Warning';
|
||||||
|
case LoggingLevel.Info:
|
||||||
|
return 'Info';
|
||||||
|
case LoggingLevel.Debug:
|
||||||
|
return 'Debug';
|
||||||
|
case LoggingLevel.Trace:
|
||||||
|
return 'Trace';
|
||||||
|
case LoggingLevel.All:
|
||||||
|
return 'All';
|
||||||
|
default:
|
||||||
|
throw new Error(`[LoggingLevelMapping] Received unknown logging level [${level}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static convertLoggingLevelTypeToLoggingLevel(level: LoggingLevelType): LoggingLevel {
|
||||||
|
switch (level) {
|
||||||
|
case 'Off':
|
||||||
|
return LoggingLevel.Off;
|
||||||
|
case 'Fatal':
|
||||||
|
return LoggingLevel.Fatal;
|
||||||
|
case 'Error':
|
||||||
|
return LoggingLevel.Error;
|
||||||
|
case 'Warning':
|
||||||
|
return LoggingLevel.Warning;
|
||||||
|
case 'Info':
|
||||||
|
return LoggingLevel.Info;
|
||||||
|
case 'Debug':
|
||||||
|
return LoggingLevel.Debug;
|
||||||
|
case 'Trace':
|
||||||
|
return LoggingLevel.Trace;
|
||||||
|
case 'All':
|
||||||
|
return LoggingLevel.All;
|
||||||
|
default:
|
||||||
|
throw new Error(`[LoggingLevelMapping] Received unknown logging level type [${level}]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
test/logger/Threshold.ts
Normal file
13
test/logger/Threshold.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import {LoggingLevel, LoggingLevelMapping, type LoggingLevelType} from './LoggingLevel';
|
||||||
|
|
||||||
|
export class Threshold {
|
||||||
|
private readonly _level: {value: LoggingLevel};
|
||||||
|
|
||||||
|
constructor({level}: {level: LoggingLevel}) {
|
||||||
|
this._level = {value: level};
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): LoggingLevel {
|
||||||
|
return this._level.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
test/logger/appenders/ConsoleAppender.ts
Normal file
31
test/logger/appenders/ConsoleAppender.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import IAppender from './IAppender';
|
||||||
|
import {LoggingLevel} from '../LoggingLevel';
|
||||||
|
import assertUnreachable from '../../../lang/assertUnreachable';
|
||||||
|
|
||||||
|
export class ConsoleAppender implements IAppender {
|
||||||
|
append(timestamp: string, level: LoggingLevel, category: string, message: string): void {
|
||||||
|
switch (level) {
|
||||||
|
case LoggingLevel.Debug:
|
||||||
|
case LoggingLevel.Info:
|
||||||
|
case LoggingLevel.Warning:
|
||||||
|
case LoggingLevel.Trace:
|
||||||
|
console.info(`${timestamp} [${category}] ${message}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LoggingLevel.Error:
|
||||||
|
console.error(`${timestamp} [${category}] ${message}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LoggingLevel.Fatal:
|
||||||
|
console.error(`${timestamp} [${category}] ${message}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LoggingLevel.Off:
|
||||||
|
case LoggingLevel.All:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertUnreachable(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
test/logger/appenders/IAppender.ts
Normal file
7
test/logger/appenders/IAppender.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import {LoggingLevel} from '../LoggingLevel';
|
||||||
|
|
||||||
|
interface IAppender {
|
||||||
|
append(timestamp: string, level: LoggingLevel, category: string, message: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IAppender;
|
||||||
29
tsconfig.json
Normal file
29
tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Environment setup & latest features
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "Preserve",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": false,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user