Initial Commit
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.DS_Store
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
4
.npmrc
Normal file
4
.npmrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
save-exact=true
|
||||||
|
package-lock=false
|
||||||
|
@techniker-me:registry=https://registry-node.techniker.me
|
||||||
|
//registry-node.techniker.me/:_authToken="${REGISTRY_AUTH_TOKEN}"
|
||||||
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"printWidth": 160,
|
||||||
|
"semi": true,
|
||||||
|
"singleAttributePerLine": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
8
bunfig.toml
Normal file
8
bunfig.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[install]
|
||||||
|
exact = true
|
||||||
|
|
||||||
|
[install.lockfile]
|
||||||
|
save = false
|
||||||
|
|
||||||
|
[install.scopes]
|
||||||
|
"@techniker-me" = {url = "https://registry-node.techniker.me", token = "${REGISTRY_AUTH_TOKEN}"}
|
||||||
11
eslint.config.js
Normal file
11
eslint.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import globals from 'globals';
|
||||||
|
import pluginJs from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
/** @type {import('eslint').Linter.Config[]} */
|
||||||
|
export default [
|
||||||
|
{files: ['**/*.{js,mjs,cjs,ts}']},
|
||||||
|
{languageOptions: {globals: {...globals.browser, ...globals.node}}},
|
||||||
|
pluginJs.configs.recommended,
|
||||||
|
...tseslint.configs.recommended
|
||||||
|
];
|
||||||
65
package.json
Normal file
65
package.json
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"name": "@techniker-me/logger",
|
||||||
|
"version": "0.0.15",
|
||||||
|
"description": "A logger package for logging",
|
||||||
|
"type": "module",
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"import": "./dist/browser/index.js",
|
||||||
|
"require": "./dist/node/index.js",
|
||||||
|
"default": "./dist/node/index.js"
|
||||||
|
},
|
||||||
|
"private": false,
|
||||||
|
"author": {
|
||||||
|
"name": "Alexander Zinn",
|
||||||
|
"git+url": "https://git.techniker.me/techniker-me/tools.git"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://registry-node.techniker.me"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"ci-install": "bun install",
|
||||||
|
"ci-test": "bun test",
|
||||||
|
"ci-build": "bash scripts/ci-build.sh",
|
||||||
|
"ci-deploy:ga": "bash scripts/ci-deploy.sh --beta false",
|
||||||
|
"ci-deploy:beta": "bash scripts/ci-deploy.sh --beta true",
|
||||||
|
"format": "prettier ./ --write",
|
||||||
|
"prelint": "bun run format",
|
||||||
|
"lint": "eslint \"./src\"",
|
||||||
|
"lint:fix": "eslint \"./src\" --fix",
|
||||||
|
"test": "bun test",
|
||||||
|
"clean": "rm -rf dist",
|
||||||
|
"prebuild": "bun run clean",
|
||||||
|
"build": "bash scripts/ci-build.sh",
|
||||||
|
"build:node:debug": "bun build ./src/index.ts --target=node --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/node",
|
||||||
|
"build:browser:debug": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/browser",
|
||||||
|
"build:node": "bun build ./src/index.ts --target=node --sourcemap=none --format=cjs --splitting --minify --outdir=dist/node",
|
||||||
|
"build:browser": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --splitting --minify --outdir=dist/browser",
|
||||||
|
"build:types": "tsc -p tsconfig.json",
|
||||||
|
"build:prepare-package-json": "bash scripts/prepare-package-json.sh",
|
||||||
|
"dev": "bun run build:types && bun --watch ./src/index.ts",
|
||||||
|
"versionbump:minor": "bash scripts/version-bump.sh minor",
|
||||||
|
"versionbump:major": "bash scripts/version-bump.sh major",
|
||||||
|
"versionbump:patch": "bash scripts/version-bump.sh patch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "9.21.0",
|
||||||
|
"@types/node": "22.14.1",
|
||||||
|
"bun-types": "^1.2.4",
|
||||||
|
"eslint": "9.21.0",
|
||||||
|
"globals": "16.0.0",
|
||||||
|
"prettier": "3.5.3",
|
||||||
|
"tsx": "4.19.3",
|
||||||
|
"typescript": "5.8.2",
|
||||||
|
"typescript-eslint": "8.26.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@techniker-me/tools": "2025.0.16"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"package.json",
|
||||||
|
"dist",
|
||||||
|
"README.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
29
scripts/ci-build.sh
Normal file
29
scripts/ci-build.sh
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
distDirectory=${DIST_DIRECTORY:-"dist"}
|
||||||
|
|
||||||
|
rm -rf ${distDirectory}
|
||||||
|
|
||||||
|
bun run lint
|
||||||
|
bun run build:node
|
||||||
|
bun run build:browser
|
||||||
|
bun run build:types
|
||||||
|
bun run build:prepare-package-json
|
||||||
|
|
||||||
|
echo "Copying [.npmrc] to [${distDirectory}]"
|
||||||
|
cp .npmrc ./${distDirectory}
|
||||||
|
|
||||||
|
echo "Copying [.nvmrc] to [${distDirectory}]"
|
||||||
|
cp .nvmrc ./${distDirectory}
|
||||||
|
|
||||||
|
echo "Copying [README.md] to [${distDirectory}]"
|
||||||
|
cp README.md ./${distDirectory}
|
||||||
|
|
||||||
|
ls ${distDirectory}
|
||||||
|
|
||||||
|
echo -e "\nci-build complete!"
|
||||||
|
exit 0
|
||||||
0
scripts/ci-deploy.h
Normal file
0
scripts/ci-deploy.h
Normal file
81
scripts/ci-deploy.sh
Executable file
81
scripts/ci-deploy.sh
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
registryUrl="https://registry-node.techniker.me"
|
||||||
|
packageVersionToDeploy=""
|
||||||
|
isBeta="true"
|
||||||
|
|
||||||
|
while [[ "${#}" -gt 0 ]]; do
|
||||||
|
case "${1}" in
|
||||||
|
--beta)
|
||||||
|
isBeta="${2}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--version)
|
||||||
|
packageVersionToDeploy="${2}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option [${1}]"
|
||||||
|
exit "${LINENO}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
function cleanDirectory {
|
||||||
|
local directory="${1}"
|
||||||
|
|
||||||
|
if [ -d "${directory}" ]; then
|
||||||
|
echo "Deleting [${directory}]..."
|
||||||
|
|
||||||
|
rm -rf "${directory}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePackageJsonMember {
|
||||||
|
local packageJsonPath="dist/package.json"
|
||||||
|
local memberToRemove="${1}"
|
||||||
|
|
||||||
|
if [ -f "${packageJsonPath}" ]; then
|
||||||
|
echo "Removing [${memberToRemove}] from the dist/package.json"
|
||||||
|
|
||||||
|
jq "del(.${memberToRemove})" "${packageJsonPath}" > tmp.$$.json && mv tmp.$$.json "$packageJsonPath"
|
||||||
|
else
|
||||||
|
echo "Error: [${packageJsonPath}] not found."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePackageJsonVersion {
|
||||||
|
local versionToUpdate="${1}"
|
||||||
|
|
||||||
|
if [ isBeta == "true" ]; then
|
||||||
|
echo "Version to update [${versionToUpdate}] Contains beta"
|
||||||
|
echo "Updating package.json version to [${versionToUpdate}]"
|
||||||
|
|
||||||
|
local packageJsonVersion=$(jq -r '.version' package.json)
|
||||||
|
|
||||||
|
sed -i "s/\"version\": \"${packageJsonVersion}\"/\"version\": \"${versionToUpdate}\"/" dist/package.json
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
echo "Deploying [${packageVersionToDeploy}]";
|
||||||
|
echo "isBeta [${isBeta}]"
|
||||||
|
|
||||||
|
cleanDirectory "dist"
|
||||||
|
|
||||||
|
npm run ci-build
|
||||||
|
|
||||||
|
removePackageJsonMember "devDependencies"
|
||||||
|
removePackageJsonMember "scripts"
|
||||||
|
|
||||||
|
|
||||||
|
if [ "${isBeta}" == "true" ]; then
|
||||||
|
updatePackageJsonVersion "${packageVersionToDeploy}"
|
||||||
|
npm publish --registry "${registryUrl}" --tag beta
|
||||||
|
else
|
||||||
|
npm publish --registry "${registryUrl}"
|
||||||
|
fi
|
||||||
16
scripts/prepare-package-json.sh
Normal file
16
scripts/prepare-package-json.sh
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
distDirectory=${DIST_DIRECTORY:-"dist"}
|
||||||
|
packageJsonPath=${PACKAGE_JSON_PATH:-"package.json"}
|
||||||
|
|
||||||
|
if [ ! -d "${distDirectory}" ]; then
|
||||||
|
echo "Unable to prepare package.json, [${distDirectory}] not found"
|
||||||
|
exit $LINENO
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Preparing [package.json] to [${distDirectory}]"
|
||||||
|
jq '{name, version, author, type, types, exports, files, publishConfig}' "${packageJsonPath}" > "${distDirectory}/package.json"
|
||||||
37
scripts/version-bump.sh
Executable file
37
scripts/version-bump.sh
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check if a parameter was provided
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: ./version-bump.sh [major|minor|patch]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read current version
|
||||||
|
current_version=$(node -p "require('./package.json').version")
|
||||||
|
IFS='.' read -r major minor patch <<< "$current_version"
|
||||||
|
|
||||||
|
# Update version based on parameter
|
||||||
|
case "$1" in
|
||||||
|
"major")
|
||||||
|
new_version="$((major + 1)).0.0"
|
||||||
|
;;
|
||||||
|
"minor")
|
||||||
|
new_version="$major.$((minor + 1)).0"
|
||||||
|
;;
|
||||||
|
"patch")
|
||||||
|
new_version="$major.$minor.$((patch + 1))"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid parameter. Use: major, minor, or patch"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Update package.json
|
||||||
|
node -e "
|
||||||
|
const pkg = require('./package.json');
|
||||||
|
pkg.version = '$new_version';
|
||||||
|
require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "Version bumped from $current_version to $new_version"
|
||||||
11
src/Defaults.ts
Normal file
11
src/Defaults.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import {LoggingLevel} from './level/LoggingLevel';
|
||||||
|
|
||||||
|
export default class Defaults {
|
||||||
|
static get loggingLevel(): LoggingLevel {
|
||||||
|
return LoggingLevel.Info;
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor() {
|
||||||
|
throw new Error('Defaults is a static class that may not be instantiated');
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/ILogger.ts
Normal file
8
src/ILogger.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export default interface ILogger {
|
||||||
|
info(message: string, ...optionalParameters: unknown[]): void;
|
||||||
|
warn(message: string, ...optionalParameters: unknown[]): void;
|
||||||
|
error(message: string, ...optionalParameters: unknown[]): void;
|
||||||
|
debug(message: string, ...optionalParameters: unknown[]): void;
|
||||||
|
trace(message: string, ...optionalParameters: unknown[]): void;
|
||||||
|
silly(message: string, ...optionalParameters: unknown[]): void;
|
||||||
|
}
|
||||||
102
src/Logger.ts
Normal file
102
src/Logger.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import IAppender from './appenders/IAppender';
|
||||||
|
import {LoggingLevel} from './level/LoggingLevel';
|
||||||
|
import LoggingLevelMapping from './level/LoggingLevelMapping';
|
||||||
|
import Threshold from './level/Threshold';
|
||||||
|
|
||||||
|
export default class Logger {
|
||||||
|
private readonly _category: string;
|
||||||
|
private readonly _threshold: Threshold;
|
||||||
|
private readonly _appenders: Set<IAppender>;
|
||||||
|
|
||||||
|
constructor(category: string, threshold: Threshold, appenders: Set<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.Warn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Warn, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value > LoggingLevel.Error) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Error, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public silly(message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
if (this._threshold.value > LoggingLevel.Silly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(LoggingLevel.Silly, message, ...optionalParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatMessage(message: string, ...optionalParameters: unknown[]): string {
|
||||||
|
let optionalParameterIndex = 0;
|
||||||
|
|
||||||
|
return message.replace(/%[sdj]/g, match => {
|
||||||
|
if (optionalParameterIndex >= optionalParameters.length) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
const param = optionalParameters[optionalParameterIndex++];
|
||||||
|
|
||||||
|
switch (match) {
|
||||||
|
case '%s':
|
||||||
|
return String(param);
|
||||||
|
case '%d':
|
||||||
|
return typeof param === 'number' ? param.toString() : 'NaN';
|
||||||
|
case '%j':
|
||||||
|
try {
|
||||||
|
return JSON.stringify(param);
|
||||||
|
} catch {
|
||||||
|
return '[Circular]';
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private log(loggingLevel: LoggingLevel, message: string, ...optionalParameters: unknown[]): void {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
const formattedMessage = this.formatMessage(message, ...optionalParameters);
|
||||||
|
const level = LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(loggingLevel);
|
||||||
|
|
||||||
|
for (const appender of this._appenders) {
|
||||||
|
appender.log(timestamp, level, this._category, formattedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/LoggerFactory.ts
Normal file
49
src/LoggerFactory.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import {Disposable} from '@techniker-me/tools';
|
||||||
|
import ConsoleAppender from './appenders/ConsoleAppender';
|
||||||
|
import IAppender from './appenders/IAppender';
|
||||||
|
import Threshold from './level/Threshold';
|
||||||
|
import Logger from './Logger';
|
||||||
|
import {LoggingLevelType} from './level/LoggingLevel';
|
||||||
|
import LoggingLevelMapping from './level/LoggingLevelMapping';
|
||||||
|
import TechnikerMeAppender from './appenders/TechnikerMeAppender';
|
||||||
|
|
||||||
|
type Category = string;
|
||||||
|
|
||||||
|
export default class LoggerFactory {
|
||||||
|
private static readonly _appenders: Set<IAppender> = new Set();
|
||||||
|
private static readonly _threshold: Threshold = new Threshold();
|
||||||
|
private static readonly _loggers: Map<Category, Logger> = new Map();
|
||||||
|
|
||||||
|
static {
|
||||||
|
this.applyConsoleAppender();
|
||||||
|
this.applyRemoteAppender();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getLogger(category: string): Logger {
|
||||||
|
if (!LoggerFactory._loggers.has(category)) {
|
||||||
|
this._loggers.set(category, new Logger(category, LoggerFactory._threshold, LoggerFactory._appenders));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoggerFactory._loggers.get(category) as Logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static applyApppender(appender: IAppender): Disposable {
|
||||||
|
LoggerFactory._appenders.add(appender);
|
||||||
|
|
||||||
|
return new Disposable(() => LoggerFactory._appenders.delete(appender));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static setLoggingLevel(loggingLevelType: LoggingLevelType): void {
|
||||||
|
const loggingLevel = LoggingLevelMapping.convertLoggingLevelTypeToLoggingLevel(loggingLevelType);
|
||||||
|
|
||||||
|
LoggerFactory._threshold.value = loggingLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static applyConsoleAppender(): void {
|
||||||
|
LoggerFactory.applyApppender(new ConsoleAppender());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static applyRemoteAppender(): void {
|
||||||
|
LoggerFactory.applyApppender(new TechnikerMeAppender());
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/appenders/ConsoleAppender.ts
Normal file
31
src/appenders/ConsoleAppender.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import {LoggingLevel, LoggingLevelType} from '../level/LoggingLevel';
|
||||||
|
import LoggingLevelMapping from '../level/LoggingLevelMapping';
|
||||||
|
import IAppender from './IAppender';
|
||||||
|
import {assertUnreachable} from '@techniker-me/tools';
|
||||||
|
|
||||||
|
export default class ConsoleAppender implements IAppender {
|
||||||
|
public log(timestamp: string, level: LoggingLevelType, category: string, message: string) {
|
||||||
|
const loggingLevel = LoggingLevelMapping.convertLoggingLevelTypeToLoggingLevel(level);
|
||||||
|
|
||||||
|
switch (loggingLevel) {
|
||||||
|
case LoggingLevel.Off:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LoggingLevel.Warn:
|
||||||
|
case LoggingLevel.Error:
|
||||||
|
console.error(`${timestamp} [${level}] [${category}] ${message}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LoggingLevel.Info:
|
||||||
|
case LoggingLevel.Debug:
|
||||||
|
case LoggingLevel.Trace:
|
||||||
|
case LoggingLevel.Silly:
|
||||||
|
case LoggingLevel.All:
|
||||||
|
console.log(`${timestamp} [${level}] [${category}] ${message}`);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertUnreachable(loggingLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/appenders/IAppender.ts
Normal file
5
src/appenders/IAppender.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import {LoggingLevelType} from '../level/LoggingLevel';
|
||||||
|
|
||||||
|
export default interface IAppender {
|
||||||
|
log(timestamp: string, level: LoggingLevelType, category: string, message: string): void;
|
||||||
|
}
|
||||||
60
src/appenders/TechnikerMeAppender.ts
Normal file
60
src/appenders/TechnikerMeAppender.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import type {LoggingLevelType} from '../level/LoggingLevel';
|
||||||
|
import type IAppender from './IAppender';
|
||||||
|
|
||||||
|
type LogMessage = {
|
||||||
|
timestamp: string;
|
||||||
|
level: string;
|
||||||
|
category: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class TechnikerMeAppender implements IAppender {
|
||||||
|
private readonly _logRecorderUrl: string = 'https://logserver.techniker.me/api/logs';
|
||||||
|
private readonly _domain: string = typeof window !== 'undefined' ? (window.location?.hostname ?? '') : '';
|
||||||
|
private readonly _logMessageQueue: LogMessage[] = [];
|
||||||
|
private _pendingPostLogMessagePromise: Promise<Response | undefined> | undefined = undefined;
|
||||||
|
|
||||||
|
public log(timestamp: string, level: LoggingLevelType, category: string, message: string): void {
|
||||||
|
const logMessage = {
|
||||||
|
timestamp,
|
||||||
|
domain: this._domain,
|
||||||
|
level,
|
||||||
|
category,
|
||||||
|
message
|
||||||
|
};
|
||||||
|
this.queueMessage(logMessage);
|
||||||
|
this.postLogMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async postLogMessage(): Promise<void> {
|
||||||
|
const logMessage = this._logMessageQueue.shift();
|
||||||
|
|
||||||
|
if (!logMessage || this._pendingPostLogMessagePromise !== undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof fetch === 'undefined') {
|
||||||
|
console.error('Fetch API is not available in this environment');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pendingPostLogMessagePromise = fetch(this._logRecorderUrl, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Accept: 'application/json'
|
||||||
|
},
|
||||||
|
mode: 'no-cors',
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(logMessage)
|
||||||
|
}).then(() => (this._pendingPostLogMessagePromise = undefined));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Unable to send logs due to [%o]', e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private queueMessage(logMessage: LogMessage): void {
|
||||||
|
this._logMessageQueue.push(logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/index.ts
Normal file
8
src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type ILogger from './ILogger';
|
||||||
|
import type IAppender from './appenders/IAppender';
|
||||||
|
import LoggerFactory from './LoggerFactory';
|
||||||
|
import LoggingLevelMapping from './level/LoggingLevelMapping';
|
||||||
|
|
||||||
|
export type {ILogger, IAppender};
|
||||||
|
export {LoggerFactory, LoggingLevelMapping};
|
||||||
|
export default {LoggerFactory, LoggingLevelMapping};
|
||||||
12
src/level/LoggingLevel.ts
Normal file
12
src/level/LoggingLevel.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export enum LoggingLevel {
|
||||||
|
Off = -1,
|
||||||
|
Info = 10,
|
||||||
|
Warn = 20,
|
||||||
|
Error = 30,
|
||||||
|
Debug = 40,
|
||||||
|
Trace = 50,
|
||||||
|
Silly = 60,
|
||||||
|
All = 100
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LoggingLevelType = 'Off' | 'Info' | 'Warn' | 'Error' | 'Debug' | 'Trace' | 'Silly' | 'All';
|
||||||
51
src/level/LoggingLevelMapping.ts
Normal file
51
src/level/LoggingLevelMapping.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import {assertUnreachable} from '@techniker-me/tools';
|
||||||
|
import {LoggingLevel, LoggingLevelType} from './LoggingLevel';
|
||||||
|
|
||||||
|
export default class LoggingLevelMapping {
|
||||||
|
public static convertLoggingLevelToLoggingLevelType(loggingLevel: LoggingLevel): LoggingLevelType {
|
||||||
|
switch (loggingLevel) {
|
||||||
|
case LoggingLevel.Off:
|
||||||
|
return 'Off';
|
||||||
|
case LoggingLevel.Info:
|
||||||
|
return 'Info';
|
||||||
|
case LoggingLevel.Warn:
|
||||||
|
return 'Warn';
|
||||||
|
case LoggingLevel.Error:
|
||||||
|
return 'Error';
|
||||||
|
case LoggingLevel.Debug:
|
||||||
|
return 'Debug';
|
||||||
|
case LoggingLevel.Trace:
|
||||||
|
return 'Trace';
|
||||||
|
case LoggingLevel.Silly:
|
||||||
|
return 'Silly';
|
||||||
|
case LoggingLevel.All:
|
||||||
|
return 'All';
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertUnreachable(loggingLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static convertLoggingLevelTypeToLoggingLevel(loggingLevelType: LoggingLevelType): LoggingLevel {
|
||||||
|
switch (loggingLevelType) {
|
||||||
|
case 'Off':
|
||||||
|
return LoggingLevel.Off;
|
||||||
|
case 'Info':
|
||||||
|
return LoggingLevel.Info;
|
||||||
|
case 'Warn':
|
||||||
|
return LoggingLevel.Warn;
|
||||||
|
case 'Error':
|
||||||
|
return LoggingLevel.Error;
|
||||||
|
case 'Debug':
|
||||||
|
return LoggingLevel.Debug;
|
||||||
|
case 'Trace':
|
||||||
|
return LoggingLevel.Trace;
|
||||||
|
case 'Silly':
|
||||||
|
return LoggingLevel.Silly;
|
||||||
|
case 'All':
|
||||||
|
return LoggingLevel.All;
|
||||||
|
default:
|
||||||
|
assertUnreachable(loggingLevelType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/level/Threshold.ts
Normal file
21
src/level/Threshold.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import {Subject} from '@techniker-me/tools';
|
||||||
|
import Defaults from '../Defaults';
|
||||||
|
import {LoggingLevel} from './LoggingLevel';
|
||||||
|
|
||||||
|
class Threshold {
|
||||||
|
private _threshold: Subject<LoggingLevel> = new Subject(LoggingLevel.Debug);
|
||||||
|
|
||||||
|
constructor(loggingLevel?: LoggingLevel) {
|
||||||
|
this._threshold = new Subject(loggingLevel ?? Defaults.loggingLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(value: LoggingLevel) {
|
||||||
|
this._threshold.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): LoggingLevel {
|
||||||
|
return this._threshold.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Threshold;
|
||||||
26
tsconfig.json
Normal file
26
tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist/types",
|
||||||
|
"rootDir": "src",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"target": "ES2020",
|
||||||
|
"lib": ["DOM", "ES2020"],
|
||||||
|
"allowJs": false,
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"composite": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["dist", "node_modules", "tests"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user